From a6f349dab9f9b0694a3e8e98553e11a24756d0c1 Mon Sep 17 00:00:00 2001 From: David Baumgold Date: Fri, 12 Jul 2013 09:40:51 -0400 Subject: [PATCH 1/3] Studio i18n --- cms/djangoapps/contentstore/utils.py | 5 +- cms/djangoapps/contentstore/views/tabs.py | 6 +- cms/djangoapps/contentstore/views/user.py | 9 +- cms/static/client_templates/checklist.html | 10 +- .../course_info_handouts.html | 2 +- cms/static/coffee/files.json | 1 + cms/static/js/base.js | 38 ++-- .../js/models/settings/course_details.js | 12 +- .../models/settings/course_grading_policy.js | 12 +- cms/static/js/views/textbook.js | 10 +- cms/templates/404.html | 13 +- cms/templates/500.html | 13 +- cms/templates/activation_active.html | 7 +- cms/templates/activation_complete.html | 7 +- cms/templates/activation_invalid.html | 13 +- cms/templates/asset_index.html | 4 +- cms/templates/base.html | 3 +- cms/templates/checklists.html | 13 +- cms/templates/course_info.html | 13 +- cms/templates/edit-tabs.html | 21 +- cms/templates/edit_subsection.html | 77 ++++--- cms/templates/emails/activation_email.txt | 10 +- .../emails/activation_email_subject.txt | 3 +- cms/templates/error.html | 27 ++- cms/templates/export.html | 32 +-- cms/templates/howitworks.html | 94 +++++---- cms/templates/import.html | 28 +-- cms/templates/index.html | 10 +- cms/templates/login.html | 33 +-- cms/templates/manage_users.html | 13 +- cms/templates/overview.html | 79 +++---- .../registration/activation_complete.html | 13 +- cms/templates/registration/reg_complete.html | 4 +- cms/templates/settings.html | 2 +- cms/templates/settings_advanced.html | 31 +-- .../settings_discussions_faculty.html | 197 +++++++++--------- cms/templates/settings_graders.html | 39 ++-- cms/templates/signup.html | 61 +++--- cms/templates/static-pages.html | 13 +- cms/templates/unit.html | 54 ++--- cms/templates/widgets/header.html | 13 +- cms/templates/widgets/sock.html | 27 +-- cms/urls.py | 2 - common/djangoapps/mitxmako/middleware.py | 1 + common/djangoapps/student/views.py | 68 +++--- common/lib/xmodule/xmodule/course_module.py | 1 - common/lib/xmodule/xmodule/util/date_utils.py | 17 +- 47 files changed, 600 insertions(+), 561 deletions(-) diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index 452806fe64..5fa0d949b0 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -9,12 +9,13 @@ import copy import logging import re from xmodule.modulestore.draft import DIRECT_ONLY_CATEGORIES +from django.utils.translation import ugettext as _ log = logging.getLogger(__name__) # In order to instantiate an open ended tab automatically, need to have this data -OPEN_ENDED_PANEL = {"name": "Open Ended Panel", "type": "open_ended"} -NOTES_PANEL = {"name": "My Notes", "type": "notes"} +OPEN_ENDED_PANEL = {"name": _("Open Ended Panel"), "type": "open_ended"} +NOTES_PANEL = {"name": _("My Notes"), "type": "notes"} EXTRA_TAB_PANELS = dict([(p['type'], p) for p in [OPEN_ENDED_PANEL, NOTES_PANEL]]) diff --git a/cms/djangoapps/contentstore/views/tabs.py b/cms/djangoapps/contentstore/views/tabs.py index a7b232e92a..154f9fb55d 100644 --- a/cms/djangoapps/contentstore/views/tabs.py +++ b/cms/djangoapps/contentstore/views/tabs.py @@ -13,7 +13,7 @@ from xmodule.modulestore.django import modulestore from ..utils import get_course_for_item, get_modulestore from .access import get_location_and_verify_access -__all__ = ['edit_tabs', 'reorder_static_tabs', 'static_pages', 'edit_static'] +__all__ = ['edit_tabs', 'reorder_static_tabs', 'static_pages'] def initialize_course_tabs(course): @@ -127,7 +127,3 @@ def static_pages(request, org, course, coursename): return render_to_response('static-pages.html', { 'context_course': course, }) - - -def edit_static(request, org, course, coursename): - return render_to_response('edit-static-page.html', {}) diff --git a/cms/djangoapps/contentstore/views/user.py b/cms/djangoapps/contentstore/views/user.py index dae0d246a5..948ed614d2 100644 --- a/cms/djangoapps/contentstore/views/user.py +++ b/cms/djangoapps/contentstore/views/user.py @@ -2,6 +2,7 @@ from django.conf import settings from django.core.exceptions import PermissionDenied from django.core.urlresolvers import reverse from django.contrib.auth.decorators import login_required +from django.utils.translation import ugettext as _ from django_future.csrf import ensure_csrf_cookie from mitxmako.shortcuts import render_to_response @@ -78,7 +79,7 @@ def add_user(request, location): if not email: msg = { 'Status': 'Failed', - 'ErrMsg': 'Please specify an email address.', + 'ErrMsg': _('Please specify an email address.'), } return JsonResponse(msg, 400) @@ -92,7 +93,7 @@ def add_user(request, location): if user is None: msg = { 'Status': 'Failed', - 'ErrMsg': "Could not find user by email address '{0}'.".format(email), + 'ErrMsg': _("Could not find user by email address '{email}'.").format(email=email), } return JsonResponse(msg, 404) @@ -100,7 +101,7 @@ def add_user(request, location): if not user.is_active: msg = { 'Status': 'Failed', - 'ErrMsg': 'User {0} has registered but has not yet activated his/her account.'.format(email), + 'ErrMsg': _('User {email} has registered but has not yet activated his/her account.').format(email=email), } return JsonResponse(msg, 400) @@ -129,7 +130,7 @@ def remove_user(request, location): if user is None: msg = { 'Status': 'Failed', - 'ErrMsg': "Could not find user by email address '{0}'.".format(email), + 'ErrMsg': _("Could not find user by email address '{email}'.").format(email=email), } return JsonResponse(msg, 404) diff --git a/cms/static/client_templates/checklist.html b/cms/static/client_templates/checklist.html index e985ab9509..5d36264d07 100644 --- a/cms/static/client_templates/checklist.html +++ b/cms/static/client_templates/checklist.html @@ -6,15 +6,15 @@ class="course-checklist" <% } %> id="<%= 'course-checklist' + checklistIndex %>"> - <% var widthPercentage = 'width:' + percentChecked + '%;'; %> - - <%= percentChecked %>% of checklist completed + + <%= _.template(gettext("{number}% of checklists completed"), {number: '' + percentChecked + ''}, {interpolate: /\{(.+?)\}/g}) %> +

