Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Localization #2195

Merged
merged 22 commits into from
Dec 11, 2017
Merged

Localization #2195

merged 22 commits into from
Dec 11, 2017

Conversation

alexcjohnson
Copy link
Collaborator

  • Creates a framework for localization:

    • Dictionaries can either be registered (in the bundle or afterward) or provided to a plot as config
    • Locale to use must be provided as plot config
  • Wraps all displayed string literals in gd-specific localization function (did I miss any?)

  • Collects all translation key strings (on build or test) and barfs if any non-literals are translated

  • Does not localize date tick labels. This will be a separate PR, using the same config.locale but pulling world-calendars locale data in, so no additional translations will be needed there.

cc @bpostlethwaite @etpinard

@@ -47,31 +49,31 @@ var modeBarButtons = module.exports = {};

modeBarButtons.toImage = {
name: 'toImage',
title: 'Download plot as a png',
title: function(gd) { return _(gd, 'Download plot as a png'); },
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's kind of annoying to have to turn all these button titles into functions... just to follow the rule that you can only translate a literal string. In principle we could do this translation in ModeBar.createButton by adding some complexity to the find_locale_strings task to allow one non-literal translation and look for keys inside modebarButtons.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that way you have here is cleaner than more special logic to find_locale_strings 👌

@@ -296,7 +296,7 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) {
dragTail(zoomMode);

if(SHOWZOOMOUTTIP && gd.data && gd._context.showTips) {
Lib.notifier('Double-click to<br>zoom back out', 'long');
Lib.notifier(Lib._(gd, 'Double-click to<br>zoom back out'), 'long');
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems funny to include <br> in the translation - should we 🔪 that and maybe just set notifier max width in css? (it's a <div> so we can do that 😄 )

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we 🔪 that and maybe just set notifier max width in css?

That would be lovely 👌

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we 🔪 that and maybe just set notifier max width in css?

Actually we already have it, and as currently set we just break at the next word:
screen shot 2017-12-07 at 2 51 29 pm 2

I'm inclined to take the <br> out here and just leave it to break where it will. We can of course still use <br> in cases where it conveys some meaning, but this is not such a case.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<br> removed in 5d6f267

q1: _(gd, 'q1'),
q3: _(gd, 'q3'),
max: _(gd, 'max'),
mean: trace.boxmean === 'sd' ? _(gd, 'mean ± σ') : _(gd, 'mean'),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also not sure about making mean and mean ± σ separate translations... most languages will probably leave ± σ intact, but some might want to change it (Greek?)

@@ -82,21 +83,21 @@ module.exports = function calc(gd, trace) {

function noZsmooth(msg) {
zsmooth = trace._input.zsmooth = trace.zsmooth = false;
Lib.notifier('cannot fast-zsmooth: ' + msg);
Lib.notifier(_(gd, 'cannot use zsmooth: "fast"') + '<br>' + msg);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really want this (and the 3 msgs below) to be notified? Can I just turn it into a console warning and leave it in English?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I just turn it into a console warning and leave it in English?

I vote 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Lib.notifier should only be used for things that might affect the end user.

Lib.{log,warn,error} should suffice for messages that only help developers debug .

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved these to Lib.warn and turned log level 1 on by default in 0f8520e

see also #420 (comment)

var openName = _(gd, 'Open') + ': ';
var highName = _(gd, 'High') + ': ';
var lowName = _(gd, 'Low') + ': ';
var closeName = _(gd, 'Close') + ': ';
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should : be part of the translation? (Applies a bunch of other places as well)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose in principle one could make that argument about other punctuation as well (,, (, )...) but that seems like a can of worms we don't really want to open...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should : be part of the translation?

I think so. For example, in french it is customary to add a nbsp before :, ;, ! and ? e.g. Open: 10 would be Ouverture : 10.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved colons into the translation keys in bf2c6db

for old-style placeholder that contained the axis number
caught by test image 19
@etpinard
Copy link
Contributor

etpinard commented Dec 7, 2017

cc #856

Copy link
Contributor

@etpinard etpinard left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking great. I'm impressed with how little new code has been added 👌

I made a few comments. The most blocking one is in scattergeo.

@@ -47,31 +49,31 @@ var modeBarButtons = module.exports = {};

modeBarButtons.toImage = {
name: 'toImage',
title: 'Download plot as a png',
title: function(gd) { return _(gd, 'Download plot as a png'); },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that way you have here is cleaner than more special logic to find_locale_strings 👌


// Which localization should we use?
// Should be a string like 'en' or 'en-US'.
locale: 'en',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. I guess Click to enter Colorscale title would be Click to enter Colourscale title in non-US english. So should our default locale be en-US?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So should our default locale be en-US?

Ha sure - might be important for dates too. Then I can make and pre-register en and en-US dictionaries to override these appropriately.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default locale to 'en-US', with pre-registered dictionaries for this and 'en', in 04ed6f0.
This also conveniently documents the locale module format.

@@ -0,0 +1,60 @@
Autoscale
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick question: are we planning on having a set on official dictionaries or should non en-US dictionaries be entirely handled by users?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we planning on having a set on official dictionaries

They're not going to be very big, and seems really useful so I'd vote yes - with the caveat that they would probably mostly have to be contributed by users. We might have to make use of google translate during review, as a sanity check...

Also it occurs to me now, some of these keys might not be obvious out of context - perhaps I should comment each with the file & line it came from? Something like:

kde        src/traces/violin/calc.js:73

and then we could comment there with more information // kernel density estimate (maybe I could even put that comment into translation-keys.txt?)

@@ -296,7 +296,7 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) {
dragTail(zoomMode);

if(SHOWZOOMOUTTIP && gd.data && gd._context.showTips) {
Lib.notifier('Double-click to<br>zoom back out', 'long');
Lib.notifier(Lib._(gd, 'Double-click to<br>zoom back out'), 'long');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we 🔪 that and maybe just set notifier max width in css?

That would be lovely 👌

@@ -381,9 +381,6 @@ module.exports = function setConvert(ax, fullLayout) {
}

if(!isFinite(ax._m) || !isFinite(ax._b)) {
Lib.notifier(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me guess: you removed this so that we don't have to pass gd to setConvert ?

Regardless, I'm ok with making this just an (English-only) error 😉

@@ -82,21 +83,21 @@ module.exports = function calc(gd, trace) {

function noZsmooth(msg) {
zsmooth = trace._input.zsmooth = trace.zsmooth = false;
Lib.notifier('cannot fast-zsmooth: ' + msg);
Lib.notifier(_(gd, 'cannot use zsmooth: "fast"') + '<br>' + msg);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Lib.notifier should only be used for things that might affect the end user.

Lib.{log,warn,error} should suffice for messages that only help developers debug .

calcTrace[0].t = {
labels: {
lat: _(gd, 'lat') + ': ',
lon: _(gd, 'lon') + ': '
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an interesting case here. One could argue that labels lat and lon could be configured using a layout attribute e.g.

  geo: {
    lonaxis: {
       hoverprefix: 'longitude' 
    },
    lataxis: {
       hoverprefix: 'latitude'
     }
  }
}

So I ask: where do we draw the line between configurable hover axis labels and dictionaries?

cc #265

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to me if we were to allow configuring labels like this, it would work like the dfltTitle items - the translation result would be used as the default in supplyDefaults, but any other value provided with the figure gets used verbatim. We can't expect to translate user-provided strings, but for the most part it doesn't matter, usually translation is not about viewing other people's plots in your own language, but about making your own plot work and function the way you want it to.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to me if we were to allow configuring labels like this, it would work like the dfltTitle items

Good point. There's no downside to allowing lat:/lon: to be translated via dictionaries even if we add {lat,lon}axis.hoverprefix at some point.

var localizeRE = /(^|[\.])(_|localize)$/;

// main
findLocaleStrings();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lovely script 👌

/**
* supplyDefaults that fills in necessary _context
*/
module.exports = function supplyDefaults(gd) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

});

it('does not generate an automatic base language dictionary in context', function(done) {
plot('fr', {'fr-QC': {fries: 'poutine'}})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test case of the year 🏆

q3: // traces/box/calc.js:131
trace // plots/plots.js:439
upper fence: // traces/box/calc.js:135
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@etpinard @bpostlethwaite thoughts on this format, with the (first) source line for each key included for a translator to use for more context?

Also having all these strings together raises some questions about whether they're all actually what we want, or if any could be made more consistent:

  • Is the verb "Double-click" or "Double click"?
  • Most of the hover label names (the items ending in ':') are lowercase, but some (OHLC, sankey) are capitalized - is this what we want?
  • In making this PR I standardized the "Click to enter <item> title" ones to have <item> capitalized (previously one or two of them were lowercase) - is that correct?
  • Most of the Lib.notifier items have no punctuation but some of them do. Should we standardize?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thoughts on this format,

I like this format a lot. Nicely done 👌

Is the verb "Double-click" or "Double click"?

I have no preference.

Most of the hover label names (the items ending in ':') are lowercase, but some (OHLC, sankey) are capitalized - is this what we want?

I'd vote for standardizing to lower case.

I standardized the "Click to enter title" [...] is that correct?

fine by me.

Most of the Lib.notifier items have no punctuation but some of them do. Should we standardize?

No preference.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made all the hover labels lower case, standardized on "Double-click" (when used as a verb, which is the only way we use it in plotly.js text), and tweaked a few other messages -> 97821e9

so geo_choropleth-text doesn't barf on its unrecognized locations
@@ -4,6 +4,9 @@
<!-- this index file gets copied in to the image server docker -->
<script type="text/javascript" src="../plotly.js/dist/extras/mathjax/MathJax.js?config=TeX-AMS-MML_SVG"></script>
<script type="text/javascript" src="../plotly.js/build/plotly.js" charset="utf-8"></script>
<script type="text/javascript">
Plotly.setPlotConfig({logging: 0});
</script>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@etpinard I added this so geo_choropleth-text would render with no console logs (which trip the image server to throw an error). That way the image server keeps the same definition of "error" as it had before. I'm kind of inclined though to either:

  1. Downgrade the Location with id *** does not have a matching topojson feature at this resolution. message from a Lib.warn to a Lib.log, or
  2. Remove the offending ids from that mock. It's a lot though: ARB, BHR, CEB, CSS, EAP, EAS, ECA, ECS, EMU, EUU, FCS, HIC, HKG, HPC, LAC, LCN, LDC, LIC, LMC, LMY, MEA, MIC, MLT, MNA, NAC, NOC, OEC, OED, OSS, SAS, SGP, SSA, SSF, SST, UMC, WLD

Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about both?

Downgrade the Location with id *** does not have a matching topojson feature at this resolution. message from a Lib.warn to a Lib.log,

Yes, that should certainly be Lib.log 👌

Remove the offending ids from that mock. It's a lot though: ARB, BHR, CEB, CSS, EAP, EAS, ECA, ECS, EMU, EUU, FCS, HIC, HKG, HPC, LAC, LCN, LDC, LIC, LMC, LMY, MEA, MIC, MLT, MNA, NAC, NOC, OEC, OED, OSS, SAS, SGP, SSA, SSF, SST, UMC, WLD

I'd vote for removing the offending ids from the mock, but adding a jasmine test spying on Lib.log (if not done already). This part here could be done in a separate PR.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alright, did both.

  • missing location downgraded to Lib.log 4b8f980 (we have tests of these messages, modified in that commit)
  • removed offending ids anyway 8b9e7d8

lib/locales.js Outdated

'use strict';

module.exports = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. I think registering only one local (en-US) in the dist bundles should suffice, but then again as only the spelling of colo(u)rscale differs between the two, including both en and en-US is fine by me 👌

@@ -15,4 +15,7 @@ Plotly.register([
require('./pie')
]);

// locales
Plotly.register(require('./locales.js'));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would better to put these Plotly.register calls in src/core.js so that we don't have to duplicate them?

That would also mean moving locale modules to a new src/locales/ directory, as we wouldn't want to require lib/ files from src/ right?

tasks/bundle.js Outdated
var outPath = path.join(constants.pathToDist, outName);
wrapLocale(file, outPath);
});
});
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@etpinard what do you think about this solution?

  • Turned the locales into json files
  • Explicitly loaded just 'en' and 'en-US' into the built bundles (ie I got rid of locales.js so that if/when we add more locales they will not automatically go into all the bundles) but we'll still put new locales into lib/ for use if you make your own bundle.
  • Built separate minified js files for each locale, so if you load via a script tag (from CDN or script) you can first load plotly.js and then in the next script tag load the locale you want.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turned the locales into json files

Requiring JSON files isn't ideal as described in #2195 (comment)

I'd vote for converting those lib/locale-??? files into plain js files.

so that if/when we add more locales they will not automatically go into all the bundles) but we'll still put new locales into lib/ for use if you make your own bundle.

great 👌

Built separate minified js files for each locale, so if you load via a script tag (from CDN or script) you can first load plotly.js and then in the next script tag load the locale you want.

Lovely ❤️ and by the looks of it out push-to-cdn script should just worktm.

Plotly.register(require('./locales.js'));
Plotly.register([
require('./locale-en.json'),
require('./locale-en-us.json')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ha. Webpack users won't like that. Webpack (v1 at least) needs a special loader to bundle JSON files unlike browserify. See #183

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, back to js 1275661

.pipe(fs.createWriteStream(pathToOutput))
.on('finish', function() {
logger(pathToOutput);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Smooth work with add-stream and info-stream. It would be nice to rewrite the addHeadersInDistFiles step in tasks/header.js using streams for 🐎

because it doesn't really seem to be an error, things work fine even when this is triggered
at least in updatemenus.json
@@ -114,7 +114,7 @@ exports.manageCommandObserver = function(gd, container, commandList, onchange) {
} else {
// TODO: It'd be really neat to actually give a *reason* for this, but at least a warning
// is a start
Lib.warn('Unable to automatically bind plot updates to API command');
Lib.log('Unable to automatically bind plot updates to API command');
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updatemenus.json emits this twice, and yet everything in its menus seems to work as expected... not sure what this is really about but since it doesn't (necessarily) mean anything is broken it seems more appropriate as a log than a warn.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems more appropriate as a log than a warn.

I agree 👌

@jackparmer
Copy link
Contributor

woot! A mere 95 files changed! 😄 📚

@etpinard
Copy link
Contributor

💃 nicely done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants