mirror of
https://github.com/videojs/video.js.git
synced 2024-12-25 02:42:10 +02:00
Added localization support. closes #1360
This commit is contained in:
parent
e4fbf27358
commit
6b612d8cdc
31
Gruntfile.js
31
Gruntfile.js
@ -31,7 +31,9 @@ module.exports = function(grunt) {
|
||||
// Project configuration.
|
||||
grunt.initConfig({
|
||||
pkg: pkg,
|
||||
|
||||
lang: {
|
||||
src: 'lang/*.json'
|
||||
},
|
||||
build: {
|
||||
src: 'src/js/dependencies.js',
|
||||
options: {
|
||||
@ -331,11 +333,11 @@ module.exports = function(grunt) {
|
||||
// grunt.loadTasks('./docs/tasks/');
|
||||
// grunt.loadTasks('../videojs-doc-generator/tasks/');
|
||||
|
||||
grunt.registerTask('pretask', ['jshint', 'less', 'build', 'minify', 'usebanner']);
|
||||
grunt.registerTask('pretask', ['jshint', 'less', 'lang', 'build', 'minify', 'usebanner']);
|
||||
// Default task.
|
||||
grunt.registerTask('default', ['pretask', 'dist']);
|
||||
// Development watch task
|
||||
grunt.registerTask('dev', ['jshint', 'less', 'build', 'qunit:source']);
|
||||
grunt.registerTask('dev', ['jshint', 'less', 'lang', 'build', 'qunit:source']);
|
||||
grunt.registerTask('test-qunit', ['pretask', 'qunit']);
|
||||
|
||||
// The test task will run `karma:saucelabs` when running in travis,
|
||||
@ -425,7 +427,26 @@ module.exports = function(grunt) {
|
||||
var fs = require('fs'),
|
||||
gzip = require('zlib').gzip;
|
||||
|
||||
grunt.registerMultiTask('lang', 'Building Language Support', function() {
|
||||
|
||||
grunt.log.writeln('Building Language Support');
|
||||
|
||||
var langFiles = this.files;
|
||||
var combined;
|
||||
|
||||
// Create a combined languages file
|
||||
langFiles.forEach(function(result) {
|
||||
combined += grunt.file.read(result.src);
|
||||
});
|
||||
|
||||
combined = combined.replace('undefined', 'vjs.options["languages"] = ');
|
||||
|
||||
grunt.file.write('build/files/combined.languages.js', combined);
|
||||
|
||||
});
|
||||
|
||||
grunt.registerMultiTask('build', 'Building Source', function(){
|
||||
|
||||
// Fix windows file path delimiter issue
|
||||
var i = sourceFiles.length;
|
||||
while (i--) {
|
||||
@ -439,6 +460,10 @@ module.exports = function(grunt) {
|
||||
});
|
||||
// Replace CDN version ref in js. Use major/minor version.
|
||||
combined = combined.replace(/GENERATED_CDN_VSN/g, version.majorMinor);
|
||||
|
||||
// Add Combined Langauges
|
||||
combined += grunt.file.read('build/files/combined.languages.js');
|
||||
|
||||
grunt.file.write('build/files/combined.video.js', combined);
|
||||
|
||||
// Copy over other files
|
||||
|
28
lang/es.json
Normal file
28
lang/es.json
Normal file
@ -0,0 +1,28 @@
|
||||
{'es':
|
||||
{
|
||||
'Play': 'Juego',
|
||||
'Pause': 'Pausa',
|
||||
'Current Time': 'Tiempo Actual',
|
||||
'Duration Time': 'Tiempo de Duracion',
|
||||
'Remaining Time': 'Tiempo Restante',
|
||||
'Stream Type': 'Tipo de Transmision',
|
||||
'LIVE': 'En Vivo',
|
||||
'Loaded': 'Cargado',
|
||||
'Progress': 'Progreso',
|
||||
'Fullscreen': 'Pantalla Completa',
|
||||
'Non-Fullscreen': 'No Pantalla Completa',
|
||||
'Mute': 'Mudo',
|
||||
'Unmuted': 'Activar sonido',
|
||||
'Playback Rate': 'Reproduccion Cambio',
|
||||
'Subtitles': 'Subtitulos',
|
||||
'subtitles off': 'subtitulos fuera',
|
||||
'Captions': 'Subtitulos',
|
||||
'captions off': 'subtitulos fuera',
|
||||
'Chapters': 'Capitulos',
|
||||
'You aborted the video playback': 'Ha anulado la reproduccion de video',
|
||||
'A network error caused the video download to fail part-way.': 'Un error en la red hizo que la descarga de video falle parte del camino.',
|
||||
'The video could not be loaded, either because the server or network failed or because the format is not supported.': 'El video no se puede cargar, ya sea porque el servidor o la red fracasaron o porque el formato no es compatible.',
|
||||
'The video playback was aborted due to a corruption problem or because the video used features your browser did not support.': 'La reproduccion de video se ha cancelado debido a un problema de corrupcion o porque el video utilizado cuenta con su navegador no soporta.',
|
||||
'No compatible source was found for this video.': 'Ninguna fuente compatible se encontro para este video.'
|
||||
}
|
||||
}
|
@ -45,7 +45,7 @@ vjs.Button.prototype.createEl = function(type, props){
|
||||
|
||||
this.controlText_ = vjs.createEl('span', {
|
||||
className: 'vjs-control-text',
|
||||
innerHTML: this.buttonText || 'Need Text'
|
||||
innerHTML: this.localize(this.buttonText) || 'Need Text'
|
||||
});
|
||||
|
||||
this.contentEl_.appendChild(this.controlText_);
|
||||
|
@ -195,6 +195,15 @@ vjs.Component.prototype.createEl = function(tagName, attributes){
|
||||
return vjs.createEl(tagName, attributes);
|
||||
};
|
||||
|
||||
vjs.Component.prototype.localize = function(string){
|
||||
var lang = this.player_.language(),
|
||||
languages = this.player_.languages();
|
||||
if (languages && languages[lang] && languages[lang][string]) {
|
||||
return languages[lang][string];
|
||||
}
|
||||
return string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the component's DOM element
|
||||
*
|
||||
|
@ -25,9 +25,9 @@ vjs.FullscreenToggle.prototype.buildCSSClass = function(){
|
||||
vjs.FullscreenToggle.prototype.onClick = function(){
|
||||
if (!this.player_.isFullscreen()) {
|
||||
this.player_.requestFullscreen();
|
||||
this.controlText_.innerHTML = 'Non-Fullscreen';
|
||||
this.controlText_.innerHTML = this.localize('Non-Fullscreen');
|
||||
} else {
|
||||
this.player_.exitFullscreen();
|
||||
this.controlText_.innerHTML = 'Fullscreen';
|
||||
this.controlText_.innerHTML = this.localize('Fullscreen');
|
||||
}
|
||||
};
|
||||
|
@ -18,7 +18,7 @@ vjs.LiveDisplay.prototype.createEl = function(){
|
||||
|
||||
this.contentEl_ = vjs.createEl('div', {
|
||||
className: 'vjs-live-display',
|
||||
innerHTML: '<span class="vjs-control-text">Stream Type </span>LIVE',
|
||||
innerHTML: '<span class="vjs-control-text">' + this.localize('Stream Type') + '</span>' + this.localize('LIVE'),
|
||||
'aria-live': 'off'
|
||||
});
|
||||
|
||||
|
@ -29,7 +29,7 @@ vjs.MuteToggle = vjs.Button.extend({
|
||||
vjs.MuteToggle.prototype.createEl = function(){
|
||||
return vjs.Button.prototype.createEl.call(this, 'div', {
|
||||
className: 'vjs-mute-control vjs-control',
|
||||
innerHTML: '<div><span class="vjs-control-text">Mute</span></div>'
|
||||
innerHTML: '<div><span class="vjs-control-text">' + this.localize('Mute') + '</span></div>'
|
||||
});
|
||||
};
|
||||
|
||||
@ -53,12 +53,12 @@ vjs.MuteToggle.prototype.update = function(){
|
||||
// This causes unnecessary and confusing information for screen reader users.
|
||||
// This check is needed because this function gets called every time the volume level is changed.
|
||||
if(this.player_.muted()){
|
||||
if(this.el_.children[0].children[0].innerHTML!='Unmute'){
|
||||
this.el_.children[0].children[0].innerHTML = 'Unmute'; // change the button text to "Unmute"
|
||||
if(this.el_.children[0].children[0].innerHTML!=this.localize('Unmute')){
|
||||
this.el_.children[0].children[0].innerHTML = this.localize('Unmute'); // change the button text to "Unmute"
|
||||
}
|
||||
} else {
|
||||
if(this.el_.children[0].children[0].innerHTML!='Mute'){
|
||||
this.el_.children[0].children[0].innerHTML = 'Mute'; // change the button text to "Mute"
|
||||
if(this.el_.children[0].children[0].innerHTML!=this.localize('Mute')){
|
||||
this.el_.children[0].children[0].innerHTML = this.localize('Mute'); // change the button text to "Mute"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,12 +34,12 @@ vjs.PlayToggle.prototype.onClick = function(){
|
||||
vjs.PlayToggle.prototype.onPlay = function(){
|
||||
vjs.removeClass(this.el_, 'vjs-paused');
|
||||
vjs.addClass(this.el_, 'vjs-playing');
|
||||
this.el_.children[0].children[0].innerHTML = 'Pause'; // change the button text to "Pause"
|
||||
this.el_.children[0].children[0].innerHTML = this.localize('Pause'); // change the button text to "Pause"
|
||||
};
|
||||
|
||||
// OnPause - Add the vjs-paused class to the element so it can change appearance
|
||||
vjs.PlayToggle.prototype.onPause = function(){
|
||||
vjs.removeClass(this.el_, 'vjs-playing');
|
||||
vjs.addClass(this.el_, 'vjs-paused');
|
||||
this.el_.children[0].children[0].innerHTML = 'Play'; // change the button text to "Play"
|
||||
this.el_.children[0].children[0].innerHTML = this.localize('Play'); // change the button text to "Play"
|
||||
};
|
||||
|
@ -21,7 +21,7 @@ vjs.PlaybackRateMenuButton = vjs.MenuButton.extend({
|
||||
vjs.PlaybackRateMenuButton.prototype.createEl = function(){
|
||||
var el = vjs.Component.prototype.createEl.call(this, 'div', {
|
||||
className: 'vjs-playback-rate vjs-menu-button vjs-control',
|
||||
innerHTML: '<div class="vjs-control-content"><span class="vjs-control-text">Playback Rate</span></div>'
|
||||
innerHTML: '<div class="vjs-control-content"><span class="vjs-control-text">' + this.localize('Playback Rate') + '</span></div>'
|
||||
});
|
||||
|
||||
this.labelEl_ = vjs.createEl('div', {
|
||||
|
@ -124,7 +124,8 @@ vjs.LoadProgressBar = vjs.Component.extend({
|
||||
|
||||
vjs.LoadProgressBar.prototype.createEl = function(){
|
||||
return vjs.Component.prototype.createEl.call(this, 'div', {
|
||||
className: 'vjs-load-progress'
|
||||
className: 'vjs-load-progress',
|
||||
innerHTML: '<span class="vjs-control-text"><span>' + this.localize('Loaded') + '</span>: 0%</span>'
|
||||
});
|
||||
};
|
||||
|
||||
@ -181,7 +182,7 @@ vjs.PlayProgressBar = vjs.Component.extend({
|
||||
vjs.PlayProgressBar.prototype.createEl = function(){
|
||||
return vjs.Component.prototype.createEl.call(this, 'div', {
|
||||
className: 'vjs-play-progress',
|
||||
innerHTML: '<span class="vjs-control-text">Progress: 0%</span>'
|
||||
innerHTML: '<span class="vjs-control-text"><span>' + this.localize('Progress') + '</span>: 0%</span>'
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -31,7 +31,7 @@ vjs.CurrentTimeDisplay.prototype.createEl = function(){
|
||||
vjs.CurrentTimeDisplay.prototype.updateContent = function(){
|
||||
// Allows for smooth scrubbing, when player can't keep up.
|
||||
var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
|
||||
this.contentEl_.innerHTML = '<span class="vjs-control-text">Current Time </span>' + vjs.formatTime(time, this.player_.duration());
|
||||
this.contentEl_.innerHTML = '<span class="vjs-control-text">' + this.localize('Current Time') + '</span> ' + vjs.formatTime(time, this.player_.duration());
|
||||
};
|
||||
|
||||
/**
|
||||
@ -61,7 +61,7 @@ vjs.DurationDisplay.prototype.createEl = function(){
|
||||
|
||||
this.contentEl_ = vjs.createEl('div', {
|
||||
className: 'vjs-duration-display',
|
||||
innerHTML: '<span class="vjs-control-text">Duration Time </span>' + '0:00', // label the duration time for screen reader users
|
||||
innerHTML: '<span class="vjs-control-text">' + this.localize('Duration Time') + '</span> ' + '0:00', // label the duration time for screen reader users
|
||||
'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
|
||||
});
|
||||
|
||||
@ -72,7 +72,7 @@ vjs.DurationDisplay.prototype.createEl = function(){
|
||||
vjs.DurationDisplay.prototype.updateContent = function(){
|
||||
var duration = this.player_.duration();
|
||||
if (duration) {
|
||||
this.contentEl_.innerHTML = '<span class="vjs-control-text">Duration Time </span>' + vjs.formatTime(duration); // label the duration time for screen reader users
|
||||
this.contentEl_.innerHTML = '<span class="vjs-control-text">' + this.localize('Duration Time') + '</span> ' + vjs.formatTime(duration); // label the duration time for screen reader users
|
||||
}
|
||||
};
|
||||
|
||||
@ -121,7 +121,7 @@ vjs.RemainingTimeDisplay.prototype.createEl = function(){
|
||||
|
||||
this.contentEl_ = vjs.createEl('div', {
|
||||
className: 'vjs-remaining-time-display',
|
||||
innerHTML: '<span class="vjs-control-text">Remaining Time </span>' + '-0:00', // label the remaining time for screen reader users
|
||||
innerHTML: '<span class="vjs-control-text">' + this.localize('Remaining Time') + '</span> ' + '-0:00', // label the remaining time for screen reader users
|
||||
'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
|
||||
});
|
||||
|
||||
@ -131,7 +131,7 @@ vjs.RemainingTimeDisplay.prototype.createEl = function(){
|
||||
|
||||
vjs.RemainingTimeDisplay.prototype.updateContent = function(){
|
||||
if (this.player_.duration()) {
|
||||
this.contentEl_.innerHTML = '<span class="vjs-control-text">Remaining Time </span>' + '-'+ vjs.formatTime(this.player_.remainingTime());
|
||||
this.contentEl_.innerHTML = '<span class="vjs-control-text">' + this.localize('Remaining Time') + '</span> ' + '-'+ vjs.formatTime(this.player_.remainingTime());
|
||||
}
|
||||
|
||||
// Allows for smooth scrubbing, when player can't keep up.
|
||||
|
@ -42,7 +42,7 @@ vjs.VolumeMenuButton.prototype.onClick = function(){
|
||||
vjs.VolumeMenuButton.prototype.createEl = function(){
|
||||
return vjs.Button.prototype.createEl.call(this, 'div', {
|
||||
className: 'vjs-volume-menu-button vjs-menu-button vjs-control',
|
||||
innerHTML: '<div><span class="vjs-control-text">Mute</span></div>'
|
||||
innerHTML: '<div><span class="vjs-control-text">' + this.localize('Mute') + '</span></div>'
|
||||
});
|
||||
};
|
||||
vjs.VolumeMenuButton.prototype.update = vjs.MuteToggle.prototype.update;
|
||||
|
@ -103,6 +103,11 @@ vjs.options = {
|
||||
'errorDisplay': {}
|
||||
},
|
||||
|
||||
'language': document.getElementsByTagName('html')[0].getAttribute('lang') || navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language || 'en',
|
||||
|
||||
// locales and their language translations
|
||||
'languages': {},
|
||||
|
||||
// Default message to show when a video cannot be played.
|
||||
'notSupportedMessage': 'No compatible source was found for this video.'
|
||||
};
|
||||
|
@ -26,6 +26,6 @@ vjs.ErrorDisplay.prototype.createEl = function(){
|
||||
|
||||
vjs.ErrorDisplay.prototype.update = function(){
|
||||
if (this.player().error()) {
|
||||
this.contentEl_.innerHTML = this.player().error().message;
|
||||
this.contentEl_.innerHTML = this.localize(this.player().error().message);
|
||||
}
|
||||
};
|
||||
|
@ -67,6 +67,7 @@ goog.exportProperty(vjs.Component.prototype, 'ready', vjs.Component.prototype.re
|
||||
goog.exportProperty(vjs.Component.prototype, 'addClass', vjs.Component.prototype.addClass);
|
||||
goog.exportProperty(vjs.Component.prototype, 'removeClass', vjs.Component.prototype.removeClass);
|
||||
goog.exportProperty(vjs.Component.prototype, 'buildCSSClass', vjs.Component.prototype.buildCSSClass);
|
||||
goog.exportProperty(vjs.Component.prototype, 'localize', vjs.Component.prototype.localize);
|
||||
|
||||
// Need to export ended to ensure it's not removed by CC, since it's not used internally
|
||||
goog.exportProperty(vjs.Player.prototype, 'ended', vjs.Player.prototype.ended);
|
||||
@ -76,6 +77,8 @@ goog.exportProperty(vjs.Player.prototype, 'preload', vjs.Player.prototype.preloa
|
||||
goog.exportProperty(vjs.Player.prototype, 'remainingTime', vjs.Player.prototype.remainingTime);
|
||||
goog.exportProperty(vjs.Player.prototype, 'supportsFullScreen', vjs.Player.prototype.supportsFullScreen);
|
||||
goog.exportProperty(vjs.Player.prototype, 'currentType', vjs.Player.prototype.currentType);
|
||||
goog.exportProperty(vjs.Player.prototype, 'language', vjs.Player.prototype.language);
|
||||
goog.exportProperty(vjs.Player.prototype, 'languages', vjs.Player.prototype.languages);
|
||||
|
||||
goog.exportSymbol('videojs.MediaLoader', vjs.MediaLoader);
|
||||
goog.exportSymbol('videojs.TextTrackDisplay', vjs.TextTrackDisplay);
|
||||
|
@ -45,6 +45,12 @@ vjs.Player = vjs.Component.extend({
|
||||
// (tag must exist before Player)
|
||||
options = vjs.obj.merge(this.getTagSettings(tag), options);
|
||||
|
||||
// Update Current Language
|
||||
this.language_ = options['language'] || vjs.options['language'];
|
||||
|
||||
// Update Supported Languages
|
||||
this.languages_ = options['languages'] || vjs.options['languages'];
|
||||
|
||||
// Cache for video property values.
|
||||
this.cache_ = {};
|
||||
|
||||
@ -93,6 +99,41 @@ vjs.Player = vjs.Component.extend({
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* The players's stored language code
|
||||
*
|
||||
* @type {String}
|
||||
* @private
|
||||
*/
|
||||
vjs.Player.prototype.language_;
|
||||
|
||||
/**
|
||||
* The player's language code
|
||||
* @param {String} languageCode The locale string
|
||||
* @return {String} The locale string when getting
|
||||
* @return {vjs.Player} self, when setting
|
||||
*/
|
||||
vjs.Player.prototype.language = function (languageCode) {
|
||||
if (languageCode === undefined) {
|
||||
return this.language_;
|
||||
}
|
||||
|
||||
this.language_ = languageCode;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* The players's stored language dictionary
|
||||
*
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
vjs.Player.prototype.languages_;
|
||||
|
||||
vjs.Player.prototype.languages = function(){
|
||||
return this.languages_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Player instance options, surfaced using vjs.options
|
||||
* vjs.options = vjs.Player.prototype.options_
|
||||
|
@ -25,6 +25,7 @@
|
||||
'test/unit/util.js',
|
||||
'test/unit/events.js',
|
||||
'test/unit/component.js',
|
||||
'test/unit/button.js',
|
||||
'test/unit/mediafaker.js',
|
||||
'test/unit/player.js',
|
||||
'test/unit/core.js',
|
||||
|
@ -183,12 +183,8 @@ test('fullscreenToggle does not depend on minified player methods', function(){
|
||||
noop = function(){};
|
||||
requestFullscreen = false;
|
||||
exitFullscreen = false;
|
||||
player = {
|
||||
id: noop,
|
||||
on: noop,
|
||||
ready: noop,
|
||||
reportUserActivity: noop
|
||||
};
|
||||
|
||||
player = PlayerTest.makePlayer();
|
||||
|
||||
player['requestFullscreen'] = function(){
|
||||
requestFullscreen = true;
|
||||
|
22
test/unit/button.js
Normal file
22
test/unit/button.js
Normal file
@ -0,0 +1,22 @@
|
||||
module('Button');
|
||||
|
||||
test('should localize its text', function(){
|
||||
expect(1);
|
||||
|
||||
var player, testButton, el;
|
||||
|
||||
player = PlayerTest.makePlayer({
|
||||
'language': 'es',
|
||||
'languages': {
|
||||
'es': {
|
||||
'Play': 'Juego'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
testButton = new vjs.Button(player);
|
||||
testButton.buttonText = 'Play';
|
||||
el = testButton.createEl();
|
||||
|
||||
ok(el.textContent, 'Juego', 'translation was successful');
|
||||
});
|
4
test/unit/controls.js
vendored
4
test/unit/controls.js
vendored
@ -9,6 +9,8 @@ test('should hide volume control if it\'s not supported', function(){
|
||||
id: noop,
|
||||
on: noop,
|
||||
ready: noop,
|
||||
language: noop,
|
||||
languages: noop,
|
||||
tech: {
|
||||
features: {
|
||||
'volumeControl': false
|
||||
@ -32,6 +34,8 @@ test('should test and toggle volume control on `loadstart`', function(){
|
||||
listeners = [];
|
||||
player = {
|
||||
id: noop,
|
||||
language: noop,
|
||||
languages: noop,
|
||||
on: function(event, callback){
|
||||
listeners.push(callback);
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user