diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..88a306a --- /dev/null +++ b/Makefile @@ -0,0 +1,37 @@ +.DEFAULT_GOAL := help + +WORKING_DIR := scormxblock +JS_TARGET := $(WORKING_DIR)/public/js/translations +EXTRACT_DIR := $(WORKING_DIR)/translations/en/LC_MESSAGES +EXTRACTED_DJANGO := $(EXTRACT_DIR)/django-partial.po +EXTRACTED_TEXT := $(EXTRACT_DIR)/text.po +EXTRACTED_DJANGOJS := $(EXTRACT_DIR)/djangojs-partial.po +EXTRACTED_TEXTJS := $(EXTRACT_DIR)/textjs.po + + +help: ## display this help message + @echo "Please use \`make ' where is one of" + @perl -nle'print $& if m{^[a-zA-Z_-]+:.*?## .*$$}' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m %-25s\033[0m %s\n", $$1, $$2}' + +extract_translations: ## extract strings to be translated, outputting .po files + cd $(WORKING_DIR) && i18n_tool extract + mv $(EXTRACTED_DJANGO) $(EXTRACTED_TEXT) + if [ -f "$(EXTRACTED_DJANGOJS)" ]; then mv $(EXTRACTED_DJANGOJS) $(EXTRACTED_TEXTJS); fi + rm -f $(EXTRACTED_DJANGO) + rm -f $(EXTRACTED_DJANGOJS) + find $(EXTRACT_DIR) -type f -name "*.po" -exec sed -i'' -e 's/nplurals=INTEGER/nplurals=2/' {} \; + find $(EXTRACT_DIR) -type f -name "*.po" -exec sed -i'' -e 's/plural=EXPRESSION/plural=\(n != 1\)/' {} \; + +compile_translations: ## compile translation files, outputting .mo files for each supported language + cd $(WORKING_DIR) && i18n_tool generate + python manage.py compilejsi18n --output $(JS_TARGET) + +detect_changed_source_translations: + cd $(WORKING_DIR) && i18n_tool changed + +dummy_translations: ## generate dummy translation (.po) files + cd $(WORKING_DIR) && i18n_tool dummy + +build_dummy_translations: dummy_translations compile_translations ## generate and compile dummy translation files + +validate_translations: build_dummy_translations detect_changed_source_translations ## validate translations diff --git a/README.md b/README.md index 6715d81..c6bafdd 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ edx_xblock_scorm ========================= XBlock to display SCORM content within the Open edX LMS. Editable within Open edx Studio. Will save student state and report scores to the progress tab of the course. -Currently supports SCORM 1.2 standard, but not yet SCORM 2004. It supports multi-SCO content packages. +Currently supports SCORM 1.2 standard, but not yet SCORM 2004. It supports multi-SCO content packages. # Installation @@ -10,12 +10,12 @@ Currently supports SCORM 1.2 standard, but not yet SCORM 2004. It supports mult ## Using Ansible * Add to your `server-vars.yml` file this entry underneath the `EDXAPP_EXTRA_REQUIREMENTS` key - + ``` - name: 'git+https://github.com/appsembler/edx_xblock_scorm@use_ssla_player#egg=scormxblock' ``` -* Configure a SCORM player backend in the XBlock settings, under the `"ScormXBlock"`. Currently you must provide the SSLA player by JCA Solutions. +* Configure a SCORM player backend in the XBlock settings, under the `"ScormXBlock"`. Currently you must provide the SSLA player by JCA Solutions. ``` EDXAPP_XBLOCK_SETTINGS: @@ -38,7 +38,7 @@ Each backend is a key under `SCORM_PLAYER_BACKENDS` and should provide a `"name" * Configure Staff Debug Info: If you want staff debug info for SCORM XBlocks to be visible to instructors and other permitted staff, add this key to your `EDXAPP_XBLOCK_SETTINGS` key for 'ScormXBlock'. Only the 'Delete Student State' action will work; 'Reset Student Attempts' and 'Rescore Student Submission' are not yet operable, but may be in the future. -``` +``` "SCORM_DISPLAY_STAFF_DEBUG_INFO": true ``` @@ -49,12 +49,34 @@ Nginx (or other front-end web server) must be configured to serve SCORM content. # Usage * In Studio, add `scormxblock` to the list of advanced modules in the advanced settings of a course. -* Add a 'scorm' component to your Unit. -* Specify a display name, an optional description, and choose a 'player' (IMPORTANT: currently only works using the commercial SSLA player from JCA Solutions. Contact Appsembler if you would like assistance with this). +* Add a 'scorm' component to your Unit. +* Specify a display name, an optional description, and choose a 'player' (IMPORTANT: currently only works using the commercial SSLA player from JCA Solutions. Contact Appsembler if you would like assistance with this). * Specify a weight for your SCORM content within your Open edX course. For example, your course SCO may have an internal max. score of 100. If you give your SCORM component a weight of 10 and a student scores 50 within the SCORM course, they will receive 5 points within Open edX. SCORM components can be weighted as normal against other gradable Open edX components. -* Choose a display format (popup or iframe -- only use iframe if your SCORM content package itself creates a popup window) and display size. +* Choose a display format (popup or iframe -- only use iframe if your SCORM content package itself creates a popup window) and display size. * Upload a zip file containint your content package. The `imsmanifest.xml` file must be at the root of the zipped package (i.e., make sure you don't have an additional directory at the root of the Zip archive which can handle if e.g., you select an entire folder and use Mac OS X's compress feature). * Publish your content as usual. +# Translation (i18n) + +This repo offers multiple make targets to automate the translation tasks. +Each make target will be explained below: + +- `extract_translations`. Use [`i18n_tool` extract](https://github.com/edx/i18n-tools) to create `.po` files based on all the tagged strings in the python and javascript code. +- `compile_translations`. Use [`i18n_tool` generate](https://github.com/edx/i18n-tools) to create `.mo` compiled files. +- `detect_changed_source_translations`. Use [`i18n_tool` changed](https://github.com/edx/i18n-tools) to identify any updated translations. +- `validate_translations`. Compile translations and check the source translations haven't changed. + +If you want to add a new language: + 1. Add language to `scormxblock/translations/config.yaml` + 2. Make sure all tagged strings have been extracted: + ```bash + make extract_translations + ``` + 3. Clone `en` directory to `scormxblock/translations//` for example: `scormxblock/translations/fa_IR/` + 4. Make necessary changes to translation files headers. Make sure you have proper `Language` and `Plural-Forms` lines. + 5. When you finished your modification process, re-compile the translation messages. + ```bash + make compile_translations + ``` diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..a4151a8 --- /dev/null +++ b/manage.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "scormxblock.settings") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/scormxblock/conf/locale b/scormxblock/conf/locale new file mode 120000 index 0000000..5ac3362 --- /dev/null +++ b/scormxblock/conf/locale @@ -0,0 +1 @@ +../translations \ No newline at end of file diff --git a/scormxblock/public/js/translations/en/textjs.js b/scormxblock/public/js/translations/en/textjs.js new file mode 100644 index 0000000..d8dafc6 --- /dev/null +++ b/scormxblock/public/js/translations/en/textjs.js @@ -0,0 +1,101 @@ + + (function(global){ + var ScormxBlockXBlockI18N = { + init: function() { + + +(function (globals) { + + var django = globals.django || (globals.django = {}); + + + django.pluralidx = function (count) { return (count == 1) ? 0 : 1; }; + + + + /* gettext identity library */ + + django.gettext = function (msgid) { return msgid; }; + django.ngettext = function (singular, plural, count) { return (count == 1) ? singular : plural; }; + django.gettext_noop = function (msgid) { return msgid; }; + django.pgettext = function (context, msgid) { return msgid; }; + django.npgettext = function (context, singular, plural, count) { return (count == 1) ? singular : plural; }; + + + django.interpolate = function (fmt, obj, named) { + if (named) { + return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])}); + } else { + return fmt.replace(/%s/g, function(match){return String(obj.shift())}); + } + }; + + + /* formatting library */ + + django.formats = { + "DATETIME_FORMAT": "N j, Y, P", + "DATETIME_INPUT_FORMATS": [ + "%Y-%m-%d %H:%M:%S", + "%Y-%m-%d %H:%M:%S.%f", + "%Y-%m-%d %H:%M", + "%Y-%m-%d", + "%m/%d/%Y %H:%M:%S", + "%m/%d/%Y %H:%M:%S.%f", + "%m/%d/%Y %H:%M", + "%m/%d/%Y", + "%m/%d/%y %H:%M:%S", + "%m/%d/%y %H:%M:%S.%f", + "%m/%d/%y %H:%M", + "%m/%d/%y" + ], + "DATE_FORMAT": "N j, Y", + "DATE_INPUT_FORMATS": [ + "%Y-%m-%d", + "%m/%d/%Y", + "%m/%d/%y" + ], + "DECIMAL_SEPARATOR": ".", + "FIRST_DAY_OF_WEEK": "0", + "MONTH_DAY_FORMAT": "F j", + "NUMBER_GROUPING": "3", + "SHORT_DATETIME_FORMAT": "m/d/Y P", + "SHORT_DATE_FORMAT": "m/d/Y", + "THOUSAND_SEPARATOR": ",", + "TIME_FORMAT": "P", + "TIME_INPUT_FORMATS": [ + "%H:%M:%S", + "%H:%M:%S.%f", + "%H:%M" + ], + "YEAR_MONTH_FORMAT": "F Y" + }; + + django.get_format = function (format_type) { + var value = django.formats[format_type]; + if (typeof(value) == 'undefined') { + return format_type; + } else { + return value; + } + }; + + /* add to global namespace */ + globals.pluralidx = django.pluralidx; + globals.gettext = django.gettext; + globals.ngettext = django.ngettext; + globals.gettext_noop = django.gettext_noop; + globals.pgettext = django.pgettext; + globals.npgettext = django.npgettext; + globals.interpolate = django.interpolate; + globals.get_format = django.get_format; + +}(this)); + + + } + }; + ScormxBlockXBlockI18N.init(); + global.ScormxBlockXBlockI18N = ScormxBlockXBlockI18N; + }(this)); + \ No newline at end of file diff --git a/scormxblock/settings.py b/scormxblock/settings.py new file mode 100644 index 0000000..7ae83fc --- /dev/null +++ b/scormxblock/settings.py @@ -0,0 +1,83 @@ +""" +Django settings for the scormxblock 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, ...) +import os +import yaml + +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', 'xydut433=!s!i(n9u&1oiyv!hu1k=(h-)nuu30d(gd(ew%7+1w') + +# 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', + 'scormxblock', + 'django.contrib.auth', + 'django.contrib.contenttypes', +) + +# Internationalization +# https://docs.djangoproject.com/en/1.6/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.6/howto/static-files/ + +STATIC_URL = '/static/' + +LOCALE_PATHS = [ + os.path.join(BASE_DIR, 'scormxblock/translations'), +] + +# statici18n +# http://django-statici18n.readthedocs.io/en/latest/settings.html + +with open(os.path.join(BASE_DIR, 'scormxblock/translations/config.yaml'), 'r') as locale_config_file: + LOCALE_CONFIG = yaml.load(locale_config_file) + + LANGUAGES = [ + (code, code,) + for code in LOCALE_CONFIG['locales'] + LOCALE_CONFIG['dummy_locales'] + ] + +STATICI18N_DOMAIN = 'textjs' + +STATICI18N_NAMESPACE = 'ScormxBlockXBlockI18N' +STATICI18N_PACKAGES = ( + 'scormxblock', +) +STATICI18N_ROOT = 'scormxblock/public/js' +STATICI18N_OUTPUT_DIR = 'translations' diff --git a/scormxblock/translations/config.yaml b/scormxblock/translations/config.yaml new file mode 100644 index 0000000..37550c7 --- /dev/null +++ b/scormxblock/translations/config.yaml @@ -0,0 +1,29 @@ +# Configuration for i18n workflow. + +locales: + - en # English - Source Language + - ar # Arabic + - es_419 # Spanish (Latin America) + - ja_JP # Japanese + - ko_KR # Korean + - fr # French + - pt_BR # Portuguese (Brazil) + - zh_CN # Chinese (China) + - de_DE # German + - pl # Polish + +# The locales used for fake-accented English, for testing. +dummy_locales: + - eo + +# Directories we don't search for strings. +ignore_dirs: + - tests + - translations + +# i18n_tools produces just "django-partial.po" by default, but we need "text.po": +generate_merge: + text.po: + - django-partial.po + textjs.po: + - djangojs-partial.po diff --git a/scormxblock/translations/en/LC_MESSAGES/text.po b/scormxblock/translations/en/LC_MESSAGES/text.po new file mode 100644 index 0000000..b1ae06e --- /dev/null +++ b/scormxblock/translations/en/LC_MESSAGES/text.po @@ -0,0 +1,158 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-09-01 05:30+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: scormxblock.py:53 +msgid "Display Name" +msgstr "" + +#: scormxblock.py:54 +msgid "Display name for this module" +msgstr "" + +#: scormxblock.py:59 +msgid "Description" +msgstr "" + +#: scormxblock.py:60 +msgid "" +"Brief description of the SCORM modules will be displayed above the player. " +"Can contain HTML." +msgstr "" + +#: scormxblock.py:65 +msgid "Upload scorm file (.zip)" +msgstr "" + +#: scormxblock.py:66 +msgid "Upload a new SCORM package." +msgstr "" + +#: scormxblock.py:71 +msgid "SCORM player" +msgstr "" + +#: scormxblock.py:72 +msgid "" +"SCORM player configured in Django settings, or index.html file contained in " +"SCORM package" +msgstr "" + +#: scormxblock.py:98 +msgid "" +"SCORM block's problem weight in the course, in points. If not graded, set " +"to 0" +msgstr "" + +#: scormxblock.py:102 +msgid "Enable completion upon viewing SCORM file" +msgstr "" + +#: scormxblock.py:107 +msgid "Locking" +msgstr "" + +#: scormxblock.py:108 +msgid "" +"Enable requirement to complete SCORM content before moving to next module" +msgstr "" + +#: scormxblock.py:113 +msgid "Display Type" +msgstr "" + +#: scormxblock.py:116 +msgid "" +"Open in a new popup window, or an iframe. This setting may be overridden by " +"player-specific configuration." +msgstr "" + +#: scormxblock.py:120 +msgid "Popup Launch Type" +msgstr "" + +#: scormxblock.py:123 +msgid "Open in a new popup through button or automatically." +msgstr "" + +#: scormxblock.py:127 +msgid "Launch Button Text" +msgstr "" + +#: scormxblock.py:128 +msgid "Display text for Launch Button" +msgstr "" + +#: scormxblock.py:133 +msgid "Display Width (px)" +msgstr "" + +#: scormxblock.py:134 +msgid "Width of iframe or popup window" +msgstr "" + +#: scormxblock.py:139 +msgid "Display Height (px)" +msgstr "" + +#: scormxblock.py:140 +msgid "Height of iframe or popup window" +msgstr "" + +#: scormxblock.py:145 +msgid "SCORM Package text encoding" +msgstr "" + +#: scormxblock.py:147 +msgid "" +"Character set used in SCORM package. Defaults to cp850 (or IBM850), for " +"Latin-1: Western European languages)" +msgstr "" + +#: scormxblock.py:152 +msgid "Player Configuration" +msgstr "" + +#: scormxblock.py:154 +msgid "" +"JSON object string with overrides to be passed to selected SCORM player. " +"These will be exposed as data attributes on the host iframe and sent in a " +"window.postMessage to the iframe's content window. Attributes can be any. " +"'Internal player' will always check this field for an 'initial_html' " +"attribute to override index.html as the initial page." +msgstr "" + +#: scormxblock.py:158 +msgid "Scorm File Name" +msgstr "" + +#: scormxblock.py:159 +msgid "Scorm Package Uploaded File Name" +msgstr "" + +#: scormxblock.py:541 +msgid "Question" +msgstr "" + +#: scormxblock.py:542 +msgid "Answer" +msgstr "" + +#: scormxblock.py:543 +msgid "Submissions count" +msgstr "" diff --git a/scormxblock/translations/en/LC_MESSAGES/textjs.po b/scormxblock/translations/en/LC_MESSAGES/textjs.po new file mode 100644 index 0000000..044ad87 --- /dev/null +++ b/scormxblock/translations/en/LC_MESSAGES/textjs.po @@ -0,0 +1,30 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-09-01 05:30+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: static/js/src/scormxblock.js:81 +msgid "Content is complete, please continue." +msgstr "" + +#: static/js/src/scormxblock.js:82 +msgid "Complete all content to continue." +msgstr "" + +#: static/js/src/scormxblock.js:83 +msgid "Check for Completion" +msgstr "" diff --git a/setup.py b/setup.py index 804fab6..39fab7f 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ def package_data(pkg, roots): setup( name='scormxblock-xblock', - version='2.2', + version='2.3', description='XBlock to integrate SCORM content packages', packages=[ 'scormxblock',