Skip to content

Commit 7e075f5

Browse files
committed
localization routine and tests
1 parent 4d8cca4 commit 7e075f5

38 files changed

+351
-73
lines changed

src/lib/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ lib.clearThrottle = throttleModule.clear;
106106

107107
lib.getGraphDiv = require('./get_graph_div');
108108

109+
lib._ = require('./localize');
110+
109111
lib.notifier = require('./notifier');
110112

111113
lib.filterUnique = require('./filter_unique');

src/lib/localize.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* Copyright 2012-2017, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
10+
'use strict';
11+
12+
var Registry = require('../registry');
13+
14+
/**
15+
* localize: translate a string for the current locale
16+
*
17+
* @param {object} gd: the graphDiv for context
18+
* gd._context.locale determines the language (& optional region/country)
19+
* the dictionary for each locale may either be supplied in
20+
* gd._context.dictionaries or globally via Plotly.register
21+
* @param {string} s: the string to translate
22+
*/
23+
module.exports = function localize(gd, s) {
24+
var locale = gd._context.locale;
25+
26+
/*
27+
* Priority of lookup:
28+
* contextDicts[locale],
29+
* registeredDicts[locale],
30+
* contextDicts[baseLocale], (if baseLocale is distinct)
31+
* registeredDicts[baseLocale]
32+
* Return the first translation we find.
33+
* This way if you have a regionalization you are allowed to specify
34+
* only what's different from the base locale, everything else will
35+
* fall back on the base.
36+
*/
37+
for(var i = 0; i < 2; i++) {
38+
var dicts = gd._context.dictionaries;
39+
for(var j = 0; j < 2; j++) {
40+
var dict = dicts[locale];
41+
if(dict) {
42+
var out = dict[s];
43+
if(out) return out;
44+
}
45+
dicts = Registry.localeRegistry;
46+
}
47+
48+
var baseLocale = locale.split('-')[0];
49+
if(baseLocale === locale) break;
50+
locale = baseLocale;
51+
}
52+
53+
return s;
54+
};

src/plot_api/plot_config.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,5 +126,19 @@ module.exports = {
126126

127127
// Set global transform to be applied to all traces with no
128128
// specification needed
129-
globalTransforms: []
129+
globalTransforms: [],
130+
131+
// Which localization should we use?
132+
// Should be a string like 'en' or 'en-US'.
133+
locale: 'en',
134+
135+
// Localization dictionaries
136+
// Dictionaries can be provided either here (specific to one chart) or globally
137+
// by registering them as modules.
138+
// Here `dictionaries` should be an object of objects
139+
// {'da': {'Reset axes': 'Nulstil aksler', ...}, ...}
140+
// When looking for a translation we look at these dictionaries first, then
141+
// the ones registered as modules. If those fail, we strip off any
142+
// regionalization ('en-US' -> 'en') and try each again
143+
dictionaries: {}
130144
};

src/plot_api/register.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ module.exports = function register(_modules) {
4040
registerComponentModule(newModule);
4141
break;
4242

43+
case 'locale':
44+
Registry.registerLocale(newModule);
45+
break;
46+
4347
default:
4448
throw new Error('Invalid module was attempted to be registered!');
4549
}

src/plot_api/validate.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
var Lib = require('../lib');
1414
var Plots = require('../plots/plots');
1515
var PlotSchema = require('./plot_schema');
16+
var dfltConfig = require('./plot_config');
1617

1718
var isPlainObject = Lib.isPlainObject;
1819
var isArray = Array.isArray;
@@ -40,9 +41,9 @@ var isArray = Array.isArray;
4041
* error message (shown in console in logger config argument is enable)
4142
*/
4243
module.exports = function valiate(data, layout) {
43-
var schema = PlotSchema.get(),
44-
errorList = [],
45-
gd = {};
44+
var schema = PlotSchema.get();
45+
var errorList = [];
46+
var gd = {_context: Lib.extendFlat({}, dfltConfig)};
4647

4748
var dataIn, layoutIn;
4849

src/registry.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ exports.componentsRegistry = {};
2828
exports.layoutArrayContainers = [];
2929
exports.layoutArrayRegexes = [];
3030
exports.traceLayoutAttributes = {};
31+
exports.localeRegistry = {};
3132

3233
/**
3334
* register a module as the handler for a trace type
@@ -311,3 +312,39 @@ function getTraceType(traceType) {
311312
if(typeof traceType === 'object') traceType = traceType.type;
312313
return traceType;
313314
}
315+
316+
/**
317+
* Register a new locale dictionary
318+
*
319+
* @param {object} module
320+
* @param {string} moduleType
321+
* should be 'locale' so that Plotly.register will forward to this function
322+
* @param {string} module.name
323+
* the locale name. Should be a 2-digit language string ('en', 'de')
324+
* optionally with a country/region code ('en-GB', 'de-CH'). If a country
325+
* code is used but the base language locale has not yet been supplied,
326+
* we will use this locale for the base as well.
327+
* @param {object} module.dictionary
328+
* the dictionary mapping input strings to localized strings
329+
* generally the keys should be the literal input strings, but
330+
* if default translations are provided you can use any string as a key.
331+
*/
332+
exports.registerLocale = function(_module) {
333+
var locale = _module.name;
334+
var baseLocale = locale.split('-')[0];
335+
336+
var newDict = _module.dictionary;
337+
338+
var locales = exports.localeRegistry;
339+
340+
// Should we use this dict for the base locale?
341+
// In case we're overwriting a previous dict for this locale, check
342+
// whether the base matches the full locale dict now. If we're not
343+
// overwriting, locales[locale] is undefined so this just checks if
344+
// baseLocale already had a dict or not.
345+
if(baseLocale !== locale && locales[baseLocale] === locales[locale]) {
346+
locales[baseLocale] = newDict;
347+
}
348+
349+
locales[locale] = newDict;
350+
};

test/jasmine/assets/modebar_button.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@ var modeBarButtons = require('../../../src/components/modebar/buttons');
66
module.exports = function selectButton(modeBar, name) {
77
var button = {};
88

9+
var title = modeBarButtons[name].title;
10+
11+
if(typeof title === 'function') {
12+
title = title(modeBar.graphInfo);
13+
}
14+
915
var node = button.node = d3.select(modeBar.element)
10-
.select('[data-title="' + modeBarButtons[name].title + '"]')
16+
.select('[data-title="' + title + '"]')
1117
.node();
1218

1319
button.click = function() {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
'use strict';
2+
3+
var Plots = require('@src/plots/plots');
4+
5+
/**
6+
* supplyDefaults that fills in necessary _context
7+
*/
8+
module.exports = function supplyDefaults(gd) {
9+
if(!gd._context) gd._context = {};
10+
if(!gd._context.locale) gd._context.locale = 'en';
11+
if(!gd._context.dictionaries) gd._context.dictionaries = {};
12+
13+
Plots.supplyDefaults(gd);
14+
};

test/jasmine/tests/axes_test.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ var createGraphDiv = require('../assets/create_graph_div');
1414
var destroyGraphDiv = require('../assets/destroy_graph_div');
1515
var failTest = require('../assets/fail_test');
1616
var selectButton = require('../assets/modebar_button');
17+
var supplyDefaults = require('../assets/supply_defaults');
1718

1819

1920
describe('Test axes', function() {
@@ -49,7 +50,7 @@ describe('Test axes', function() {
4950
type: 'date'
5051
};
5152

52-
Plots.supplyDefaults(gd);
53+
supplyDefaults(gd);
5354

5455
Axes.swap(gd, [0]);
5556

@@ -80,7 +81,7 @@ describe('Test axes', function() {
8081
expectedLayoutAfter.xaxis.type = 'linear';
8182
expectedLayoutAfter.yaxis.type = 'linear';
8283

83-
Plots.supplyDefaults(gd);
84+
supplyDefaults(gd);
8485

8586
Axes.swap(gd, [0]);
8687

@@ -160,7 +161,7 @@ describe('Test axes', function() {
160161
{x: 5, y: 0.5, xref: 'x', yref: 'paper'}
161162
];
162163

163-
Plots.supplyDefaults(gd);
164+
supplyDefaults(gd);
164165

165166
Axes.swap(gd, [0, 1]);
166167

@@ -177,7 +178,8 @@ describe('Test axes', function() {
177178
beforeEach(function() {
178179
layoutOut = {
179180
_has: Plots._hasPlotType,
180-
_basePlotModules: []
181+
_basePlotModules: [],
182+
_dfltTitle: {x: 'x', y: 'y'}
181183
};
182184
fullData = [];
183185
});

test/jasmine/tests/bar_test.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ var createGraphDiv = require('../assets/create_graph_div');
1111
var destroyGraphDiv = require('../assets/destroy_graph_div');
1212
var fail = require('../assets/fail_test');
1313
var checkTicks = require('../assets/custom_assertions').checkTicks;
14+
var supplyAllDefaults = require('../assets/supply_defaults');
1415

1516
var d3 = require('d3');
1617

@@ -1520,10 +1521,11 @@ function mockBarPlot(dataWithoutTraceType, layout) {
15201521
var gd = {
15211522
data: dataWithTraceType,
15221523
layout: layout || {},
1523-
calcdata: []
1524+
calcdata: [],
1525+
_context: {locale: 'en', dictionaries: {}}
15241526
};
15251527

1526-
Plots.supplyDefaults(gd);
1528+
supplyAllDefaults(gd);
15271529
Plots.doCalcdata(gd);
15281530

15291531
var plotinfo = {

test/jasmine/tests/carpet_test.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ var fail = require('../assets/fail_test');
1414
var mouseEvent = require('../assets/mouse_event');
1515
var assertHoverLabelContent = require('../assets/custom_assertions').assertHoverLabelContent;
1616

17+
var supplyAllDefaults = require('../assets/supply_defaults');
18+
1719
describe('carpet supplyDefaults', function() {
1820
'use strict';
1921

@@ -114,13 +116,13 @@ describe('carpet supplyDefaults', function() {
114116
describe('supplyDefaults visibility check', function() {
115117
it('does not hide empty subplots', function() {
116118
var gd = {data: [], layout: {xaxis: {}}};
117-
Plots.supplyDefaults(gd);
119+
supplyAllDefaults(gd);
118120
expect(gd._fullLayout.xaxis.visible).toBe(true);
119121
});
120122

121123
it('does not hide axes with non-carpet traces', function() {
122124
var gd = {data: [{x: []}]};
123-
Plots.supplyDefaults(gd);
125+
supplyAllDefaults(gd);
124126
expect(gd._fullLayout.xaxis.visible).toBe(true);
125127
});
126128

@@ -135,7 +137,7 @@ describe('supplyDefaults visibility check', function() {
135137
type: 'contourcarpet',
136138
z: [[1, 2, 3], [4, 5, 6]],
137139
}]};
138-
Plots.supplyDefaults(gd);
140+
supplyAllDefaults(gd);
139141
expect(gd._fullLayout.xaxis.visible).toBe(true);
140142
});
141143

@@ -149,7 +151,7 @@ describe('supplyDefaults visibility check', function() {
149151
type: 'contourcarpet',
150152
z: [[1, 2, 3], [4, 5, 6]],
151153
}]};
152-
Plots.supplyDefaults(gd);
154+
supplyAllDefaults(gd);
153155
expect(gd._fullLayout.xaxis.visible).toBe(false);
154156
});
155157

@@ -179,7 +181,7 @@ describe('supplyDefaults visibility check', function() {
179181
}]
180182
};
181183

182-
Plots.supplyDefaults(gd);
184+
supplyAllDefaults(gd);
183185
expect(gd._fullLayout.xaxis.visible).toBe(true);
184186
});
185187

@@ -209,7 +211,7 @@ describe('supplyDefaults visibility check', function() {
209211
}]
210212
};
211213

212-
Plots.supplyDefaults(gd);
214+
supplyAllDefaults(gd);
213215
expect(gd._fullLayout.xaxis.visible).toBe(true);
214216
});
215217
});

test/jasmine/tests/choropleth_test.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ describe('Test choropleth', function() {
2222

2323
var defaultColor = '#444',
2424
layout = {
25-
font: Plots.layoutAttributes.font
25+
font: Plots.layoutAttributes.font,
26+
_dfltTitle: {colorbar: 'cb'}
2627
};
2728

2829
beforeEach(function() {

test/jasmine/tests/colorscale_test.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,8 @@ describe('Test colorscale:', function() {
195195
describe('handleDefaults (heatmap-like version)', function() {
196196
var handleDefaults = Colorscale.handleDefaults,
197197
layout = {
198-
font: Plots.layoutAttributes.font
198+
font: Plots.layoutAttributes.font,
199+
_dfltTitle: {colorbar: 'cb'}
199200
},
200201
opts = {prefix: '', cLetter: 'z'};
201202
var traceIn, traceOut;
@@ -265,7 +266,8 @@ describe('Test colorscale:', function() {
265266
describe('handleDefaults (scatter-like version)', function() {
266267
var handleDefaults = Colorscale.handleDefaults,
267268
layout = {
268-
font: Plots.layoutAttributes.font
269+
font: Plots.layoutAttributes.font,
270+
_dfltTitle: {colorbar: 'cb'}
269271
},
270272
opts = {prefix: 'marker.', cLetter: 'c'};
271273
var traceIn, traceOut;

test/jasmine/tests/contour_test.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ var fail = require('../assets/fail_test');
1010
var createGraphDiv = require('../assets/create_graph_div');
1111
var destroyGraphDiv = require('../assets/destroy_graph_div');
1212
var checkTicks = require('../assets/custom_assertions').checkTicks;
13+
var supplyAllDefaults = require('../assets/supply_defaults');
1314

1415

1516
describe('contour defaults', function() {
@@ -20,7 +21,8 @@ describe('contour defaults', function() {
2021

2122
var defaultColor = '#444',
2223
layout = {
23-
font: Plots.layoutAttributes.font
24+
font: Plots.layoutAttributes.font,
25+
_dfltTitle: {colorbar: 'cb'}
2426
};
2527

2628
var supplyDefaults = Contour.supplyDefaults;
@@ -64,7 +66,7 @@ describe('contour defaults', function() {
6466
y: [1, 2],
6567
z: [[1, 2], [3, 4]]
6668
};
67-
supplyDefaults(traceIn, traceOut, defaultColor, {calendar: 'islamic'});
69+
supplyDefaults(traceIn, traceOut, defaultColor, Lib.extendFlat({calendar: 'islamic'}, layout));
6870

6971
// we always fill calendar attributes, because it's hard to tell if
7072
// we're on a date axis at this point.
@@ -80,7 +82,7 @@ describe('contour defaults', function() {
8082
xcalendar: 'coptic',
8183
ycalendar: 'ethiopian'
8284
};
83-
supplyDefaults(traceIn, traceOut, defaultColor, {calendar: 'islamic'});
85+
supplyDefaults(traceIn, traceOut, defaultColor, Lib.extendFlat({calendar: 'islamic'}, layout));
8486

8587
// we always fill calendar attributes, because it's hard to tell if
8688
// we're on a date axis at this point.
@@ -182,7 +184,7 @@ describe('contour calc', function() {
182184
trace = Lib.extendFlat({}, base, opts),
183185
gd = { data: [trace] };
184186

185-
Plots.supplyDefaults(gd);
187+
supplyAllDefaults(gd);
186188
var fullTrace = gd._fullData[0];
187189

188190
var out = Contour.calc(gd, fullTrace)[0];

0 commit comments

Comments
 (0)