diff --git a/package-lock.json b/package-lock.json index cfde0a0e8..f532e9abf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -673,6 +673,28 @@ "trim-right": "1.0.1" } }, + "babel-helper-bindify-decorators": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz", + "integrity": "sha1-FMGeXxQte0fxmlJDHlKxzLxAozA=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-helper-builder-binary-assignment-operator-visitor": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", + "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", + "dev": true, + "requires": { + "babel-helper-explode-assignable-expression": "6.24.1", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + } + }, "babel-helper-builder-react-jsx": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz", @@ -708,6 +730,29 @@ "lodash": "4.17.4" } }, + "babel-helper-explode-assignable-expression": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz", + "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-helper-explode-class": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz", + "integrity": "sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes=", + "dev": true, + "requires": { + "babel-helper-bindify-decorators": "6.24.1", + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + } + }, "babel-helper-function-name": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", @@ -762,6 +807,19 @@ "lodash": "4.17.4" } }, + "babel-helper-remap-async-to-generator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz", + "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", + "dev": true, + "requires": { + "babel-helper-function-name": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + } + }, "babel-helper-replace-supers": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", @@ -842,6 +900,42 @@ "integrity": "sha1-LO9jclm9S2KKbKzgOd5fzRTbsAY=", "dev": true }, + "babel-plugin-syntax-async-functions": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", + "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", + "dev": true + }, + "babel-plugin-syntax-async-generators": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz", + "integrity": "sha1-a8lj67FuzLrmuStZbrfzXDQqi5o=", + "dev": true + }, + "babel-plugin-syntax-class-properties": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz", + "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=", + "dev": true + }, + "babel-plugin-syntax-decorators": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz", + "integrity": "sha1-MSVjtNvePMgGzuPkFszurd0RrAs=", + "dev": true + }, + "babel-plugin-syntax-dynamic-import": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz", + "integrity": "sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo=", + "dev": true + }, + "babel-plugin-syntax-exponentiation-operator": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", + "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=", + "dev": true + }, "babel-plugin-syntax-flow": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz", @@ -860,6 +954,59 @@ "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", "dev": true }, + "babel-plugin-syntax-trailing-function-commas": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", + "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=", + "dev": true + }, + "babel-plugin-transform-async-generator-functions": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz", + "integrity": "sha1-8FiQAUX9PpkHpt3yjaWfIVJYpds=", + "dev": true, + "requires": { + "babel-helper-remap-async-to-generator": "6.24.1", + "babel-plugin-syntax-async-generators": "6.13.0", + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-async-to-generator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz", + "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", + "dev": true, + "requires": { + "babel-helper-remap-async-to-generator": "6.24.1", + "babel-plugin-syntax-async-functions": "6.13.0", + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-class-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz", + "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=", + "dev": true, + "requires": { + "babel-helper-function-name": "6.24.1", + "babel-plugin-syntax-class-properties": "6.13.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" + } + }, + "babel-plugin-transform-decorators": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz", + "integrity": "sha1-eIAT2PjGtSIr33s0Q5Df13Vp4k0=", + "dev": true, + "requires": { + "babel-helper-explode-class": "6.24.1", + "babel-plugin-syntax-decorators": "6.13.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-types": "6.26.0" + } + }, "babel-plugin-transform-es2015-arrow-functions": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", @@ -1094,6 +1241,17 @@ "regexpu-core": "2.0.0" } }, + "babel-plugin-transform-exponentiation-operator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", + "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", + "dev": true, + "requires": { + "babel-helper-builder-binary-assignment-operator-visitor": "6.24.1", + "babel-plugin-syntax-exponentiation-operator": "6.13.0", + "babel-runtime": "6.26.0" + } + }, "babel-plugin-transform-flow-strip-types": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz", @@ -1263,6 +1421,31 @@ "babel-preset-flow": "6.23.0" } }, + "babel-preset-stage-2": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz", + "integrity": "sha1-2eKWD7PXEYfw5k7sYrwHdnIZvcE=", + "dev": true, + "requires": { + "babel-plugin-syntax-dynamic-import": "6.18.0", + "babel-plugin-transform-class-properties": "6.24.1", + "babel-plugin-transform-decorators": "6.24.1", + "babel-preset-stage-3": "6.24.1" + } + }, + "babel-preset-stage-3": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz", + "integrity": "sha1-g2raCp56f6N8sTj7kyb4eTSkg5U=", + "dev": true, + "requires": { + "babel-plugin-syntax-trailing-function-commas": "6.22.0", + "babel-plugin-transform-async-generator-functions": "6.24.1", + "babel-plugin-transform-async-to-generator": "6.24.1", + "babel-plugin-transform-exponentiation-operator": "6.24.1", + "babel-plugin-transform-object-rest-spread": "6.26.0" + } + }, "babel-register": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", diff --git a/package.json b/package.json index af23fdfac..53a8f6ad6 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "url": "https://github.com/plotly/react-plotly.js-editor/issues" }, "scripts": { + "find-strings": "babel-node utils/findLocaleStrings.js", "make:lib": "mkdirp lib && babel src --out-dir lib --ignore=__tests__/* --source-maps && npm run make:lib:css", "make:lib:css": "mkdirp lib && node-sass src/styles/main.scss > lib/react-plotly.js-editor.css", "make:dist": "mkdirp dist && browserify src/PlotlyEditor.js -o ./dist/PlotlyEditor.js -t [ babelify --presets [ es2015 react ] ] -t browserify-global-shim --standalone createPlotlyComponent && uglifyjs ./dist/PlotlyEditor.js --compress --mangle --output ./dist/PlotlyEditor.min.js --source-map filename=dist/PlotlyEditor.min.js.map && make:dist:css", @@ -39,10 +40,13 @@ ], "devDependencies": { "babel-cli": "^6.26.0", + "babel-core": "^6.26.0", "babel-eslint": "^8.0.2", "babel-plugin-transform-object-rest-spread": "^6.26.0", "babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1", + "babel-preset-stage-2": "^6.24.1", + "babel-traverse": "^6.26.0", "babelify": "^7.3.0", "browserify": "^14.5.0", "classnames": "^2.2.5", diff --git a/src/DefaultEditor.js b/src/DefaultEditor.js index 044dc790b..461310717 100644 --- a/src/DefaultEditor.js +++ b/src/DefaultEditor.js @@ -387,7 +387,7 @@ class DefaultEditor extends Component { {_( 'The positioning inputs are relative to the ' + - 'anchor points on the text box' + 'anchor points on the text box.' )} {children} {multiValued ? ( - -
{_(multiValueText.title)}
-
{_(multiValueText.text)}
-
{_(multiValueText.subText)}
+ +
{getMultiValueText('title', _)}
+
{getMultiValueText('text', _)}
+
+ {getMultiValueText('subText', _)} +
) : null} diff --git a/src/components/widgets/ColorPicker.js b/src/components/widgets/ColorPicker.js index 138028c97..2c6229191 100644 --- a/src/components/widgets/ColorPicker.js +++ b/src/components/widgets/ColorPicker.js @@ -49,7 +49,7 @@ const CustomColorPicker = localize( return (
-

{_('Custom colors')}

+

{_('Custom Colors')}

@@ -72,7 +72,7 @@ const CustomColorPicker = localize(
-

{_('Default colors')}

+

{_('Default Colors')}

diff --git a/src/components/widgets/text_editors/RichText/LinkEditor.js b/src/components/widgets/text_editors/RichText/LinkEditor.js index 2ada97a09..213b8a5b4 100644 --- a/src/components/widgets/text_editors/RichText/LinkEditor.js +++ b/src/components/widgets/text_editors/RichText/LinkEditor.js @@ -89,7 +89,7 @@ class LinkEditor extends Component { render() { const {position} = this.state; const {onBlur, onFocus, linkURL, localize: _} = this.props; - const placeholderText = _('Enter link URL'); + const placeholderText = _('Enter Link URL'); const urlText = _('URL'); return ( diff --git a/src/lib/constants.js b/src/lib/constants.js index 6ec83f79a..3f9bf1cce 100644 --- a/src/lib/constants.js +++ b/src/lib/constants.js @@ -13,14 +13,19 @@ export const MULTI_VALUED = '\x1bMIXED_VALUES'; // how mixed values are represented in text inputs export const MULTI_VALUED_PLACEHOLDER = '---'; -export const multiValueText = { - title: 'Multiple Values', - text: - 'This input has multiple values associated with it. ' + - 'Changing this setting will override these custom inputs.', - subText: - "Common Case: An 'All' tab might display this message " + - 'because the X and Y tabs contain different settings.', +export const getMultiValueText = (key, _) => { + const multiValueText = { + title: _('Multiple Values'), + text: _( + 'This input has multiple values associated with it. ' + + 'Changing this setting will override these custom inputs.' + ), + subText: _( + "Common Case: An 'All' tab might display this message " + + 'because the X and Y tabs contain different settings.' + ), + }; + return multiValueText[key]; }; export const EDITOR_ACTIONS = { diff --git a/utils/findLocaleStrings.js b/utils/findLocaleStrings.js new file mode 100644 index 000000000..4485e9d50 --- /dev/null +++ b/utils/findLocaleStrings.js @@ -0,0 +1,110 @@ +import {transform} from 'babel-core'; +import traverse from 'babel-traverse'; +import fs from 'fs'; +import glob from 'glob'; +import path from 'path'; + +const pathToSrc = path.join(__dirname, '../src'); +const srcGlob = path.join(pathToSrc, '**/*.js'); + +const localizeRE = /(^|[\.])(_|localize)$/; + +findLocaleStrings(); + +function findLocaleStrings() { + glob(srcGlob, (err, files) => { + if (err) { + throw new Error(err); + } + + const dict = {}; + let hasTranslation = false; + let maxLen = 0; + + files.forEach(file => { + const code = fs.readFileSync(file, 'utf-8'); + const filePartialPath = file.substr(pathToSrc.length); + const ast = transform(code, { + presets: ['react', 'es2015', 'stage-2'], + }).ast; + + traverse(ast, { + enter(path) { + if ( + path.node.type === 'CallExpression' && + path.node.callee.name === '_' + ) { + const strNode = path.node.arguments[0]; + let strNodeValue = strNode.value; + + if (path.node.arguments.length !== 1) { + logError(file, path.node, 'Localize takes 1 args'); + } + + if ( + ['StringLiteral', 'BinaryExpression'].indexOf(strNode.type) < 0 + ) { + logError( + file, + path.node, + `The localization function takes a string as argument, instead it received a ${ + strNode.type + }` + ); + } + + if (strNode.type === 'BinaryExpression') { + strNodeValue = path.get('arguments')[0].evaluate().value; + if (typeof strNodeValue !== 'string') { + logError( + file, + path.node, + `The localization function takes a string as argument, instead it received a ${typeof strNodeValue}` + ); + } + } + + if (!dict[strNodeValue]) { + dict[strNodeValue] = + filePartialPath + ':' + strNode.loc.start.line; + maxLen = Math.max(maxLen, strNodeValue.length); + hasTranslation = true; + } + } + }, + }); + }); + + if (!hasTranslation) { + throw new Error('Found no translations.'); + } + + const strings = Object.keys(dict) + .sort() + .map(k => k + spaces(maxLen - k.length) + ' // ' + dict[k]) + .join('\n'); + const pathToTranslationKeys = path.join(__dirname, 'translationKeys.txt'); + fs.writeFile(pathToTranslationKeys, strings); + console.log(`translation keys were written to: ${pathToTranslationKeys}`); + }); +} + +function logError(file, node, msg) { + throw new Error( + file + ' [line ' + node.loc.start.line + '] ' + msg + '\n ' + ); +} + +function spaces(len) { + let out = ''; + for (let i = 0; i < len; i++) { + out += ' '; + } + return out; +} + +process.on('exit', function(code) { + if (code === 1) { + throw new Error('findLocaleStrings failed.'); + } +}); diff --git a/utils/translationKeys.txt b/utils/translationKeys.txt new file mode 100644 index 000000000..ef2f856b1 --- /dev/null +++ b/utils/translationKeys.txt @@ -0,0 +1,106 @@ +Anchor Point // /DefaultEditor.js:253 +Angle // /DefaultEditor.js:227 +Arrow // /DefaultEditor.js:229 +Arrowhead // /DefaultEditor.js:239 +Auto // /DefaultEditor.js:178 +Axes // /DefaultEditor.js:300 +Background Color // /DefaultEditor.js:380 +Bar Padding // /DefaultEditor.js:147 +Bar Width // /DefaultEditor.js:139 +Bars // /components/containers/TraceMarkerSection.js:20 +Blank // /DefaultEditor.js:166 +Border Color // /DefaultEditor.js:134 +Border Width // /DefaultEditor.js:133 +Bottom // /DefaultEditor.js:213 +Box Padding // /DefaultEditor.js:151 +Box Width // /DefaultEditor.js:143 +Canvas // /DefaultEditor.js:174 +Center // /DefaultEditor.js:265 +Color // /DefaultEditor.js:118 +Common Case: An 'All' tab might display this message because the X and Y tabs contain different settings. // /lib/constants.js:24 +Connect // /DefaultEditor.js:165 +Connect Gaps // /DefaultEditor.js:162 +Continue // /components/widgets/text_editors/MultiFormatTextEditor.js:197 +Continuing will convert your LaTeX expression into raw text. // /components/widgets/text_editors/MultiFormatTextEditor.js:121 +Continuing will convert your note to LaTeX-style text. // /components/widgets/text_editors/MultiFormatTextEditor.js:116 +Continuing will remove your expression. // /components/widgets/text_editors/MultiFormatTextEditor.js:126 +Custom // /DefaultEditor.js:179 +Custom Colors // /components/widgets/ColorPicker.js:52 +Default Colors // /components/widgets/ColorPicker.js:75 +Display // /DefaultEditor.js:94 +Edit in HTML // /components/widgets/text_editors/MultiFormatTextEditor.js:34 +Edit in Rich Text // /components/widgets/text_editors/MultiFormatTextEditor.js:241 +Enter Link URL // /components/widgets/text_editors/RichText/LinkEditor.js:92 +Filled Area // /DefaultEditor.js:104 +Fixed Height // /DefaultEditor.js:183 +Fixed Width // /DefaultEditor.js:182 +Font Color // /DefaultEditor.js:199 +Font Size // /DefaultEditor.js:195 +Global Font // /DefaultEditor.js:201 +Go back // /components/widgets/text_editors/MultiFormatTextEditor.js:191 +Heads up! // /components/widgets/text_editors/MultiFormatTextEditor.js:179 +Hide // /DefaultEditor.js:234 +Horizontal // /DefaultEditor.js:126 +Horizontal Postitioning // /DefaultEditor.js:251 +LaTeX // /components/widgets/text_editors/MultiFormatTextEditor.js:29 +LaTeX is a math typesetting language that doesn't work with rich text. // /components/widgets/text_editors/MultiFormatTextEditor.js:114 +Layout // /DefaultEditor.js:173 +Left // /DefaultEditor.js:214 +Legend // /DefaultEditor.js:353 +Legend Box // /DefaultEditor.js:369 +Line Color // /DefaultEditor.js:158 +Line Width // /DefaultEditor.js:237 +Linear // /DefaultEditor.js:324 +Lines // /DefaultEditor.js:156 +Margins and Padding // /DefaultEditor.js:211 +Max // /DefaultEditor.js:320 +Middle // /DefaultEditor.js:288 +Min // /DefaultEditor.js:319 +Multiple Values // /lib/constants.js:18 +Normal // /DefaultEditor.js:439 +Note Text // /DefaultEditor.js:222 +Notes // /DefaultEditor.js:220 +Opacity // /DefaultEditor.js:79 +Orientation // /DefaultEditor.js:426 +Padding // /DefaultEditor.js:216 +Points // /components/containers/TraceMarkerSection.js:22 +Position // /DefaultEditor.js:272 +Positioning // /DefaultEditor.js:384 +Range // /DefaultEditor.js:309 +Relative To // /DefaultEditor.js:271 +Reversed // /DefaultEditor.js:440 +Rich Text // /components/widgets/text_editors/MultiFormatTextEditor.js:24 +Rich text is incompatible with LaTeX. // /components/widgets/text_editors/MultiFormatTextEditor.js:120 +Right // /DefaultEditor.js:215 +Scale // /DefaultEditor.js:241 +Selection // /DefaultEditor.js:311 +Shape // /DefaultEditor.js:160 +Show // /DefaultEditor.js:233 +Size // /DefaultEditor.js:131 +Size and Spacing // /DefaultEditor.js:137 +Symbol // /DefaultEditor.js:132 +Text // /DefaultEditor.js:364 +Text Attributes // /DefaultEditor.js:82 +The anchor point determines which side of the annotation's positioning coordinates refer to. // /DefaultEditor.js:256 +The positioning inputs are relative to the anchor points on the text box. // /DefaultEditor.js:389 +This input has multiple values associated with it. Changing this setting will override these custom inputs. // /lib/constants.js:20 +Tick Labels // /DefaultEditor.js:334 +Tick Markers // /DefaultEditor.js:342 +Title // /DefaultEditor.js:187 +Title and Fonts // /DefaultEditor.js:186 +Titles // /DefaultEditor.js:301 +Top // /DefaultEditor.js:212 +Trace // /DefaultEditor.js:78 +Trace Order // /DefaultEditor.js:435 +Type // /DefaultEditor.js:159 +Typeface // /DefaultEditor.js:190 +URL // /components/widgets/text_editors/RichText/LinkEditor.js:93 +Vertical // /DefaultEditor.js:125 +Vertical Postitioning // /DefaultEditor.js:274 +Width // /DefaultEditor.js:157 +X Position // /DefaultEditor.js:414 +X Vector // /DefaultEditor.js:248 +Y Position // /DefaultEditor.js:420 +Y Vector // /DefaultEditor.js:249 +Zoom Interactivity // /DefaultEditor.js:345 +log // /DefaultEditor.js:325 \ No newline at end of file