<%= checklistShortDescription %>

- Tasks Completed: <%= itemsChecked %>/<%= items.length %> + <%= gettext("Tasks Completed:") %> <%= itemsChecked %>/<%= items.length %>
@@ -47,7 +47,7 @@
  • - rel="external" title="This link will open in a new browser window/tab" + rel="external" title="<%= gettext("This link will open in a new browser window/tab") %>" <% } %> ><%= item['action_text'] %>
  • diff --git a/cms/static/client_templates/course_info_handouts.html b/cms/static/client_templates/course_info_handouts.html index 958a1c77d6..cf9fdb5c85 100644 --- a/cms/static/client_templates/course_info_handouts.html +++ b/cms/static/client_templates/course_info_handouts.html @@ -6,7 +6,7 @@ <%= model.get('data') %> <% } else {%> -

    You have no handouts defined

    +

    ${_("You have no handouts defined")}

    <% } %>
    diff --git a/cms/static/coffee/files.json b/cms/static/coffee/files.json index 3c27629f69..3964bee455 100644 --- a/cms/static/coffee/files.json +++ b/cms/static/coffee/files.json @@ -1,5 +1,6 @@ { "static_files": [ + "../jsi18n/", "js/vendor/RequireJS.js", "js/vendor/jquery.min.js", "js/vendor/jquery-ui.min.js", diff --git a/cms/static/js/base.js b/cms/static/js/base.js index d597c2af27..f74a6cb5e4 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -79,10 +79,10 @@ $(document).ready(function() { }); // general link management - new window/tab - $('a[rel="external"]').attr('title', 'This link will open in a new browser window/tab').bind('click', linkNewWindow); + $('a[rel="external"]').attr('title', gettext('This link will open in a new browser window/tab')).bind('click', linkNewWindow); // general link management - lean modal window - $('a[rel="modal"]').attr('title', 'This link will open in a modal window').leanModal({ + $('a[rel="modal"]').attr('title', gettext('This link will open in a modal window')).leanModal({ overlay: 0.50, closeButton: '.action-modal-close' }); @@ -199,8 +199,10 @@ function toggleSections(e) { $section = $('.courseware-section'); sectionCount = $section.length; $button = $(this); - $labelCollapsed = $(' Collapse All Sections'); - $labelExpanded = $(' Expand All Sections'); + $labelCollapsed = $(' ' + + gettext('Collapse All Sections') + ''); + $labelExpanded = $(' ' + + gettext('Expand All Sections') + ''); var buttonLabel = $button.hasClass('is-activated') ? $labelCollapsed : $labelExpanded; $button.toggleClass('is-activated').html(buttonLabel); @@ -326,7 +328,7 @@ function saveSubsection() { $changedInput = null; }, error: function() { - showToastMessage('There has been an error while saving your changes.'); + showToastMessage(gettext('There has been an error while saving your changes.')); } }); } @@ -372,7 +374,7 @@ function deleteSection(e) { } function _deleteItem($el) { - if (!confirm('Are you sure you wish to delete this item. It cannot be reversed!')) return; + if (!confirm(gettext('Are you sure you wish to delete this item. It cannot be reversed!'))) return; var id = $el.data('id'); @@ -599,7 +601,7 @@ function saveNewCourse(e) { var display_name = $newCourse.find('.new-course-name').val(); if (org == '' || number == '' || display_name == '') { - alert('You must specify all fields in order to create a new course.'); + alert(gettext('You must specify all fields in order to create a new course.')); return; } @@ -730,18 +732,16 @@ function saveSetSectionScheduleDate(e) { }) }).success(function() { var $thisSection = $('.courseware-section[data-id="' + id + '"]'); - var format = gettext('Will Release: %(date)s at %(time)s UTC'); - var willReleaseAt = interpolate(format, { - 'date': input_date, - 'time': input_time - }, - true); - $thisSection.find('.section-published-date').html( - '' + willReleaseAt + '' + - '' + gettext('Edit') + ''); + var html = _.template( + '' + + '' + gettext("Will Release:") + '' + + gettext("<%= date %> at <%= time %> UTC") + + '' + + '' + + gettext("Edit") + + '', + {date: input_date, time: input_time, id: id}); + $thisSection.find('.section-published-date').html(html); hideModal(); saving.hide(); }); diff --git a/cms/static/js/models/settings/course_details.js b/cms/static/js/models/settings/course_details.js index 993832f830..d7e11d5689 100644 --- a/cms/static/js/models/settings/course_details.js +++ b/cms/static/js/models/settings/course_details.js @@ -38,23 +38,23 @@ CMS.Models.Settings.CourseDetails = Backbone.Model.extend({ // A bit funny in that the video key validation is asynchronous; so, it won't stop the validation. var errors = {}; if (newattrs.start_date === null) { - errors.start_date = "The course must have an assigned start date."; + errors.start_date = gettext("The course must have an assigned start date."); } if (newattrs.start_date && newattrs.end_date && newattrs.start_date >= newattrs.end_date) { - errors.end_date = "The course end date cannot be before the course start date."; + errors.end_date = gettext("The course end date cannot be before the course start date."); } if (newattrs.start_date && newattrs.enrollment_start && newattrs.start_date < newattrs.enrollment_start) { - errors.enrollment_start = "The course start date cannot be before the enrollment start date."; + errors.enrollment_start = gettext("The course start date cannot be before the enrollment start date."); } if (newattrs.enrollment_start && newattrs.enrollment_end && newattrs.enrollment_start >= newattrs.enrollment_end) { - errors.enrollment_end = "The enrollment start date cannot be after the enrollment end date."; + errors.enrollment_end = gettext("The enrollment start date cannot be after the enrollment end date."); } if (newattrs.end_date && newattrs.enrollment_end && newattrs.end_date < newattrs.enrollment_end) { - errors.enrollment_end = "The enrollment end date cannot be after the course end date."; + errors.enrollment_end = gettext("The enrollment end date cannot be after the course end date."); } if (newattrs.intro_video && newattrs.intro_video !== this.get('intro_video')) { if (this._videokey_illegal_chars.exec(newattrs.intro_video)) { - errors.intro_video = "Key should only contain letters, numbers, _, or -"; + errors.intro_video = gettext("Key should only contain letters, numbers, _, or -"); } // TODO check if key points to a real video using google's youtube api } diff --git a/cms/static/js/models/settings/course_grading_policy.js b/cms/static/js/models/settings/course_grading_policy.js index 3014b39e82..04ae3f4c32 100644 --- a/cms/static/js/models/settings/course_grading_policy.js +++ b/cms/static/js/models/settings/course_grading_policy.js @@ -79,14 +79,14 @@ CMS.Models.Settings.CourseGrader = Backbone.Model.extend({ // FIXME somehow this.collection is unbound sometimes. I can't track down when var existing = this.collection && this.collection.some(function(other) { return (other.cid != this.cid) && (other.get('type') == attrs['type']);}, this); if (existing) { - errors.type = "There's already another assignment type with this name."; + errors.type = gettext("There's already another assignment type with this name."); } } } if (_.has(attrs, 'weight')) { var intWeight = parseInt(attrs.weight); // see if this ensures value saved is int if (!isFinite(intWeight) || /\D+/.test(attrs.weight) || intWeight < 0 || intWeight > 100) { - errors.weight = "Please enter an integer between 0 and 100."; + errors.weight = gettext("Please enter an integer between 0 and 100."); } else { attrs.weight = intWeight; @@ -100,18 +100,20 @@ CMS.Models.Settings.CourseGrader = Backbone.Model.extend({ }} if (_.has(attrs, 'min_count')) { if (!isFinite(attrs.min_count) || /\D+/.test(attrs.min_count)) { - errors.min_count = "Please enter an integer."; + errors.min_count = gettext("Please enter an integer."); } else attrs.min_count = parseInt(attrs.min_count); } if (_.has(attrs, 'drop_count')) { if (!isFinite(attrs.drop_count) || /\D+/.test(attrs.drop_count)) { - errors.drop_count = "Please enter an integer."; + errors.drop_count = gettext("Please enter an integer."); } else attrs.drop_count = parseInt(attrs.drop_count); } if (_.has(attrs, 'min_count') && _.has(attrs, 'drop_count') && attrs.drop_count > attrs.min_count) { - errors.drop_count = "Cannot drop more " + attrs.type + " than will assigned."; + errors.drop_count = _.template( + gettext("Cannot drop more <% attrs.types %> than will assigned."), + attrs, {variable: 'attrs'}); } if (!_.isEmpty(errors)) return errors; } diff --git a/cms/static/js/views/textbook.js b/cms/static/js/views/textbook.js index fe12082c7a..74eaae8601 100644 --- a/cms/static/js/views/textbook.js +++ b/cms/static/js/views/textbook.js @@ -26,8 +26,8 @@ CMS.Views.ShowTextbook = Backbone.View.extend({ if(e && e.preventDefault) { e.preventDefault(); } var textbook = this.model, collection = this.model.collection; var msg = new CMS.Views.Prompt.Warning({ - title: _.str.sprintf(gettext("Delete “%s”?"), - textbook.escape('name')), + title: _.template(gettext("Delete “<%= name %>”?"), + {name: textbook.escape('name')}), message: gettext("Deleting a textbook cannot be undone and once deleted any reference to it in your courseware's navigation will also be removed."), actions: { primary: { @@ -241,8 +241,8 @@ CMS.Views.EditChapter = Backbone.View.extend({ asset_path: this.$("input.chapter-asset-path").val() }); var msg = new CMS.Models.FileUpload({ - title: _.str.sprintf(gettext("Upload a new asset to %s"), - section.escape('name')), + title: _.template(gettext("Upload a new asset to “<%= name %>”"), + {name: section.escape('name')}), message: "Files must be in PDF format." }); var view = new CMS.Views.UploadDialog({model: msg, chapter: this.model}); @@ -260,7 +260,7 @@ CMS.Views.UploadDialog = Backbone.View.extend({ this.listenTo(this.model, "change", this.render); }, render: function() { - var isValid = this.model.isValid() + var isValid = this.model.isValid(); var selectedFile = this.model.get('selectedFile'); var oldInput = this.$("input[type=file]").get(0); this.$el.html(this.template({ diff --git a/cms/templates/404.html b/cms/templates/404.html index a45a223bad..be7a66a31c 100644 --- a/cms/templates/404.html +++ b/cms/templates/404.html @@ -1,14 +1,19 @@ +<%! from django.utils.translation import ugettext as _ %> <%inherit file="base.html" /> -<%block name="title">Page Not Found +<%block name="title">${_("Page Not Found")} <%block name="content">
    -

    Page not found

    -

    The page that you were looking for was not found. Go back to the homepage or let us know about any pages that may have been moved at technical@edx.org.

    +

    ${_("Page not found")}

    +

    ${_('The page that you were looking for was not found.')} + ${_('Go back to the {homepage} or let us know about any pages that may have been moved at {email}.').format( + homepage='homepage', + email='technical@edx.org')} +

    - \ No newline at end of file + diff --git a/cms/templates/500.html b/cms/templates/500.html index 3d18d9dcc5..5d79dd7a16 100644 --- a/cms/templates/500.html +++ b/cms/templates/500.html @@ -1,18 +1,19 @@ +<%! from django.utils.translation import ugettext as _ %> <%inherit file="base.html" /> -<%block name="title">Studio Server Error +<%block name="title">${_("Studio Server Error")} <%block name="content">
    -

    The Studio servers encountered an error

    +

    ${_("The Studio servers encountered an error")}

    - An error occurred in Studio and the page could not be loaded. Please try again in a few moments. - We've logged the error and our staff is currently working to resolve this error as soon as possible. - If the problem persists, please email us at technical@edx.org. + ${_("An error occurred in Studio and the page could not be loaded. Please try again in a few moments.")} + ${_("We've logged the error and our staff is currently working to resolve this error as soon as possible.")} + ${_('If the problem persists, please email us at {email}.').format(email='technical@edx.org')}

    - \ No newline at end of file + diff --git a/cms/templates/activation_active.html b/cms/templates/activation_active.html index 712c73abf9..9a4ebd7e4e 100644 --- a/cms/templates/activation_active.html +++ b/cms/templates/activation_active.html @@ -1,3 +1,4 @@ +<%! from django.utils.translation import ugettext as _ %> <%inherit file="base.html" /> <%block name="content"> @@ -6,9 +7,9 @@
    -

    Account already active!

    -

    This account has already been activated. Log in here.

    +

    ${_("Account already active!")}

    +

    ${_('This account has already been activated.')}${_("Log in here.")}

    - \ No newline at end of file + diff --git a/cms/templates/activation_complete.html b/cms/templates/activation_complete.html index 1e195a632c..d845c5153b 100644 --- a/cms/templates/activation_complete.html +++ b/cms/templates/activation_complete.html @@ -1,12 +1,13 @@ +<%! from django.utils.translation import ugettext as _ %> <%inherit file="base.html" /> <%block name="content">
    -

    Activation Complete!

    -

    Thanks for activating your account. Log in here.

    +

    ${_("Activation Complete!")}

    +

    ${_('Thanks for activating your account.')}${_("Log in here.")}

    - \ No newline at end of file + diff --git a/cms/templates/activation_invalid.html b/cms/templates/activation_invalid.html index c4eb16875b..3ee4e8ec4e 100644 --- a/cms/templates/activation_invalid.html +++ b/cms/templates/activation_invalid.html @@ -1,16 +1,15 @@ +<%! from django.utils.translation import ugettext as _ %> <%inherit file="base.html" /> <%block name="content">
    -

    Activation Invalid

    +

    ${_("Activation Invalid")}

    -

    Something went wrong. Check to make sure the URL you went to was - correct -- e-mail programs will sometimes split it into two - lines. If you still have issues, e-mail us to let us know what happened - at bugs@mitx.mit.edu.

    +

    ${_('Something went wrong. Check to make sure the URL you went to was correct -- e-mail programs will sometimes split it into two lines. If you still have issues, e-mail us to let us know what happened at {email}.').format(email='bugs@mitx.mit.edu')}

    -

    Or you can go back to the home page.

    +

    ${_('Or you can go back to the {link_start}home page{link_end}.').format( + link_start='', link_end='')}

    - \ No newline at end of file + diff --git a/cms/templates/asset_index.html b/cms/templates/asset_index.html index bdad7b7b88..6c92994a6f 100644 --- a/cms/templates/asset_index.html +++ b/cms/templates/asset_index.html @@ -2,7 +2,7 @@ <%! from django.core.urlresolvers import reverse %> <%! from django.utils.translation import ugettext as _ %> <%block name="bodyclass">is-signedin course uploads -<%block name="title">Files & Uploads +<%block name="title">${_("Files & Uploads")} <%namespace name='static' file='static_content.html'/> @@ -48,7 +48,7 @@

    Page Actions

    diff --git a/cms/templates/base.html b/cms/templates/base.html index e58dcdfc60..44ebf59170 100644 --- a/cms/templates/base.html +++ b/cms/templates/base.html @@ -1,3 +1,4 @@ +## -*- coding: utf-8 -*- <%namespace name='static' file='static_content.html'/> @@ -17,6 +18,7 @@ + <%static:css group='base-style'/> @@ -35,7 +37,6 @@ ## javascript - diff --git a/cms/templates/checklists.html b/cms/templates/checklists.html index 6f78e952c0..ad4f29aeb6 100644 --- a/cms/templates/checklists.html +++ b/cms/templates/checklists.html @@ -1,3 +1,4 @@ +<%! from django.utils.translation import ugettext as _ %> <%inherit file="base.html" /> <%! from django.core.urlresolvers import reverse %> <%block name="title">Course Checklists @@ -30,8 +31,8 @@

    - Tools - > Course Checklists + ${_("Tools")} + > ${_("Course Checklists")}

    @@ -40,18 +41,18 @@
    -

    Current Checklists

    +

    ${_("Current Checklists")}