Commit b8eba419 by polesye

BLD-699: Fix i18n.

parent 4b56fc29
...@@ -11,6 +11,7 @@ from lxml import etree ...@@ -11,6 +11,7 @@ from lxml import etree
from cache_toolbox.core import del_cached_content from cache_toolbox.core import del_cached_content
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext as _
from xmodule.exceptions import NotFoundError from xmodule.exceptions import NotFoundError
from xmodule.contentstore.content import StaticContent from xmodule.contentstore.content import StaticContent
...@@ -103,8 +104,10 @@ def get_transcripts_from_youtube(youtube_id): ...@@ -103,8 +104,10 @@ def get_transcripts_from_youtube(youtube_id):
data = requests.get(youtube_api['url'], params=youtube_api['params']) data = requests.get(youtube_api['url'], params=youtube_api['params'])
if data.status_code != 200 or not data.text: if data.status_code != 200 or not data.text:
msg = "Can't receive transcripts from Youtube for {}. Status code: {}.".format( msg = _("Can't receive transcripts from Youtube for {youtube_id}. Status code: {statuc_code}.").format(
youtube_id, data.status_code) youtube_id=youtube_id,
statuc_code=data.status_code
)
raise GetTranscriptsFromYouTubeException(msg) raise GetTranscriptsFromYouTubeException(msg)
sub_starts, sub_ends, sub_texts = [], [], [] sub_starts, sub_ends, sub_texts = [], [], []
...@@ -162,7 +165,7 @@ def download_youtube_subs(youtube_subs, item): ...@@ -162,7 +165,7 @@ def download_youtube_subs(youtube_subs, item):
highest_speed_subs = subs highest_speed_subs = subs
if not highest_speed: if not highest_speed:
raise GetTranscriptsFromYouTubeException("Can't find any transcripts on the Youtube service.") raise GetTranscriptsFromYouTubeException(_("Can't find any transcripts on the Youtube service."))
# When we exit from the previous loop, `highest_speed` and `highest_speed_subs` # When we exit from the previous loop, `highest_speed` and `highest_speed_subs`
# are the transcripts data for the highest speed available on the # are the transcripts data for the highest speed available on the
...@@ -214,16 +217,16 @@ def generate_subs_from_source(speed_subs, subs_type, subs_filedata, item): ...@@ -214,16 +217,16 @@ def generate_subs_from_source(speed_subs, subs_type, subs_filedata, item):
:returns: True, if all subs are generated and saved successfully. :returns: True, if all subs are generated and saved successfully.
""" """
if subs_type != 'srt': if subs_type != 'srt':
raise TranscriptsGenerationException("We support only SubRip (*.srt) transcripts format.") raise TranscriptsGenerationException(_("We support only SubRip (*.srt) transcripts format."))
try: try:
srt_subs_obj = SubRipFile.from_string(subs_filedata) srt_subs_obj = SubRipFile.from_string(subs_filedata)
except Exception as e: except Exception as e:
raise TranscriptsGenerationException( msg = _("Something wrong with SubRip transcripts file during parsing. Inner message is {error_message}").format(
"Something wrong with SubRip transcripts file during parsing. " error_message=e.message
"Inner message is {}".format(e.message)
) )
raise TranscriptsGenerationException(msg)
if not srt_subs_obj: if not srt_subs_obj:
raise TranscriptsGenerationException("Something wrong with SubRip transcripts file during parsing.") raise TranscriptsGenerationException(_("Something wrong with SubRip transcripts file during parsing."))
sub_starts = [] sub_starts = []
sub_ends = [] sub_ends = []
......
...@@ -15,6 +15,7 @@ from django.http import HttpResponse, Http404 ...@@ -15,6 +15,7 @@ from django.http import HttpResponse, Http404
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext as _
from xmodule.contentstore.content import StaticContent from xmodule.contentstore.content import StaticContent
from xmodule.exceptions import NotFoundError from xmodule.exceptions import NotFoundError
...@@ -439,15 +440,15 @@ def _validate_transcripts_data(request): ...@@ -439,15 +440,15 @@ def _validate_transcripts_data(request):
""" """
data = json.loads(request.GET.get('data', '{}')) data = json.loads(request.GET.get('data', '{}'))
if not data: if not data:
raise TranscriptsRequestValidationException('Incoming video data is empty.') raise TranscriptsRequestValidationException(_('Incoming video data is empty.'))
try: try:
item = _get_item(request, data) item = _get_item(request, data)
except (ItemNotFoundError, InvalidLocationError, InsufficientSpecificationError): except (ItemNotFoundError, InvalidLocationError, InsufficientSpecificationError):
raise TranscriptsRequestValidationException("Can't find item by locator.") raise TranscriptsRequestValidationException(_("Can't find item by locator."))
if item.category != 'video': if item.category != 'video':
raise TranscriptsRequestValidationException('Transcripts are supported only for "video" modules.') raise TranscriptsRequestValidationException(_('Transcripts are supported only for "video" modules.'))
# parse data form request.GET.['data']['video'] to useful format # parse data form request.GET.['data']['video'] to useful format
videos = {'youtube': '', 'html5': {}} videos = {'youtube': '', 'html5': {}}
......
...@@ -33,6 +33,7 @@ src_paths: ...@@ -33,6 +33,7 @@ src_paths:
# Paths to library JavaScript files (optional) # Paths to library JavaScript files (optional)
lib_paths: lib_paths:
- common_static/js/test/i18n.js
- common_static/coffee/src/ajax_prefix.js - common_static/coffee/src/ajax_prefix.js
- common_static/coffee/src/logger.js - common_static/coffee/src/logger.js
- common_static/js/vendor/jasmine-jquery.js - common_static/js/vendor/jasmine-jquery.js
......
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
expect(timeControl).toHaveAttrs({ expect(timeControl).toHaveAttrs({
'role': 'slider', 'role': 'slider',
'title': 'video position', 'title': 'Video position',
'aria-disabled': 'false' 'aria-disabled': 'false'
}); });
......
...@@ -241,7 +241,7 @@ ...@@ -241,7 +241,7 @@
state.videoProgressSlider.notifyThroughHandleEnd({end: true}); state.videoProgressSlider.notifyThroughHandleEnd({end: true});
expect(state.videoProgressSlider.handle.attr('title')) expect(state.videoProgressSlider.handle.attr('title'))
.toBe('video ended'); .toBe('Video ended');
expect('focus').toHaveBeenTriggeredOn( expect('focus').toHaveBeenTriggeredOn(
state.videoProgressSlider.handle state.videoProgressSlider.handle
...@@ -252,7 +252,7 @@ ...@@ -252,7 +252,7 @@
state.videoProgressSlider.notifyThroughHandleEnd({end: false}); state.videoProgressSlider.notifyThroughHandleEnd({end: false});
expect(state.videoProgressSlider.handle.attr('title')) expect(state.videoProgressSlider.handle.attr('title'))
.toBe('video position'); .toBe('Video position');
expect('focus').not.toHaveBeenTriggeredOn( expect('focus').not.toHaveBeenTriggeredOn(
state.videoProgressSlider.handle state.videoProgressSlider.handle
...@@ -272,6 +272,30 @@ ...@@ -272,6 +272,30 @@
}); });
}); });
}); });
it('getTimeDescription', function () {
var cases = {
'0': '0 seconds',
'1': '1 second',
'10': '10 seconds',
'60': '1 minute 0 seconds',
'121': '2 minutes 1 second',
'3670': '1 hour 1 minute 10 seconds',
'21541': '5 hours 59 minutes 1 second',
},
getTimeDescription;
state = jasmine.initializePlayer();
getTimeDescription = state.videoProgressSlider.getTimeDescription;
$.each(cases, function(input, output) {
expect(getTimeDescription(input)).toBe(output);
});
});
}); });
}).call(this); }).call(this);
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
oldOTBD = window.onTouchBasedDevice; oldOTBD = window.onTouchBasedDevice;
window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice') window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice')
.andReturn(null); .andReturn(null);
});
afterEach(function () { afterEach(function () {
$('source').remove(); $('source').remove();
...@@ -15,14 +16,12 @@ ...@@ -15,14 +16,12 @@
describe('constructor', function () { describe('constructor', function () {
beforeEach(function () { beforeEach(function () {
spyOn($.fn, 'slider').andCallThrough(); spyOn($.fn, 'slider').andCallThrough();
$.cookie.andReturn('75');
state = jasmine.initializePlayer(); state = jasmine.initializePlayer();
}); });
it('initialize currentVolume to 100%', function () { it('initialize currentVolume to 75%', function () {
// Please note that: expect(state.videoVolumeControl.currentVolume).toEqual(75);
// 0% -> 0
// 100% -> 1.0
expect(state.videoVolumeControl.currentVolume).toEqual(1);
}); });
it('render the volume control', function () { it('render the volume control', function () {
...@@ -45,13 +44,13 @@ ...@@ -45,13 +44,13 @@
it('add ARIA attributes to slider handle', function () { it('add ARIA attributes to slider handle', function () {
var sliderHandle = $('div.volume-slider>a.ui-slider-handle'), var sliderHandle = $('div.volume-slider>a.ui-slider-handle'),
arr = [ arr = [
'muted', 'very low', 'low', 'average', 'loud', 'Muted', 'Very low', 'Low', 'Average', 'Loud',
'very loud', 'maximum' 'Very loud', 'Maximum'
]; ];
expect(sliderHandle).toHaveAttrs({ expect(sliderHandle).toHaveAttrs({
'role': 'slider', 'role': 'slider',
'title': 'volume', 'title': 'Volume',
'aria-disabled': 'false', 'aria-disabled': 'false',
'aria-valuemin': '0', 'aria-valuemin': '0',
'aria-valuemax': '100' 'aria-valuemax': '100'
...@@ -86,33 +85,33 @@ ...@@ -86,33 +85,33 @@
describe('onChange', function () { describe('onChange', function () {
var initialData = [{ var initialData = [{
range: 'muted', range: 'Muted',
value: 0, value: 0,
expectation: 'muted' expectation: 'Muted'
}, { }, {
range: 'in ]0,20]', range: 'in ]0,20]',
value: 10, value: 10,
expectation: 'very low' expectation: 'Very low'
}, { }, {
range: 'in ]20,40]', range: 'in ]20,40]',
value: 30, value: 30,
expectation: 'low' expectation: 'Low'
}, { }, {
range: 'in ]40,60]', range: 'in ]40,60]',
value: 50, value: 50,
expectation: 'average' expectation: 'Average'
}, { }, {
range: 'in ]60,80]', range: 'in ]60,80]',
value: 70, value: 70,
expectation: 'loud' expectation: 'Loud'
}, { }, {
range: 'in ]80,100[', range: 'in ]80,100[',
value: 90, value: 90,
expectation: 'very loud' expectation: 'Very loud'
}, { }, {
range: 'maximum', range: 'Maximum',
value: 100, value: 100,
expectation: 'maximum' expectation: 'Maximum'
}]; }];
beforeEach(function () { beforeEach(function () {
......
...@@ -16,19 +16,6 @@ define( ...@@ -16,19 +16,6 @@ define(
'video/01_initialize.js', 'video/01_initialize.js',
['video/03_video_player.js', 'video/00_cookie_storage.js'], ['video/03_video_player.js', 'video/00_cookie_storage.js'],
function (VideoPlayer, CookieStorage) { function (VideoPlayer, CookieStorage) {
// window.console.log() is expected to be available. We do not support
// browsers which lack this functionality.
// The function gettext() is defined by a vendor library. If, however, it
// is undefined, it is a simple wrapper. It is used to return a different
// version of the string passed (translated string, etc.). In the basic
// case, the original string is returned.
if (typeof(window.gettext) == 'undefined') {
window.gettext = function (s) {
return s;
};
}
/** /**
* @function * @function
* *
......
...@@ -81,7 +81,7 @@ function () { ...@@ -81,7 +81,7 @@ function () {
// handle, behaves as a slider named 'video slider'. // handle, behaves as a slider named 'video slider'.
state.videoControl.sliderEl.find('.ui-slider-handle').attr({ state.videoControl.sliderEl.find('.ui-slider-handle').attr({
'role': 'slider', 'role': 'slider',
'title': gettext('video slider') 'title': gettext('Video slider')
}); });
} }
......
...@@ -42,7 +42,8 @@ function () { ...@@ -42,7 +42,8 @@ function () {
onStop: onStop, onStop: onStop,
updatePlayTime: updatePlayTime, updatePlayTime: updatePlayTime,
updateStartEndTimeRegion: updateStartEndTimeRegion, updateStartEndTimeRegion: updateStartEndTimeRegion,
notifyThroughHandleEnd: notifyThroughHandleEnd notifyThroughHandleEnd: notifyThroughHandleEnd,
getTimeDescription: getTimeDescription
}; };
state.bindTo(methodsDict, state.videoProgressSlider, state); state.bindTo(methodsDict, state.videoProgressSlider, state);
...@@ -72,7 +73,7 @@ function () { ...@@ -72,7 +73,7 @@ function () {
// handle, behaves as a slider named 'video position'. // handle, behaves as a slider named 'video position'.
state.videoProgressSlider.handle.attr({ state.videoProgressSlider.handle.attr({
'role': 'slider', 'role': 'slider',
'title': 'video position', 'title': gettext('Video position'),
'aria-disabled': false, 'aria-disabled': false,
'aria-valuetext': getTimeDescription(state.videoProgressSlider 'aria-valuetext': getTimeDescription(state.videoProgressSlider
.slider.slider('option', 'value')) .slider.slider('option', 'value'))
...@@ -152,11 +153,15 @@ function () { ...@@ -152,11 +153,15 @@ function () {
if (!this.videoProgressSlider.sliderRange) { if (!this.videoProgressSlider.sliderRange) {
this.videoProgressSlider.sliderRange = $('<div />', { this.videoProgressSlider.sliderRange = $('<div />', {
class: 'ui-slider-range ' + 'class': 'ui-slider-range ' +
'ui-widget-header ' + 'ui-widget-header ' +
'ui-corner-all ' + 'ui-corner-all ' +
'slider-range' 'slider-range'
}).css(rangeParams); })
.css({
left: rangeParams.left,
width: rangeParams.width
});
this.videoProgressSlider.sliderProgress this.videoProgressSlider.sliderProgress
.after(this.videoProgressSlider.sliderRange); .after(this.videoProgressSlider.sliderRange);
...@@ -245,61 +250,50 @@ function () { ...@@ -245,61 +250,50 @@ function () {
function notifyThroughHandleEnd(params) { function notifyThroughHandleEnd(params) {
if (params.end) { if (params.end) {
this.videoProgressSlider.handle this.videoProgressSlider.handle
.attr('title', 'video ended') .attr('title', gettext('Video ended'))
.focus(); .focus();
} else { } else {
this.videoProgressSlider.handle.attr('title', 'video position'); this.videoProgressSlider.handle
.attr('title', gettext('Video position'));
} }
} }
// Returns a string describing the current time of video in hh:mm:ss // Returns a string describing the current time of video in
// format. // `%d hours %d minutes %d seconds` format.
function getTimeDescription(time) { function getTimeDescription(time) {
var seconds = Math.floor(time), var seconds = Math.floor(time),
minutes = Math.floor(seconds / 60), minutes = Math.floor(seconds / 60),
hours = Math.floor(minutes / 60), hours = Math.floor(minutes / 60),
hrStr, minStr, secStr; i18n = function (value, word) {
var msg;
switch(word) {
case 'hour':
msg = ngettext('%(value)s hour', '%(value)s hours', value);
break;
case 'minute':
msg = ngettext('%(value)s minute', '%(value)s minutes', value);
break;
case 'second':
msg = ngettext('%(value)s second', '%(value)s seconds', value);
break;
}
return interpolate(msg, {'value': value}, true);
};
seconds = seconds % 60; seconds = seconds % 60;
minutes = minutes % 60; minutes = minutes % 60;
hrStr = hours.toString(10);
minStr = minutes.toString(10);
secStr = seconds.toString(10);
if (hours) { if (hours) {
hrStr += (hours < 2 ? ' hour ' : ' hours '); return i18n(hours, 'hour') + ' ' +
i18n(minutes, 'minute') + ' ' +
if (minutes) { i18n(seconds, 'second');
minStr += (minutes < 2 ? ' minute ' : ' minutes ');
} else {
minStr += ' 0 minutes ';
}
if (seconds) {
secStr += (seconds < 2 ? ' second ' : ' seconds ');
} else {
secStr += ' 0 seconds ';
}
return hrStr + minStr + secStr;
} else if (minutes) { } else if (minutes) {
minStr += (minutes < 2 ? ' minute ' : ' minutes '); return i18n(minutes, 'minute') + ' ' +
i18n(seconds, 'second');
if (seconds) {
secStr += (seconds < 2 ? ' second ' : ' seconds ');
} else {
secStr += ' 0 seconds ';
}
return minStr + secStr;
} else if (seconds) {
secStr += (seconds < 2 ? ' second ' : ' seconds ');
return secStr;
} }
return '0 seconds'; return i18n(seconds, 'second');
} }
}); });
......
...@@ -94,7 +94,7 @@ function () { ...@@ -94,7 +94,7 @@ function () {
volumeSliderHandleEl.attr({ volumeSliderHandleEl.attr({
'role': 'slider', 'role': 'slider',
'title': 'volume', 'title': gettext('Volume'),
'aria-disabled': false, 'aria-disabled': false,
'aria-valuemin': slider.slider('option', 'min'), 'aria-valuemin': slider.slider('option', 'min'),
'aria-valuemax': slider.slider('option', 'max'), 'aria-valuemax': slider.slider('option', 'max'),
...@@ -238,20 +238,27 @@ function () { ...@@ -238,20 +238,27 @@ function () {
// Returns a string describing the level of volume. // Returns a string describing the level of volume.
function getVolumeDescription(vol) { function getVolumeDescription(vol) {
if (vol === 0) { if (vol === 0) {
return 'muted'; // Translators: Volume level equals 0%.
return gettext('Muted');
} else if (vol <= 20) { } else if (vol <= 20) {
return 'very low'; // Translators: Volume level in range (0,20]%
return gettext('Very low');
} else if (vol <= 40) { } else if (vol <= 40) {
return 'low'; // Translators: Volume level in range (20,40]%
return gettext('Low');
} else if (vol <= 60) { } else if (vol <= 60) {
return 'average'; // Translators: Volume level in range (40,60]%
return gettext('Average');
} else if (vol <= 80) { } else if (vol <= 80) {
return 'loud'; // Translators: Volume level in range (60,80]%
return gettext('Loud');
} else if (vol <= 99) { } else if (vol <= 99) {
return 'very loud'; // Translators: Volume level in range (80,100)%
return gettext('Very loud');
} }
return 'maximum'; // Translators: Volume level equals 100%.
return gettext('Maximum');
} }
}); });
......
...@@ -28,6 +28,7 @@ prepend_path: lms/static ...@@ -28,6 +28,7 @@ prepend_path: lms/static
# Paths to library JavaScript files (optional) # Paths to library JavaScript files (optional)
lib_paths: lib_paths:
- xmodule_js/common_static/js/test/i18n.js
- xmodule_js/common_static/coffee/src/ajax_prefix.js - xmodule_js/common_static/coffee/src/ajax_prefix.js
- xmodule_js/common_static/coffee/src/logger.js - xmodule_js/common_static/coffee/src/logger.js
- xmodule_js/common_static/js/vendor/jasmine-jquery.js - xmodule_js/common_static/js/vendor/jasmine-jquery.js
......
...@@ -58,8 +58,7 @@ ...@@ -58,8 +58,7 @@
</section> </section>
<div class="video-player-post"></div> <div class="video-player-post"></div>
<section class="video-controls is-hidden"> <section class="video-controls is-hidden">
<div class="slider" title="Video position"></div> <div class="slider" title="${_('Video position')}"></div>
<div> <div>
<ul class="vcr"> <ul class="vcr">
<li><a class="video_control" href="#" title="${_('Play')}" role="button" aria-disabled="false"></a></li> <li><a class="video_control" href="#" title="${_('Play')}" role="button" aria-disabled="false"></a></li>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment