Browse Source

Add translation support (implements #147).

pull/723/head
Matthew Petroff 7 years ago
parent
commit
93f3df1431
2 changed files with 78 additions and 37 deletions
  1. +6
    -5
      doc/json-config-parameters.md
  2. +72
    -32
      src/js/pannellum.js

+ 6
- 5
doc/json-config-parameters.md View File

@@ -23,6 +23,12 @@ If set, the value is displayed as the panorama's author. If no author is
desired, don't set this parameter. desired, don't set this parameter.




### `strings` (dictionary)

Allows user-facing strings to be changed / translated.
See `defaultConfig.strings` definition in `pannellum.js` for more details.


### `basePath` (string) ### `basePath` (string)


This specifies a base path to load the images from. This specifies a base path to load the images from.
@@ -165,11 +171,6 @@ Specifies the title to be displayed while the load button is displayed.
Specifies the author to be displayed while the load button is displayed. Specifies the author to be displayed while the load button is displayed.




### `loadButtonLabel` (string)

Label to display on load button. Defaults to `Click to Load Panorama`.


### `horizonPitch` and `horizonRoll` (number) ### `horizonPitch` and `horizonRoll` (number)


Specifies pitch / roll of image horizon, in degrees (for correcting Specifies pitch / roll of image horizon, in degrees (for correcting


+ 72
- 32
src/js/pannellum.js View File

@@ -100,10 +100,33 @@ var defaultConfig = {
hotSpotDebug: false, hotSpotDebug: false,
backgroundColor: [0, 0, 0], backgroundColor: [0, 0, 0],
animationTimingFunction: timingFunction, animationTimingFunction: timingFunction,
loadButtonLabel: 'Click to\nLoad\nPanorama',
draggable: true, draggable: true,
}; };


// Translatable / configurable strings
// Some strings contain '%s', which is a placeholder for inserted values
// When setting strings in external configuration, `\n` should be used instead of `<br>` to insert line breaks
defaultConfig.strings = {
// Labels
loadButtonLabel: 'Click to<br>Load<br>Panorama',
loadingLabel: 'Loading...',
bylineLabel: 'by %s', // One substitution: author

// Errors
noPanoramaError: 'No panorama image was specified.',
fileAccessError: 'The file %s could not be accessed.', // One substitution: file URL
malformedURLError: 'There is something wrong with the panorama URL.',
iOS8WebGLError: "Due to iOS 8's broken WebGL implementation, only " +
"progressive encoded JPEGs work for your device (this " +
"panorama uses standard encoding).",
genericWebGLError: 'Your browser does not have the necessary WebGL support to display this panorama.',
textureSizeError: 'This panorama is too big for your device! It\'s ' +
'%spx wide, but your device only supports images up to ' +
'%spx wide. Try another device.' +
' (If you\'re the author, try scaling down the image.)', // Two substitutions: image width, max image width
unknownError: 'Unknown error. Check developer console.',
}

var usedKeyNumbers = [16, 17, 27, 37, 38, 39, 40, 61, 65, 68, 83, 87, 107, 109, 173, 187, 189]; var usedKeyNumbers = [16, 17, 27, 37, 38, 39, 40, 61, 65, 68, 83, 87, 107, 109, 173, 187, 189];


// Initialize container // Initialize container
@@ -154,7 +177,6 @@ uiContainer.appendChild(infoDisplay.container);
infoDisplay.load = {}; infoDisplay.load = {};
infoDisplay.load.box = document.createElement('div'); infoDisplay.load.box = document.createElement('div');
infoDisplay.load.box.className = 'pnlm-load-box'; infoDisplay.load.box.className = 'pnlm-load-box';
infoDisplay.load.box.innerHTML = '<p>Loading...</p>';
infoDisplay.load.lbox = document.createElement('div'); infoDisplay.load.lbox = document.createElement('div');
infoDisplay.load.lbox.className = 'pnlm-lbox'; infoDisplay.load.lbox.className = 'pnlm-lbox';
infoDisplay.load.lbox.innerHTML = '<div class="pnlm-loading"></div>'; infoDisplay.load.lbox.innerHTML = '<div class="pnlm-loading"></div>';
@@ -303,7 +325,7 @@ function init() {
panoImage = config.panorama; panoImage = config.panorama;
} else { } else {
if (config.panorama === undefined) { if (config.panorama === undefined) {
anError('No panorama image was specified.');
anError(config.strings.noPanoramaError);
return; return;
} }
panoImage = new Image(); panoImage = new Image();
@@ -326,7 +348,7 @@ function init() {
var a = document.createElement('a'); var a = document.createElement('a');
a.href = e.target.src; a.href = e.target.src;
a.innerHTML = a.href; a.innerHTML = a.href;
anError('The file ' + a.outerHTML + ' could not be accessed.');
anError(config.strings.fileAccessError.replace('%s', a.outerHTML));
}; };
for (i = 0; i < panoImage.length; i++) { for (i = 0; i < panoImage.length; i++) {
@@ -362,7 +384,7 @@ function init() {
var a = document.createElement('a'); var a = document.createElement('a');
a.href = encodeURI(p); a.href = encodeURI(p);
a.innerHTML = a.href; a.innerHTML = a.href;
anError('The file ' + a.outerHTML + ' could not be accessed.');
anError(config.strings.fileAccessError.replace('%s', a.outerHTML));
} }
var img = this.response; var img = this.response;
parseGPanoXMP(img); parseGPanoXMP(img);
@@ -398,7 +420,7 @@ function init() {
xhr.open('GET', p, true); xhr.open('GET', p, true);
} catch (e) { } catch (e) {
// Malformed URL // Malformed URL
anError('There is something wrong with the panorama URL.');
anError(config.strings.malformedURLError);
} }
xhr.responseType = 'blob'; xhr.responseType = 'blob';
xhr.setRequestHeader('Accept', 'image/*,*/*;q=0.9'); xhr.setRequestHeader('Accept', 'image/*,*/*;q=0.9');
@@ -484,11 +506,8 @@ function parseGPanoXMP(image) {
// with non-progressive encoded JPEGs. // with non-progressive encoded JPEGs.
if (navigator.userAgent.toLowerCase().match(/(iphone|ipod|ipad).* os 8_/)) { if (navigator.userAgent.toLowerCase().match(/(iphone|ipod|ipad).* os 8_/)) {
var flagIndex = img.indexOf('\xff\xc2'); var flagIndex = img.indexOf('\xff\xc2');
if (flagIndex < 0 || flagIndex > 65536) {
anError("Due to iOS 8's broken WebGL implementation, only " +
"progressive encoded JPEGs work for your device (this " +
"panorama uses standard encoding).");
}
if (flagIndex < 0 || flagIndex > 65536)
anError(config.strings.iOS8WebGLError);
} }


var start = img.indexOf('<x:xmpmeta'); var start = img.indexOf('<x:xmpmeta');
@@ -569,7 +588,7 @@ function parseGPanoXMP(image) {
*/ */
function anError(errorMsg) { function anError(errorMsg) {
if (errorMsg === undefined) if (errorMsg === undefined)
errorMsg = 'Your browser does not have the necessary WebGL support to display this panorama.';
errorMsg = config.strings.genericWebGLError;
infoDisplay.errorMsg.innerHTML = '<p>' + errorMsg + '</p>'; infoDisplay.errorMsg.innerHTML = '<p>' + errorMsg + '</p>';
controls.load.style.display = 'none'; controls.load.style.display = 'none';
infoDisplay.load.box.style.display = 'none'; infoDisplay.load.box.style.display = 'none';
@@ -1538,12 +1557,9 @@ function renderInit() {
if (event.type == 'webgl error' || event.type == 'no webgl') { if (event.type == 'webgl error' || event.type == 'no webgl') {
anError(); anError();
} else if (event.type == 'webgl size error') { } else if (event.type == 'webgl size error') {
anError('This panorama is too big for your device! It\'s ' +
event.width + 'px wide, but your device only supports images up to ' +
event.maxWidth + 'px wide. Try another device.' +
' (If you\'re the author, try scaling down the image.)');
anError(config.strings.textureSizeError.replace('%s', event.width).replace('%s', event.maxWidth));
} else { } else {
anError('Unknown error. Check developer console.');
anError(config.strings.unknownError);
throw event; throw event;
} }
} }
@@ -1777,7 +1793,7 @@ function renderHotSpots() {
*/ */
function mergeConfig(sceneId) { function mergeConfig(sceneId) {
config = {}; config = {};
var k;
var k, s;
var photoSphereExcludes = ['haov', 'vaov', 'vOffset', 'northOffset', 'horizonPitch', 'horizonRoll']; var photoSphereExcludes = ['haov', 'vaov', 'vOffset', 'northOffset', 'horizonPitch', 'horizonRoll'];
specifiedPhotoSphereExcludes = []; specifiedPhotoSphereExcludes = [];
@@ -1791,9 +1807,17 @@ function mergeConfig(sceneId) {
// Merge default scene config // Merge default scene config
for (k in initialConfig.default) { for (k in initialConfig.default) {
if (initialConfig.default.hasOwnProperty(k)) { if (initialConfig.default.hasOwnProperty(k)) {
config[k] = initialConfig.default[k];
if (photoSphereExcludes.indexOf(k) >= 0) {
specifiedPhotoSphereExcludes.push(k);
if (k == 'strings') {
for (s in initialConfig.default.strings) {
if (initialConfig.default.strings.hasOwnProperty(s)) {
config.strings[s] = escapeHTML(initialConfig.default.strings[s]);
}
}
} else {
config[k] = initialConfig.default[k];
if (photoSphereExcludes.indexOf(k) >= 0) {
specifiedPhotoSphereExcludes.push(k);
}
} }
} }
} }
@@ -1803,9 +1827,17 @@ function mergeConfig(sceneId) {
var scene = initialConfig.scenes[sceneId]; var scene = initialConfig.scenes[sceneId];
for (k in scene) { for (k in scene) {
if (scene.hasOwnProperty(k)) { if (scene.hasOwnProperty(k)) {
config[k] = scene[k];
if (photoSphereExcludes.indexOf(k) >= 0) {
specifiedPhotoSphereExcludes.push(k);
if (k == 'strings') {
for (s in scene.strings) {
if (scene.strings.hasOwnProperty(s)) {
config.strings[s] = escapeHTML(scene.strings[s]);
}
}
} else {
config[k] = scene[k];
if (photoSphereExcludes.indexOf(k) >= 0) {
specifiedPhotoSphereExcludes.push(k);
}
} }
} }
} }
@@ -1815,9 +1847,17 @@ function mergeConfig(sceneId) {
// Merge initial config // Merge initial config
for (k in initialConfig) { for (k in initialConfig) {
if (initialConfig.hasOwnProperty(k)) { if (initialConfig.hasOwnProperty(k)) {
config[k] = initialConfig[k];
if (photoSphereExcludes.indexOf(k) >= 0) {
specifiedPhotoSphereExcludes.push(k);
if (k == 'strings') {
for (s in initialConfig.strings) {
if (initialConfig.strings.hasOwnProperty(s)) {
config.strings[s] = escapeHTML(initialConfig.strings[s]);
}
}
} else {
config[k] = initialConfig[k];
if (photoSphereExcludes.indexOf(k) >= 0) {
specifiedPhotoSphereExcludes.push(k);
}
} }
} }
} }
@@ -1862,6 +1902,10 @@ function processOptions(isPreview) {
if (!config.hasOwnProperty('title') && !config.hasOwnProperty('author')) if (!config.hasOwnProperty('title') && !config.hasOwnProperty('author'))
infoDisplay.container.style.display = 'none'; infoDisplay.container.style.display = 'none';


// Fill in load button label and loading box text
controls.load.innerHTML = '<p>' + config.strings.loadButtonLabel + '</p>';
infoDisplay.load.box.innerHTML = '<p>' + config.strings.loadingLabel + '</p>';

// Process other options // Process other options
for (var key in config) { for (var key in config) {
if (config.hasOwnProperty(key)) { if (config.hasOwnProperty(key)) {
@@ -1872,7 +1916,7 @@ function processOptions(isPreview) {
break; break;
case 'author': case 'author':
infoDisplay.author.innerHTML = 'by ' + escapeHTML(config[key]);
infoDisplay.author.innerHTML = config.strings.bylineLabel.replace('%s', escapeHTML(config[key]));
infoDisplay.container.style.display = 'inline'; infoDisplay.container.style.display = 'inline';
break; break;
@@ -1940,10 +1984,6 @@ function processOptions(isPreview) {
startOrientation(); startOrientation();
} }
break; break;

case 'loadButtonLabel':
controls.load.innerHTML = '<p>' + escapeHTML(config[key]) + '</p>';
break;
} }
} }
} }


Loading…
Cancel
Save