diff --git a/lib/locales/cs.js b/lib/locales/cs.js
index 4d715482e54..19b3c99562b 100644
--- a/lib/locales/cs.js
+++ b/lib/locales/cs.js
@@ -32,9 +32,9 @@ module.exports = {
'Reset view': 'Obnovit nastavení pohledu', // components/modebar/buttons.js:583
'Reset views': 'Obnovit nastavení pohledů', // components/modebar/buttons.js:529
'Show closest data on hover': 'Zobrazit najbližší hodnotu při najetí myší', // components/modebar/buttons.js:157
- 'Snapshot succeeded': 'Snímek vytvořen', // components/modebar/buttons.js:66
- 'Sorry, there was a problem downloading your snapshot!': 'Omlouváme se, ale došlo k chybě stahování snímku!', // components/modebar/buttons.js:69
- 'Taking snapshot - this may take a few seconds': 'Vytváří se snímek - může zabrat pár vteřin', // components/modebar/buttons.js:57
+ 'Image download succeeded': 'Snímek vytvořen', // components/modebar/buttons.js:66
+ 'Sorry, there was a problem downloading your image!': 'Omlouváme se, ale došlo k chybě stahování snímku!', // components/modebar/buttons.js:69
+ 'Capturing image - this may take a few seconds': 'Vytváří se snímek - může zabrat pár vteřin', // components/modebar/buttons.js:57
'Zoom': 'Zvětšení', // components/modebar/buttons.js:85
'Zoom in': 'Zvětšit', // components/modebar/buttons.js:121
'Zoom out': 'Zmenšit', // components/modebar/buttons.js:130
diff --git a/lib/locales/cy.js b/lib/locales/cy.js
index f8f792eac4d..475a28134f2 100644
--- a/lib/locales/cy.js
+++ b/lib/locales/cy.js
@@ -32,9 +32,9 @@ module.exports = {
'Reset view': 'Ailosodwch y golwg', // components / modebar / buttons.js: 592
'Reset views': 'Ailosod olygfeydd', // components / modebar / buttons.js: 540
'Show closest data on hover': 'Dangos y data agosaf wrth hofran', // components / modebar / buttons.js: 168
- 'Snapshot succeeded': 'Llwyddodd y Ciplun', // components / modebar / buttons.js: 77
- 'Sorry, there was a problem downloading your snapshot!': 'Mae\'n ddrwg gennym, roedd problem wrth lawrlwytho eich ciplun!', // components / modebar / buttons.js: 80
- 'Taking snapshot - this may take a few seconds': 'Tynnu ciplun - gallai hyn gymryd ychydig o eiliadau', // components / modebar / buttons.js: 62
+ 'Image download succeeded': 'Llwyddodd y Ciplun', // components / modebar / buttons.js: 77
+ 'Sorry, there was a problem downloading your image!': 'Mae\'n ddrwg gennym, roedd problem wrth lawrlwytho eich ciplun!', // components / modebar / buttons.js: 80
+ 'Capturing image - this may take a few seconds': 'Tynnu ciplun - gallai hyn gymryd ychydig o eiliadau', // components / modebar / buttons.js: 62
'Toggle Spike Lines': 'Toglo llinellau pigog', // components / modebar / buttons.js: 559
'Toggle show closest data on hover': 'Toglo dangos y data agosaf wrth hofran', // components / modebar / buttons.js: 364
'Turntable rotation': 'Cylchdroi trofwrdd', // components / modebar / buttons.js: 296
diff --git a/lib/locales/de.js b/lib/locales/de.js
index 2be03de0398..1e066990a46 100644
--- a/lib/locales/de.js
+++ b/lib/locales/de.js
@@ -31,9 +31,9 @@ module.exports = {
'Reset view': 'Ansicht zurücksetzen', // components/modebar/buttons.js:583
'Reset views': 'Ansichten zurücksetzen', // components/modebar/buttons.js:529
'Show closest data on hover': 'Zeige näheste Daten beim Überfahren', // components/modebar/buttons.js:157
- 'Snapshot succeeded': 'Snapshot erfolgreich', // components/modebar/buttons.js:66
- 'Sorry, there was a problem downloading your snapshot!': 'Es gab ein Problem beim Herunterladen des Snapshots', // components/modebar/buttons.js:69
- 'Taking snapshot - this may take a few seconds': 'Erstelle einen Snapshot - dies kann einige Sekunden dauern', // components/modebar/buttons.js:57
+ 'Image download succeeded': 'Snapshot erfolgreich', // components/modebar/buttons.js:66
+ 'Sorry, there was a problem downloading your image!': 'Es gab ein Problem beim Herunterladen des Snapshots', // components/modebar/buttons.js:69
+ 'Capturing image - this may take a few seconds': 'Erstelle einen Snapshot - dies kann einige Sekunden dauern', // components/modebar/buttons.js:57
'Zoom': 'Zoom', // components/modebar/buttons.js:85
'Zoom in': 'Hineinzoomen', // components/modebar/buttons.js:121
'Zoom out': 'Herauszoomen', // components/modebar/buttons.js:130
diff --git a/lib/locales/es.js b/lib/locales/es.js
index 15a8a41369b..9e27a55c2e3 100644
--- a/lib/locales/es.js
+++ b/lib/locales/es.js
@@ -33,9 +33,9 @@ module.exports = {
'Reset view': 'Restaurar vista', // components/modebar/buttons.js:582
'Reset views': 'Restaurar vistas', // components/modebar/buttons.js:528
'Show closest data on hover': 'Mostrar el dato más cercano al pasar por encima', // components/modebar/buttons.js:157
- 'Snapshot succeeded': 'La captura de la instantánea finalizó correctamente', // components/modebar/buttons.js:66
- 'Sorry, there was a problem downloading your snapshot!': '¡La descarga de la instantánea falló!', // components/modebar/buttons.js:69
- 'Taking snapshot - this may take a few seconds': 'Capturando una instantánea - podría tardar unos segundos', // components/modebar/buttons.js:57
+ 'Image download succeeded': 'La descarga de la imagen finalizó correctamente', // components/modebar/buttons.js:66
+ 'Sorry, there was a problem downloading your image!': '¡La descarga de la imagen falló!', // components/modebar/buttons.js:69
+ 'Capturing image - this may take a few seconds': 'Capturando una imagen - podría tardar unos segundos', // components/modebar/buttons.js:57
'Toggle Spike Lines': 'Mostrar/Ocultar Guías', // components/modebar/buttons.js:547
'Toggle show closest data on hover': 'Activar/Desactivar mostrar el dato más cercano al pasar por encima', // components/modebar/buttons.js:352
'Turntable rotation': 'Rotación plana', // components/modebar/buttons.js:288
diff --git a/lib/locales/fi.js b/lib/locales/fi.js
index 82ae56a9ada..5b7cb940cb4 100644
--- a/lib/locales/fi.js
+++ b/lib/locales/fi.js
@@ -32,9 +32,9 @@ module.exports = {
'Reset view': 'Palauta näkymän oletusasetukset',
'Reset views': 'Palauta näkymien oletusasetukset',
'Show closest data on hover': 'Näytä kursoria lähin data',
- 'Snapshot succeeded': 'Tilannekuvan ottaminen onnistui',
- 'Sorry, there was a problem downloading your snapshot!': 'Pahoittelut, tilannekuvan lataaminen epäonnistui!',
- 'Taking snapshot - this may take a few seconds': 'Otetaan tilannekuvaa - odota hetki',
+ 'Image download succeeded': 'Tilannekuvan ottaminen onnistui',
+ 'Sorry, there was a problem downloading your image!': 'Pahoittelut, tilannekuvan lataaminen epäonnistui!',
+ 'Capturing image - this may take a few seconds': 'Otetaan tilannekuvaa - odota hetki',
'Toggle Spike Lines': 'Näytä huiput',
'Toggle show closest data on hover': 'Näytä kursoria lähin data',
'Turntable rotation': 'Tasokierto',
diff --git a/lib/locales/fr.js b/lib/locales/fr.js
index 4e4653f8eee..dc6e27f2d5c 100644
--- a/lib/locales/fr.js
+++ b/lib/locales/fr.js
@@ -33,9 +33,9 @@ module.exports = {
'Reset view': 'Réinitialiser',
'Reset views': 'Réinitialiser',
'Show closest data on hover': 'Données les plus proches en survol',
- 'Snapshot succeeded': 'Conversion réussie',
- 'Sorry, there was a problem downloading your snapshot!': 'Désolé, un problème est survenu lors du téléchargement de votre graphique',
- 'Taking snapshot - this may take a few seconds': 'Conversion en cours, ceci peut prendre quelques secondes',
+ 'Image download succeeded': 'Téléchargement du graphique réussi',
+ 'Sorry, there was a problem downloading your image!': 'Désolé, un problème est survenu lors du téléchargement de votre graphique',
+ 'Capturing image - this may take a few seconds': 'Conversion en cours, ceci peut prendre quelques secondes',
'Zoom': 'Zoom',
'Zoom in': 'Zoom intérieur',
'Zoom out': 'Zoom extérieur',
diff --git a/lib/locales/hr.js b/lib/locales/hr.js
index 061acf8f394..e73d7511fa3 100644
--- a/lib/locales/hr.js
+++ b/lib/locales/hr.js
@@ -32,9 +32,9 @@ module.exports = {
'Reset view': 'Resetirajte pogled',
'Reset views': 'Resetirajte poglede',
'Show closest data on hover': 'Prikaži najbliže podatke pri zadržavanju mišem',
- 'Snapshot succeeded': 'Preuzimanje slike uspješno',
- 'Sorry, there was a problem downloading your snapshot!': 'Pojavila se greška prilikom preuzimanja slike!',
- 'Taking snapshot - this may take a few seconds': 'Preuzimanje slike - ovo može potrajati nekoliko sekundi',
+ 'Image download succeeded': 'Preuzimanje slike uspješno',
+ 'Sorry, there was a problem downloading your image!': 'Pojavila se greška prilikom preuzimanja slike!',
+ 'Capturing image - this may take a few seconds': 'Preuzimanje slike - ovo može potrajati nekoliko sekundi',
'Toggle Spike Lines': 'Postavljanje pomoćnih linija',
'Toggle show closest data on hover': 'Postavljanje prikaza najbližih podataka pri zadržavanju mišem',
'Turntable rotation': 'Turntable rotiranje',
diff --git a/lib/locales/it.js b/lib/locales/it.js
index 7118f71d620..41b2877fd2d 100644
--- a/lib/locales/it.js
+++ b/lib/locales/it.js
@@ -32,9 +32,9 @@ module.exports = {
'Reset view': 'Reimposta la vista', // components/modebar/buttons.js:583
'Reset views': 'Reimposta le viste', // components/modebar/buttons.js:529
'Show closest data on hover': 'Mostra i dati più vicini al passaggio del mouse', // components/modebar/buttons.js:157
- 'Snapshot succeeded': 'Screenshot creato con successo', // components/modebar/buttons.js:66
- 'Sorry, there was a problem downloading your snapshot!': 'Si è verificato un errore durante la creazione dello screenshot', // components/modebar/buttons.js:69
- 'Taking snapshot - this may take a few seconds': 'Creazione screenshot - potrebbe richiedere qualche secondo', // components/modebar/buttons.js:57
+ 'Image download succeeded': 'Screenshot creato con successo', // components/modebar/buttons.js:66
+ 'Sorry, there was a problem downloading your image!': 'Si è verificato un errore durante la creazione dello screenshot', // components/modebar/buttons.js:69
+ 'Capturing image - this may take a few seconds': 'Creazione screenshot - potrebbe richiedere qualche secondo', // components/modebar/buttons.js:57
'Zoom': 'Zoom', // components/modebar/buttons.js:85
'Zoom in': 'Ingrandisci', // components/modebar/buttons.js:121
'Zoom out': 'Riduci', // components/modebar/buttons.js:130
diff --git a/lib/locales/ja.js b/lib/locales/ja.js
index e227cf3c139..fb201027411 100644
--- a/lib/locales/ja.js
+++ b/lib/locales/ja.js
@@ -31,9 +31,9 @@ module.exports = {
'Reset view': 'ビューをリセット', // components/modebar/buttons.js:583
'Reset views': 'ビューをリセット', // components/modebar/buttons.js:529
'Show closest data on hover': 'ホバー時に一番近いデータを表示', // components/modebar/buttons.js:157
- 'Snapshot succeeded': 'スナップショットに成功', // components/modebar/buttons.js:66
- 'Sorry, there was a problem downloading your snapshot!': 'すみません、スナップショットダウンロードでエラーです!', // components/modebar/buttons.js:69
- 'Taking snapshot - this may take a few seconds': 'スナップショットを撮影', // components/modebar/buttons.js:57
+ 'Image download succeeded': 'スナップショットに成功', // components/modebar/buttons.js:66
+ 'Sorry, there was a problem downloading your image!': 'すみません、スナップショットダウンロードでエラーです!', // components/modebar/buttons.js:69
+ 'Capturing image - this may take a few seconds': 'スナップショットを撮影', // components/modebar/buttons.js:57
'Zoom': 'ズーム', // components/modebar/buttons.js:85
'Zoom in': '拡大', // components/modebar/buttons.js:121
'Zoom out': '縮小', // components/modebar/buttons.js:130
diff --git a/lib/locales/ko.js b/lib/locales/ko.js
index 73d7d34bf2f..920acea272e 100644
--- a/lib/locales/ko.js
+++ b/lib/locales/ko.js
@@ -32,9 +32,9 @@ module.exports = {
'Reset view': 'view 초기화',
'Reset views': 'views 초기화',
'Show closest data on hover': '마우스를 올리면 근접한 데이터를 보이기',
- 'Snapshot succeeded': 'Snapshot 성공',
- 'Sorry, there was a problem downloading your snapshot!': '죄송합니다, snapshot을 다운로드 중 문제가 발생했습니다!',
- 'Taking snapshot - this may take a few seconds': 'snapshot 찍기 - 수 초가 걸릴 수 있습니다',
+ 'Image download succeeded': 'Snapshot 성공',
+ 'Sorry, there was a problem downloading your image!': '죄송합니다, snapshot을 다운로드 중 문제가 발생했습니다!',
+ 'Capturing image - this may take a few seconds': 'snapshot 찍기 - 수 초가 걸릴 수 있습니다',
'Toggle Spike Lines': '스위치로 Lines을 고정합니다',
'Toggle show closest data on hover': '스위치로 마우스를 올렸을 때 가장 가까운 데이터를 보여줍니다',
'Turntable rotation': 'Turntable 회전',
diff --git a/lib/locales/no.js b/lib/locales/no.js
index 7956d7c2352..801b48822cb 100644
--- a/lib/locales/no.js
+++ b/lib/locales/no.js
@@ -32,9 +32,9 @@ module.exports = {
'Reset view': 'Nullstille visning', // components/modebar/buttons.js:512
'Reset views': 'Nullstille visninger', // components/modebar/buttons.js:550
'Show closest data on hover': 'Vis nærmeste verdi når musepekeren holdes over', // components/modebar/buttons.js:166
- 'Snapshot succeeded': 'Bilde Laget', // components/modebar/buttons.js:75
- 'Sorry, there was a problem downloading your snapshot!': 'Beklager, noe gikk galt under nedlasting av bildet', // components/modebar/buttons.js:78
- 'Taking snapshot - this may take a few seconds': 'Oppretter bilde - dette kan ta noen sekunder', // components/modebar/buttons.js:60
+ 'Image download succeeded': 'Bilde Laget', // components/modebar/buttons.js:75
+ 'Sorry, there was a problem downloading your image!': 'Beklager, noe gikk galt under nedlasting av bildet', // components/modebar/buttons.js:78
+ 'Capturing image - this may take a few seconds': 'Oppretter bilde - dette kan ta noen sekunder', // components/modebar/buttons.js:60
'Toggle Spike Lines': 'Aktiver / deaktiver topplinjer', // components/modebar/buttons.js:569
'Toggle show closest data on hover': 'Aktiver / deaktiver nærmeste verdi når musepekeren holdes over', // components/modebar/buttons.js:361
'Turntable rotation': 'Flat rotation', // components/modebar/buttons.js:290
diff --git a/lib/locales/pt-br.js b/lib/locales/pt-br.js
index 9a85104b062..9a6471222e3 100644
--- a/lib/locales/pt-br.js
+++ b/lib/locales/pt-br.js
@@ -32,9 +32,9 @@ module.exports = {
'Reset view': 'Restaurar visão',
'Reset views': 'Restaurar visões',
'Show closest data on hover': 'Exibir dado mais próximo ao pairar',
- 'Snapshot succeeded': 'Captura instantânea completa',
- 'Sorry, there was a problem downloading your snapshot!': 'Desculpe, houve um problema no download de sua captura instantânea!',
- 'Taking snapshot - this may take a few seconds': 'Efetuando captura instantânea - isso pode levar alguns instantes',
+ 'Image download succeeded': 'Download da imagem concluído com sucesso',
+ 'Sorry, there was a problem downloading your image!': 'Desculpe, houve um problema no download de sua imagem!',
+ 'Capturing image - this may take a few seconds': 'Efetuando captura da imagem - isso pode levar alguns instantes',
'Toggle Spike Lines': 'Habilitar/desabilitar triangulação de linhas',
'Toggle show closest data on hover': 'Habilitar/desabilitar exibição de dado mais próximo ao pairar',
'Turntable rotation': 'Rotação de mesa',
diff --git a/lib/locales/pt-pt.js b/lib/locales/pt-pt.js
index 042766ed0e1..50cf6feb4a8 100644
--- a/lib/locales/pt-pt.js
+++ b/lib/locales/pt-pt.js
@@ -32,9 +32,9 @@ module.exports = {
'Reset view': 'Restaurar vista',
'Reset views': 'Restaurar vistas',
'Show closest data on hover': 'Exibir dado mais próximo ao pairar',
- 'Snapshot succeeded': 'Captura instantânea com sucesso',
- 'Sorry, there was a problem downloading your snapshot!': 'Desculpe, houve um problema no download de sua captura instantânea!',
- 'Taking snapshot - this may take a few seconds': 'Efetuando captura instantânea - isso pode demorar alguns segundos',
+ 'Image download succeeded': 'Download da imagem concluído com sucesso',
+ 'Sorry, there was a problem downloading your image!': 'Desculpe, houve um problema no download da sua imagem!',
+ 'Capturing image - this may take a few seconds': 'Efetuando captura da imagem - isso pode demorar alguns segundos',
'Toggle Spike Lines': 'Habilitar/desabilitar triangulação de linhas',
'Toggle show closest data on hover': 'Habilitar/desabilitar exibição de dado mais próximo ao pairar',
'Turntable rotation': 'Rodar',
diff --git a/lib/locales/ro.js b/lib/locales/ro.js
index e77bc2fdd15..3937874a11c 100644
--- a/lib/locales/ro.js
+++ b/lib/locales/ro.js
@@ -37,9 +37,9 @@ module.exports = {
'Reset view': 'Resetează vizualizarea',
'Reset views': 'Resetează vizualizările',
'Show closest data on hover': 'Afișează cele mai apropiate date la trecerea cu mouse-ul',
- 'Snapshot succeeded': 'Crearea capturii de ecran a reușit',
- 'Sorry, there was a problem downloading your snapshot!': 'Ne pare rău, a apărut o eroare la descărcarea capturii de ecran!',
- 'Taking snapshot - this may take a few seconds': 'Se crează captura de ecran - poate dura câteva secunde',
+ 'Image download succeeded': 'Crearea capturii de ecran a reușit',
+ 'Sorry, there was a problem downloading your image!': 'Ne pare rău, a apărut o eroare la descărcarea capturii de ecran!',
+ 'Capturing image - this may take a few seconds': 'Se crează captura de ecran - poate dura câteva secunde',
'Toggle Spike Lines': 'Comutarea afișării liniilor de vârf',
'Toggle show closest data on hover': 'Comutarea afișării celor mai apropiate date',
'Turntable rotation': 'Rotație pe axă',
diff --git a/lib/locales/ru.js b/lib/locales/ru.js
index 00fff8a4c49..6350d49066d 100644
--- a/lib/locales/ru.js
+++ b/lib/locales/ru.js
@@ -32,9 +32,9 @@ module.exports = {
'Reset view': 'Сбросить отображение к значениям по умолчанию',
'Reset views': 'Сбросить отображения к значениям по умолчанию',
'Show closest data on hover': 'При наведении показывать ближайшие данные',
- 'Snapshot succeeded': 'Снимок успешно создан',
- 'Sorry, there was a problem downloading your snapshot!': 'К сожалению, возникла проблема при сохранении снимка',
- 'Taking snapshot - this may take a few seconds': 'Делается снимок - это может занять несколько секунд',
+ 'Image download succeeded': 'Снимок успешно создан',
+ 'Sorry, there was a problem downloading your image!': 'К сожалению, возникла проблема при сохранении снимка',
+ 'Capturing image - this may take a few seconds': 'Делается снимок - это может занять несколько секунд',
'Toggle Spike Lines': 'Включить/выключить отображение линий проекций точек',
'Toggle show closest data on hover': 'Включить/выключить показ ближайших данных при наведении',
'Turntable rotation': 'Вращение на поворотном столе',
diff --git a/lib/locales/si.js b/lib/locales/si.js
index 1289bef58a9..f81667e4f6d 100644
--- a/lib/locales/si.js
+++ b/lib/locales/si.js
@@ -38,9 +38,9 @@ module.exports = {
'Reset view': 'දැක්ම යළි සකසන්න', // components/modebar/buttons.js:599
'Reset views': 'දැක්ම් යළි සකසන්න', // components/modebar/buttons.js:637
'Show closest data on hover': 'සුනංගු කිරීමේදී ආසන්නම දත්ත පෙන්වන්න', // components/modebar/buttons.js:228
- 'Snapshot succeeded': 'ඡායාරූපය සාර්ථකයි', // components/modebar/buttons.js:67
- 'Sorry, there was a problem downloading your snapshot!': 'සමාවන්න, ඔබගේ ඡායාරූපය බාගැනීමේ ගැටලුවක් ඇත!', // components/modebar/buttons.js:70
- 'Taking snapshot - this may take a few seconds': 'ඡායාරූපය ගැනෙමින් - මෙයට තත්. කිහිපයක් ගතවිය හැකිය', // components/modebar/buttons.js:52
+ 'Image download succeeded': 'ඡායාරූපය සාර්ථකයි', // components/modebar/buttons.js:67
+ 'Sorry, there was a problem downloading your image!': 'සමාවන්න, ඔබගේ ඡායාරූපය බාගැනීමේ ගැටලුවක් ඇත!', // components/modebar/buttons.js:70
+ 'Capturing image - this may take a few seconds': 'ඡායාරූපය ගැනෙමින් - මෙයට තත්. කිහිපයක් ගතවිය හැකිය', // components/modebar/buttons.js:52
'Toggle Spike Lines': 'Toggle Spike Lines', // components/modebar/buttons.js:656
'Toggle show closest data on hover': 'Toggle show closest data on hover', // components/modebar/buttons.js:440
'Turntable rotation': 'බමර කරකැවීම', // components/modebar/buttons.js:351
diff --git a/lib/locales/sk.js b/lib/locales/sk.js
index 52360965110..ef5e20c4c16 100644
--- a/lib/locales/sk.js
+++ b/lib/locales/sk.js
@@ -32,9 +32,9 @@ module.exports = {
'Reset view': 'Obnoviť nastavenie pohľadu', // components/modebar/buttons.js:583
'Reset views': 'Obnoviť nastavenie pohľadov', // components/modebar/buttons.js:529
'Show closest data on hover': 'Zobraziť najbližšiu hodnotu při prejdení myšou', // components/modebar/buttons.js:157
- 'Snapshot succeeded': 'Obrázok vytvorený', // components/modebar/buttons.js:66
- 'Sorry, there was a problem downloading your snapshot!': 'Ospravedlňujeme sa, došlo k chybe pri sťahovaní obrázka!', // components/modebar/buttons.js:69
- 'Taking snapshot - this may take a few seconds': 'Snímanie - môže trvať niekoľko sekúnd', // components/modebar/buttons.js:57
+ 'Image download succeeded': 'Obrázok vytvorený', // components/modebar/buttons.js:66
+ 'Sorry, there was a problem downloading your image!': 'Ospravedlňujeme sa, došlo k chybe pri sťahovaní obrázka!', // components/modebar/buttons.js:69
+ 'Capturing image - this may take a few seconds': 'Snímanie - môže trvať niekoľko sekúnd', // components/modebar/buttons.js:57
'Zoom': 'Zväčšenie', // components/modebar/buttons.js:85
'Zoom in': 'Zväčšiť', // components/modebar/buttons.js:121
'Zoom out': 'Zmenšiť', // components/modebar/buttons.js:130
diff --git a/lib/locales/sv.js b/lib/locales/sv.js
index f3b215a4996..4d9c355f92a 100644
--- a/lib/locales/sv.js
+++ b/lib/locales/sv.js
@@ -32,9 +32,9 @@ module.exports = {
'Reset view': 'Återställ vy', // components/modebar/buttons.js:512
'Reset views': 'Återställ vyer', // components/modebar/buttons.js:550
'Show closest data on hover': 'Visa närmaste värde när muspekaren hålls över', // components/modebar/buttons.js:166
- 'Snapshot succeeded': 'Bild skapad', // components/modebar/buttons.js:75
- 'Sorry, there was a problem downloading your snapshot!': 'Tyvärr gick något fel vid nedladdning av bild', // components/modebar/buttons.js:78
- 'Taking snapshot - this may take a few seconds': 'Skapar bild - detta kan ta några sekunder', // components/modebar/buttons.js:60
+ 'Image download succeeded': 'Bild skapad', // components/modebar/buttons.js:75
+ 'Sorry, there was a problem downloading your image!': 'Tyvärr gick något fel vid nedladdning av bild', // components/modebar/buttons.js:78
+ 'Capturing image - this may take a few seconds': 'Skapar bild - detta kan ta några sekunder', // components/modebar/buttons.js:60
'Toggle Spike Lines': 'Aktivera/Inaktivera topplinjer', // components/modebar/buttons.js:569
'Toggle show closest data on hover': 'Aktivera/Inaktivera visa närmaste värde när muspekaren hålls över', // components/modebar/buttons.js:361
'Turntable rotation': 'Platt rotation', // components/modebar/buttons.js:290
diff --git a/lib/locales/sw.js b/lib/locales/sw.js
index 6d173e19b80..70f94eae51a 100644
--- a/lib/locales/sw.js
+++ b/lib/locales/sw.js
@@ -32,9 +32,9 @@ module.exports = {
'Reset view': 'Weka upya mtazamo',
'Reset views': 'Weka upya maoni',
'Show closest data on hover': 'Onyesha data iliyo karibu zaidi kielekezi kinapoelea',
- 'Snapshot succeeded': 'Snapshot ilifanikiwa',
- 'Sorry, there was a problem downloading your snapshot!': 'Samahani, kulikuwa na shida kupakua picha yako!',
- 'Taking snapshot - this may take a few seconds': 'Kuchukua snapshot - hii inaweza kuchukua sekunde chache',
+ 'Image download succeeded': 'Snapshot ilifanikiwa',
+ 'Sorry, there was a problem downloading your image!': 'Samahani, kulikuwa na shida kupakua picha yako!',
+ 'Capturing image - this may take a few seconds': 'Kuchukua snapshot - hii inaweza kuchukua sekunde chache',
'Toggle Spike Lines': 'Badilisha Mistari ya Spike',
'Toggle show closest data on hover': 'Badilisha mabadiliko ya karibu zaidi kwenye hover',
'Turntable rotation': 'Zunguka kwa mhimili wa Z',
diff --git a/lib/locales/tr.js b/lib/locales/tr.js
index 7accb25bcd5..3bdf679696f 100644
--- a/lib/locales/tr.js
+++ b/lib/locales/tr.js
@@ -31,9 +31,9 @@ module.exports = {
'Reset view': 'Görünümü sıfırla',
'Reset views': 'Görünümleri sıfırla',
'Show closest data on hover': 'Üzerine gelince en yakın veriyi göster',
- 'Snapshot succeeded': 'Anlık görüntü alındı',
- 'Sorry, there was a problem downloading your snapshot!': 'Üzgünüz, anlık görüntünüz indirilirken bir sorun oluştu!',
- 'Taking snapshot - this may take a few seconds': 'Anlık görüntü alınıyor - bu işlem birkaç saniye sürebilir',
+ 'Image download succeeded': 'Anlık görüntü alındı',
+ 'Sorry, there was a problem downloading your image!': 'Üzgünüz, anlık görüntünüz indirilirken bir sorun oluştu!',
+ 'Capturing image - this may take a few seconds': 'Anlık görüntü alınıyor - bu işlem birkaç saniye sürebilir',
'Zoom': 'Yakınlaştır',
'Zoom in': 'Yakınlaş',
'Zoom out': 'Uzaklaş',
diff --git a/lib/locales/uk.js b/lib/locales/uk.js
index 635bd202563..c39a8dd15e9 100644
--- a/lib/locales/uk.js
+++ b/lib/locales/uk.js
@@ -32,9 +32,9 @@ module.exports = {
'Reset view': 'Встановити відображенню значення за замовчуванням',
'Reset views': 'Встановити відображенням значення за замовчуванням',
'Show closest data on hover': 'При наведенні показувати найближчі дані',
- 'Snapshot succeeded': 'Знімок успішно створений',
- 'Sorry, there was a problem downloading your snapshot!': 'На жаль, виникла проблема при збереженні знімку',
- 'Taking snapshot - this may take a few seconds': 'Створюється знімок - це може зайняти кілька секунд',
+ 'Image download succeeded': 'Знімок успішно створений',
+ 'Sorry, there was a problem downloading your image!': 'На жаль, виникла проблема при збереженні знімку',
+ 'Capturing image - this may take a few seconds': 'Створюється знімок - це може зайняти кілька секунд',
'Toggle Spike Lines': 'Увімкнути/вимкнути відображення ліній проекцій точок',
'Toggle show closest data on hover': 'Увімкнути/вимкнути відображення найближчих даних при наведенні',
'Turntable rotation': 'Обертання на поворотному столі',
diff --git a/lib/locales/zh-cn.js b/lib/locales/zh-cn.js
index 7252e623a03..48348afa5a6 100644
--- a/lib/locales/zh-cn.js
+++ b/lib/locales/zh-cn.js
@@ -31,9 +31,9 @@ module.exports = {
'Reset view': '重置视图', // components/modebar/buttons.js:583
'Reset views': '重置视图', // components/modebar/buttons.js:529
'Show closest data on hover': '悬停时显示最近的数据', // components/modebar/buttons.js:157
- 'Snapshot succeeded': '生成快照成功', // components/modebar/buttons.js:66
- 'Sorry, there was a problem downloading your snapshot!': '抱歉,下载快照出现问题!', // components/modebar/buttons.js:69
- 'Taking snapshot - this may take a few seconds': '正在生成快照 - 可能需要几秒钟', // components/modebar/buttons.js:57
+ 'Image download succeeded': '生成快照成功', // components/modebar/buttons.js:66
+ 'Sorry, there was a problem downloading your image!': '抱歉,下载快照出现问题!', // components/modebar/buttons.js:69
+ 'Capturing image - this may take a few seconds': '正在生成快照 - 可能需要几秒钟', // components/modebar/buttons.js:57
'Zoom': '缩放', // components/modebar/buttons.js:85
'Zoom in': '放大', // components/modebar/buttons.js:121
'Zoom out': '缩小', // components/modebar/buttons.js:130
diff --git a/lib/locales/zh-tw.js b/lib/locales/zh-tw.js
index 02a29ffc9c9..81f5d209efd 100644
--- a/lib/locales/zh-tw.js
+++ b/lib/locales/zh-tw.js
@@ -38,9 +38,9 @@ module.exports = {
'Reset view': '重置視圖', // components/modebar/buttons.js:583
'Reset views': '重置視圖', // components/modebar/buttons.js:529
'Show closest data on hover': '游標停留時顯示最接近的資料', // components/modebar/buttons.js:157
- 'Snapshot succeeded': '快照成功', // components/modebar/buttons.js:66
- 'Sorry, there was a problem downloading your snapshot!': '抱歉,下載快照時發生錯誤!', // components/modebar/buttons.js:69
- 'Taking snapshot - this may take a few seconds': '產生快照中 - 可能需要一點時間', // components/modebar/buttons.js:57
+ 'Image download succeeded': '快照成功', // components/modebar/buttons.js:66
+ 'Sorry, there was a problem downloading your image!': '抱歉,下載快照時發生錯誤!', // components/modebar/buttons.js:69
+ 'Capturing image - this may take a few seconds': '產生快照中 - 可能需要一點時間', // components/modebar/buttons.js:57
'Zoom': '縮放', // components/modebar/buttons.js:85
'Zoom in': '放大', // components/modebar/buttons.js:121
'Zoom out': '縮小', // components/modebar/buttons.js:130
diff --git a/src/components/modebar/buttons.js b/src/components/modebar/buttons.js
index 09b0dc0003d..0de11f3e9ec 100644
--- a/src/components/modebar/buttons.js
+++ b/src/components/modebar/buttons.js
@@ -49,7 +49,7 @@ modeBarButtons.toImage = {
var toImageButtonOptions = gd._context.toImageButtonOptions;
var opts = {format: toImageButtonOptions.format || 'png'};
- Lib.notifier(_(gd, 'Taking snapshot - this may take a few seconds'), 'long', gd);
+ Lib.notifier(_(gd, 'Capturing image - this may take a few seconds'), 'long', gd);
['filename', 'width', 'height', 'scale'].forEach(function(key) {
if(key in toImageButtonOptions) {
@@ -59,10 +59,10 @@ modeBarButtons.toImage = {
Registry.call('downloadImage', gd, opts)
.then(function(filename) {
- Lib.notifier(_(gd, 'Snapshot succeeded') + ' - ' + filename, 'long', gd);
+ Lib.notifier(_(gd, 'Image download succeeded') + ' - ' + filename, 'long', gd);
})
.catch(function() {
- Lib.notifier(_(gd, 'Sorry, there was a problem downloading your snapshot!'), 'long', gd);
+ Lib.notifier(_(gd, 'Sorry, there was a problem downloading your image!'), 'long', gd);
});
}
};
diff --git a/src/lib/index.js b/src/lib/index.js
index d898a526140..7685064bf14 100644
--- a/src/lib/index.js
+++ b/src/lib/index.js
@@ -208,6 +208,8 @@ lib.increment = require('./increment');
lib.cleanNumber = require('./clean_number');
+lib.slugify = require('./slugify');
+
lib.ensureNumber = function ensureNumber(v) {
if (!isNumeric(v)) return BADNUM;
v = Number(v);
diff --git a/src/lib/slugify.js b/src/lib/slugify.js
new file mode 100644
index 00000000000..32342629717
--- /dev/null
+++ b/src/lib/slugify.js
@@ -0,0 +1,56 @@
+'use strict';
+
+// precompile for speed
+var HTML_TAGS_REGEX = /<[^>]*>/g; // anything contained in < > tags
+var FORBIDDEN_CHARS_REGEX = /[\\/:*?"<>|$%&!@#~.^`'(){}[\],]/g; // Characters in the set: \/:*?"<>|$%&!@#~.^`'(){}[],
+var CONTROL_CHARS_REGEX = /\p{Cc}/gu; // Unicode control characters
+
+var UNICODE_REPLACEMENT_CHAR_REGEX = /�/g; // U+FFFD, the Unicode replacement character
+var WHITESPACE_REGEX = /\s+/g;
+
+var WORD_SEP_CHAR = '-'; // character used to separate words (replaces whitespace)
+var _WORD_SEP_ESCAPED = WORD_SEP_CHAR.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+var WORD_SEP_CHARS_REGEX = new RegExp(_WORD_SEP_ESCAPED + '{3,}', 'g'); // three or more consecutive word separator chars
+var TRAILING_WORD_SEP_CHAR_REGEX = new RegExp(_WORD_SEP_ESCAPED + '$', 'g'); // trailing word separator char
+
+// Safely under the limit for most filesystems
+var DEFAULT_MAX_LEN = 60;
+
+/**
+ * Coerce a string to well-formed UTF-16, by replacing any unpaired surrogates
+ * with the replacement character U+FFFD.
+ *
+ * Uses the native String.prototype.toWellFormed (ES2024) when available, and
+ * otherwise falls back to TextEncoder/TextDecoder, which has the same effect.
+ */
+function toWellFormed(str) {
+ if(typeof str.toWellFormed === 'function') return str.toWellFormed();
+ return new TextDecoder().decode(new TextEncoder().encode(str));
+}
+
+/**
+ * slugify: turn an arbitrary string into a lowercase, hyphenated,
+ * filesystem-safe token (e.g. for use as a filename). Whitespace is replaced
+ * with hyphens, and Unicode letters (accents, CJK, etc.) are preserved.
+ * Returns a valid Unicode string.
+ *
+ * @param {string} str
+ * @param {number} [maxLen] max length in code points (default 60)
+ * @return {string}
+ */
+module.exports = function slugify(str, maxLen = DEFAULT_MAX_LEN) {
+ var slug = toWellFormed(str ?? '') // Guarantee well-formed Unicode text
+ .replace(UNICODE_REPLACEMENT_CHAR_REGEX, '') // Drop Unicode replacement chars left by previous step
+ .replace(HTML_TAGS_REGEX, ' ') // Remove < > tags, such as
(replace with space)
+ .replace(FORBIDDEN_CHARS_REGEX, '') // Remove forbidden filename characters
+ .toLowerCase() // Lowercase everything
+ .trim() // Strip leading/trailing whitespace
+ .replace(WHITESPACE_REGEX, WORD_SEP_CHAR) // Replace any remaining whitespace with the word sep char
+ .replace(CONTROL_CHARS_REGEX, '') // Remove control characters (after whitespace)
+ .replace(WORD_SEP_CHARS_REGEX, WORD_SEP_CHAR); // Replace multiple word sep chars with a single one
+
+ if (slug.length <= maxLen) return slug;
+ // Apply maxLen to the resulting string. Use Array.from().slice() instead of String.prototype.split()
+ // to avoid splitting in the middle of a surrogate pair.
+ return Array.from(slug).slice(0, maxLen).join('').replace(TRAILING_WORD_SEP_CHAR_REGEX, '');
+};
diff --git a/src/lib/svg_text_utils.js b/src/lib/svg_text_utils.js
index 0b37345312b..7850a3c889e 100644
--- a/src/lib/svg_text_utils.js
+++ b/src/lib/svg_text_utils.js
@@ -13,6 +13,20 @@ var LINE_SPACING = require('../constants/alignment').LINE_SPACING;
var FIND_TEX = /([^$]*)([$]+[^$]*[$]+)([^$]*)/;
+/**
+* Checks whether the given string contains LaTeX markup
+* (delimited by a pair of $ signs) and returns the match array if so.
+*
+* @param {string} str: the string to check for tex
+* @return {?Array} the regex match array (truthy) if the string contains tex,
+* otherwise null (for an empty/missing string or when no tex delimiters are found).
+*/
+function matchTex(str) {
+ if (!str) return null;
+ return str.match(FIND_TEX);
+};
+exports.matchTex = matchTex;
+
exports.convertToTspans = function(_context, gd, _callback) {
var str = _context.text();
@@ -21,7 +35,7 @@ exports.convertToTspans = function(_context, gd, _callback) {
var tex = (!_context.attr('data-notex')) &&
gd && gd._context.typesetMath &&
(typeof MathJax !== 'undefined') &&
- str.match(FIND_TEX);
+ matchTex(str);
var parent = d3.select(_context.node().parentNode);
if(parent.empty()) return;
diff --git a/src/snapshot/download.js b/src/snapshot/download.js
index 95c97fe7441..292f7075950 100644
--- a/src/snapshot/download.js
+++ b/src/snapshot/download.js
@@ -1,6 +1,7 @@
'use strict';
var Lib = require('../lib');
+var svgTextUtils = require('../lib/svg_text_utils');
var toImage = require('../plot_api/to_image');
@@ -35,7 +36,17 @@ function downloadImage(gd, opts) {
if(_gd) _gd._snapshotInProgress = true;
var promise = toImage(gd, opts);
- var filename = opts.filename || gd.fn || 'newplot';
+ var potentialFilename = opts.filename || gd.fn;
+ if (!potentialFilename) {
+ const plotTitle = helpers.getPlotTitle(gd);
+ // Trying to slugify a LaTeX string can result in weird ugly filenames,
+ // so ignore the title entirely if it contains LaTeX markup
+ if (!svgTextUtils.matchTex(plotTitle)) {
+ potentialFilename = Lib.slugify(plotTitle, 40);
+ }
+ }
+
+ var filename = potentialFilename || 'plot-image';
filename += '.' + opts.format.replace('-', '.');
promise.then(function(result) {
diff --git a/src/snapshot/helpers.js b/src/snapshot/helpers.js
index 3f50eb1a2d2..98b5d03c911 100644
--- a/src/snapshot/helpers.js
+++ b/src/snapshot/helpers.js
@@ -51,6 +51,23 @@ exports.octetStream = function(s) {
document.location.href = 'data:application/octet-stream' + s;
};
+/**
+ * Get the resolved plot title to derive a filename from, or undefined if there
+ * is none. We try to read _fullLayout, which reflects the title after applying
+ * layout.template, but since title.text falls back to the editable-mode placeholder
+ * when unset, a value equal to that placeholder is treated as no title.
+ * For an un-rendered figure object (no _fullLayout) we fall back to the input layout
+ * (gd.layout).
+ */
+exports.getPlotTitle = function(gd) {
+ var fullLayout = gd._fullLayout;
+ if(fullLayout) {
+ var title = fullLayout.title?.text;
+ return title === fullLayout._dfltTitle?.plot ? undefined : title;
+ }
+ return gd.layout?.title?.text;
+};
+
// Taken from https://bl.ocks.org/nolanlawson/0eac306e4dac2114c752
function fixBinary(b) {
var len = b.length;
diff --git a/test/jasmine/tests/download_test.js b/test/jasmine/tests/download_test.js
index 02466695809..a0b7df9f6e9 100644
--- a/test/jasmine/tests/download_test.js
+++ b/test/jasmine/tests/download_test.js
@@ -86,6 +86,75 @@ describe('Plotly.downloadImage', function() {
})
.then(done, done.fail);
}, LONG_TIMEOUT_INTERVAL);
+
+ describe('default filename (derived from the plot title)', function() {
+ // download with no explicit `filename`, so the name is derived from the title
+ function downloadDefault(layout) {
+ return Plotly.newPlot(gd, [{y: [1, 2, 1]}], layout).then(function() {
+ return Plotly.downloadImage(gd, {format: 'png', height: 300, width: 300});
+ });
+ }
+
+ it('slugifies the plot title into the filename', function(done) {
+ downloadDefault({title: {text: 'My Awesome Plot'}})
+ .then(function(filename) {
+ expect(filename).toBe('my-awesome-plot.png');
+ })
+ .then(done, done.fail);
+ }, LONG_TIMEOUT_INTERVAL);
+
+ it('uses a title supplied via layout.template', function(done) {
+ // the template title only appears in _fullLayout, not the input layout
+ downloadDefault({template: {layout: {title: {text: 'Title From Template'}}}})
+ .then(function(filename) {
+ expect(filename).toBe('title-from-template.png');
+ })
+ .then(done, done.fail);
+ }, LONG_TIMEOUT_INTERVAL);
+
+ it('strips forbidden characters from the title', function(done) {
+ downloadDefault({title: {text: 'Revenue, Costs & "Profit" (2024)'}})
+ .then(function(filename) {
+ expect(filename).toBe('revenue-costs-profit-2024.png');
+ })
+ .then(done, done.fail);
+ }, LONG_TIMEOUT_INTERVAL);
+
+ it('caps the title-derived name at 40 code points', function(done) {
+ downloadDefault({title: {text: 'A Very Long Plot Title That Exceeds The Forty Character Maximum Limit'}})
+ .then(function(filename) {
+ expect(filename).toBe('a-very-long-plot-title-that-exceeds-the.png');
+ })
+ .then(done, done.fail);
+ }, LONG_TIMEOUT_INTERVAL);
+
+ it('falls back to plot-image as filename when there is no title', function(done) {
+ downloadDefault({})
+ .then(function(filename) {
+ expect(filename).toBe('plot-image.png');
+ })
+ .then(done, done.fail);
+ }, LONG_TIMEOUT_INTERVAL);
+
+ it('does not use the editable-mode placeholder as the filename', function(done) {
+ // _fullLayout.title.text defaults to the "Click to enter Plot title"
+ // placeholder; the filename must come from the input layout instead
+ downloadDefault({})
+ .then(function(filename) {
+ expect(gd._fullLayout.title.text).toBe('Click to enter Plot title');
+ expect(filename).toBe('plot-image.png');
+ })
+ .then(done, done.fail);
+ }, LONG_TIMEOUT_INTERVAL);
+
+ it('ignores title if it contains LaTeX markup', function(done) {
+ downloadDefault({title: {text: '$\\alpha$ + $\\beta$'}})
+ .then(function(filename) {
+ expect(filename).toBe('plot-image.png');
+ })
+ .then(done, done.fail);
+ }, LONG_TIMEOUT_INTERVAL);
+ });
});
function downloadTest(gd, format) {
diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js
index fe1f6b71f0c..ecbffad2774 100644
--- a/test/jasmine/tests/lib_test.js
+++ b/test/jasmine/tests/lib_test.js
@@ -1941,6 +1941,59 @@ describe('Test lib.js:', function () {
});
});
+ describe('slugify', function () {
+ it('lowercases, trims, and hyphenates whitespace', function () {
+ expect(Lib.slugify(' Hello World ')).toBe('hello-world');
+ expect(Lib.slugify('Multiple Spaces\tand\ntabs')).toBe('multiple-spaces-and-tabs');
+ });
+
+ it('strips html/pseudo-html tags', function () {
+ expect(Lib.slugify('Revenue by year')).toBe('revenue-by-year');
+ });
+
+ it('removes illegal filename characters', function () {
+ expect(Lib.slugify('a/b\\c:d*e?f"g|h$i%j&k!l@m#n~o.p^q`r\'s,t'))
+ .toBe('abcdefghijklmnopqrst');
+ expect(Lib.slugify('a>b