Skip to content

Commit 7fd1a2d

Browse files
authored
Merge pull request #239 from smalruby/highlight-and-japanese-errors
feat: i18n error message when converting ruby to block.
2 parents 434f8f5 + 0694922 commit 7fd1a2d

File tree

13 files changed

+452
-66
lines changed

13 files changed

+452
-66
lines changed

package-lock.json

Lines changed: 337 additions & 31 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
"babel-loader": "^8.0.4",
4747
"base64-loader": "1.0.0",
4848
"bowser": "1.9.4",
49-
"chromedriver": "80.0.0",
49+
"chromedriver": "^80.0.2",
5050
"classnames": "2.2.6",
5151
"computed-style-to-inline-style": "3.0.0",
5252
"copy-webpack-plugin": "^4.5.1",

src/containers/controls.jsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import bindAll from 'lodash.bindall';
22
import PropTypes from 'prop-types';
33
import React from 'react';
44
import VM from 'scratch-vm';
5+
import {injectIntl, intlShape} from 'react-intl';
56
import {connect} from 'react-redux';
67

78
import ControlsComponent from '../components/controls/controls.jsx';
@@ -19,7 +20,7 @@ class Controls extends React.Component {
1920
handleGreenFlagClick (e) {
2021
e.preventDefault();
2122

22-
const converter = this.props.targetCodeToBlocks();
23+
const converter = this.props.targetCodeToBlocks(this.props.intl.formatMessage);
2324
if (!converter.result) {
2425
return;
2526
}
@@ -62,6 +63,7 @@ class Controls extends React.Component {
6263
}
6364

6465
Controls.propTypes = {
66+
intl: intlShape.isRequired,
6567
isStarted: PropTypes.bool.isRequired,
6668
projectRunning: PropTypes.bool.isRequired,
6769
targetCodeToBlocks: PropTypes.func,
@@ -77,4 +79,7 @@ const mapStateToProps = state => ({
7779
// no-op function to prevent dispatch prop being passed to component
7880
const mapDispatchToProps = () => ({});
7981

80-
export default RubyToBlocksConverterHOC(connect(mapStateToProps, mapDispatchToProps)(Controls));
82+
export default RubyToBlocksConverterHOC(injectIntl(connect(
83+
mapStateToProps,
84+
mapDispatchToProps
85+
)(Controls)));

src/containers/ruby-tab.jsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import bindAll from 'lodash.bindall';
22
import PropTypes from 'prop-types';
33
import React from 'react';
4+
import {injectIntl, intlShape} from 'react-intl';
45
import {connect} from 'react-redux';
56
import AceEditor from 'react-ace';
67
import {
@@ -32,7 +33,7 @@ class RubyTab extends React.Component {
3233
this.props.vm.editingTarget && this.props.rubyCode.target &&
3334
this.props.vm.editingTarget.id !== targetId;
3435
if (changedTarget || this.props.blocksTabVisible) {
35-
const converter = this.props.targetCodeToBlocks();
36+
const converter = this.props.targetCodeToBlocks(this.props.intl);
3637
if (converter.result) {
3738
converter.apply().then(() => {
3839
modified = false;
@@ -51,6 +52,8 @@ class RubyTab extends React.Component {
5152
});
5253
return;
5354
}
55+
const error = converter.errors[0];
56+
this.aceEditorRef.editor.moveCursorTo(error.row, error.column);
5457
this.aceEditorRef.editor.focus();
5558
}
5659
}
@@ -72,22 +75,23 @@ class RubyTab extends React.Component {
7275
this.aceEditorRef = ref;
7376
}
7477

75-
7678
render () {
7779
const {
7880
onChange,
7981
rubyCode
8082
} = this.props;
8183
const {
8284
code,
83-
errors
85+
errors,
86+
markers
8487
} = rubyCode;
8588
return (
8689
<AceEditor
8790
annotations={errors}
8891
editorProps={{$blockScrolling: true}}
8992
fontSize={16}
9093
height="inherit"
94+
markers={markers}
9195
mode="ruby"
9296
name="ruby-editor"
9397
ref={this.setAceEditorRef}
@@ -114,6 +118,7 @@ class RubyTab extends React.Component {
114118
RubyTab.propTypes = {
115119
blocksTabVisible: PropTypes.bool,
116120
editingTarget: PropTypes.string,
121+
intl: intlShape.isRequired,
117122
isVisible: PropTypes.bool,
118123
onChange: PropTypes.func,
119124
rubyCode: rubyCodeShape,
@@ -133,7 +138,7 @@ const mapDispatchToProps = dispatch => ({
133138
updateRubyCodeTargetState: target => dispatch(updateRubyCodeTarget(target))
134139
});
135140

136-
export default RubyToBlocksConverterHOC(connect(
141+
export default RubyToBlocksConverterHOC(injectIntl(connect(
137142
mapStateToProps,
138143
mapDispatchToProps
139-
)(RubyTab));
144+
)(RubyTab)));

src/containers/sb3-downloader.jsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import bindAll from 'lodash.bindall';
22
import PropTypes from 'prop-types';
33
import React from 'react';
4+
import {injectIntl, intlShape} from 'react-intl';
45
import {connect} from 'react-redux';
56
import {projectTitleInitialState} from '../reducers/project-title';
67
import downloadBlob from '../lib/download-blob';
@@ -29,7 +30,7 @@ class SB3Downloader extends React.Component {
2930
]);
3031
}
3132
downloadProject () {
32-
const converter = this.props.targetCodeToBlocks();
33+
const converter = this.props.targetCodeToBlocks(this.props.intl);
3334
if (!converter.result) {
3435
return;
3536
}
@@ -64,6 +65,7 @@ const getProjectFilename = (curTitle, defaultTitle) => {
6465
SB3Downloader.propTypes = {
6566
children: PropTypes.func,
6667
className: PropTypes.string,
68+
intl: intlShape.isRequired,
6769
onSaveFinished: PropTypes.func,
6870
projectFilename: PropTypes.string,
6971
saveProjectSb3: PropTypes.func,
@@ -78,7 +80,7 @@ const mapStateToProps = state => ({
7880
projectFilename: getProjectFilename(state.scratchGui.projectTitle, projectTitleInitialState)
7981
});
8082

81-
export default RubyToBlocksConverterHOC(connect(
83+
export default RubyToBlocksConverterHOC(injectIntl(connect(
8284
mapStateToProps,
8385
() => ({}) // omit dispatch prop
84-
)(SB3Downloader));
86+
)(SB3Downloader)));

src/lib/ruby-to-blocks-converter-hoc.jsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,13 @@ const RubyToBlocksConverterHOC = function (WrappedComponent) {
3939
]);
4040
}
4141

42-
/**
43-
* targetCodeToBlocks:
44-
* @return {RubyToBlocksConverter} - a block converter that translates ruby code into the blocks
45-
*/
46-
targetCodeToBlocks () {
42+
targetCodeToBlocks (intl) {
4743
if (this.props.rubyCode.modified) {
4844
const converter = targetCodeToBlocks(
4945
this.props.vm,
5046
this.props.rubyCode.target,
51-
this.props.rubyCode.code
47+
this.props.rubyCode.code,
48+
intl
5249
);
5350
if (!converter.result) {
5451
this.props.vm.setEditingTarget(this.props.rubyCode.target.id);

src/lib/ruby-to-blocks-converter/index.js

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/* global Opal */
2+
import {defineMessages} from 'react-intl';
23
import _ from 'lodash';
34
import log from '../log';
45
import Blockly from 'scratch-blocks';
@@ -26,6 +27,19 @@ import GdxForConverter from './gdx_for';
2627
import MeshConverter from './mesh';
2728
import SmalrubotS1Converter from './smalrubot_s1';
2829

30+
const messages = defineMessages({
31+
couldNotConvertPremitive: {
32+
defaultMessage: '"{ SOURCE }" could not be converted the block.',
33+
description: 'Error message for converting ruby to block when find the premitive',
34+
id: 'gui.smalruby3.rubyToBlocksConverter.couldNotConvertPremitive'
35+
},
36+
wrongInstruction: {
37+
defaultMessage: '"{ SOURCE }" is the wrong instruction.',
38+
description: 'Error message for converting ruby to block when find the wrong instruction',
39+
id: 'gui.smalruby3.rubyToBlocksConverter.wrongInstruction'
40+
}
41+
});
42+
2943
/* eslint-disable no-invalid-this */
3044
const ColorRegexp = /^#[0-9a-fA-F]{6}$/;
3145

@@ -61,6 +75,7 @@ const getExtensionIdForOpcode = function (opcode) {
6175
class RubyToBlocksConverter {
6276
constructor (vm) {
6377
this.vm = vm;
78+
this._translator = message => message.defaultMessage;
6479
this._converters = [
6580
MusicConverter,
6681
PenConverter,
@@ -104,6 +119,10 @@ class RubyToBlocksConverter {
104119
return this._context.broadcastMsgs;
105120
}
106121

122+
setTranslatorFunction (translator) {
123+
this._translator = translator;
124+
}
125+
107126
reset () {
108127
this._context = {
109128
currentNode: null,
@@ -144,7 +163,10 @@ class RubyToBlocksConverter {
144163
} else if (block instanceof Primitive) {
145164
throw new RubyToBlocksConverterError(
146165
block.node,
147-
`could not convert primitive: ${this._getSource(block.node)}`
166+
this._translator(
167+
messages.couldNotConvertPremitive,
168+
{SOURCE: this._getSource(block.node)}
169+
)
148170
);
149171
} else {
150172
throw new Error(`invalid block: ${block}`);
@@ -155,7 +177,10 @@ class RubyToBlocksConverter {
155177
if (this._isRubyBlock(block)) {
156178
throw new RubyToBlocksConverterError(
157179
block.node,
158-
`could not convert ${block.opcode}: ${this._getSource(block.node)}`
180+
this._translator(
181+
messages.wrongInstruction,
182+
{SOURCE: this._getSource(block.node)}
183+
)
159184
);
160185
}
161186

@@ -172,10 +197,12 @@ class RubyToBlocksConverter {
172197
error = this._toErrorAnnotation(loc.$line(), loc.$column(), e.$message());
173198
} else if (e instanceof RubyToBlocksConverterError) {
174199
const loc = e.node.$loc();
175-
error = this._toErrorAnnotation(loc.$line(), loc.$column(), e.message);
200+
error = this._toErrorAnnotation(loc.$line(), loc.$column(), e.message, this._getSource(e.node));
176201
} else if (this._context.currentNode) {
177202
const loc = this._context.currentNode.$loc();
178-
error = this._toErrorAnnotation(loc.$line(), loc.$column(), e.message);
203+
error = this._toErrorAnnotation(
204+
loc.$line(), loc.$column(), e.message, this._getSource(this._context.currentNode)
205+
);
179206
} else {
180207
error = this._toErrorAnnotation(1, 0, e.message);
181208
}
@@ -319,23 +346,21 @@ class RubyToBlocksConverter {
319346
});
320347
}
321348

322-
_toErrorAnnotation (row, column, message) {
349+
_toErrorAnnotation (row, column, message, source) {
323350
if (row === Opal.nil) {
324351
row = 0;
325352
} else {
326353
row -= 1;
327354
}
328-
let columnText = '';
329355
if (column === Opal.nil) {
330356
column = 0;
331-
} else {
332-
columnText = `${column}: `;
333357
}
334358
return {
335359
row: row,
336360
column: column,
337361
type: 'error',
338-
text: `${columnText}${message}`
362+
text: message,
363+
source: source
339364
};
340365
}
341366

@@ -1249,8 +1274,11 @@ const NullRubyToBlocksConverter = {
12491274
apply: () => Promise.resolve()
12501275
};
12511276

1252-
const targetCodeToBlocks = function (vm, target, code) {
1277+
const targetCodeToBlocks = function (vm, target, code, intl) {
12531278
const converter = new RubyToBlocksConverter(vm);
1279+
if (intl) {
1280+
converter.setTranslatorFunction(intl.formatMessage);
1281+
}
12541282
converter.result = converter.targetCodeToBlocks(target, code);
12551283
if (converter.result) {
12561284
converter.apply = () => converter.applyTargetBlocks(target);

src/locales/en.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ export default {
1515
"gui.smalruby3.telemetryOptIn.body1": "The Smalruby Team is always looking to better understand how Smalruby is used around the world. To help support this effort, you can allow Smalruby to automatically send usage information to the Smalruby Team.",
1616
"gui.smalruby3.telemetryOptIn.body2": "The information we collect includes language selection, blocks usage, and some events like saving, loading, and uploading a project. We DO NOT collect any personal information.",
1717
"gui.telemetryOptIn.buttonTextNo": "No, thanks",
18+
'gui.smalruby3.rubyToBlocksConverter.couldNotConvertPremitive': '"{ SOURCE }" could not be converted the block.',
19+
'gui.smalruby3.rubyToBlocksConverter.wrongInstruction': '"{ SOURCE }" is the wrong instruction.',
1820
"gui.smalruby3.telemetryOptIn.buttonTextYes": "Yes, I'd like to help improve Smalruby",
1921
'gui.smalruby3.extension.mesh.name': 'Mesh',
2022
'gui.smalruby3.extension.mesh.description': 'Allowing users to interact over a computer network.',

src/locales/ja-Hira.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ export default {
1818
'gui.smalruby3.telemetryOptIn.body2': 'ていきょうしていただくじょうほうにはどのげんごをせんたくしたか、どのブロックをつかったか、ほぞん・よみこみ・プロジェクトのアップロードなどのイベントをふくみます。ただし、ユーザめいなどのこじんてきなじょうほうはいっさいふくみません。',
1919
'gui.telemetryOptIn.buttonTextNo': 'いいえ、けっこうです。',
2020
'gui.smalruby3.telemetryOptIn.buttonTextYes': 'はい、スモウルビーのかいぜんにきょうりょくします。',
21+
'gui.smalruby3.rubyToBlocksConverter.couldNotConvertPremitive': '「{ SOURCE }」はブロックにへんかんできません。',
22+
'gui.smalruby3.rubyToBlocksConverter.wrongInstruction': '「{ SOURCE }」はめいれいがまちがっています。',
2123
'gui.smalruby3.extension.mesh.name': 'メッシュ',
2224
'gui.smalruby3.extension.mesh.description': 'ネットワークじょうでユーザーかんのやりとりをおこなう。',
2325
'mesh.categoryName': 'メッシュ',

src/locales/ja.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export default {
1717
'gui.smalruby3.telemetryOptIn.body1': 'スモウルビーの開発者は世界中でスモウルビーがどのように使われているのかを理解したいと考えています。その手助けのために、みなさんのスモウルビーの利用状況をスモウルビーの開発者に提供していただけないでしょうか。',
1818
'gui.smalruby3.telemetryOptIn.body2': '提供していただく情報にはどの言語を選択したか、どのブロックを使ったか、保存・読み込み・プロジェクトのアップロードなどのイベントを含みます。ただし、ユーザ名などの個人的な情報は一切含みません。',
1919
'gui.telemetryOptIn.buttonTextNo': 'いいえ、結構です。',
20+
'gui.smalruby3.rubyToBlocksConverter.couldNotConvertPremitive': '「{ SOURCE }」はブロックに変換できません。',
21+
'gui.smalruby3.rubyToBlocksConverter.wrongInstruction': '「{ SOURCE }」は命令がまちがっています。',
2022
'gui.smalruby3.telemetryOptIn.buttonTextYes': 'はい、スモウルビーの改善に協力します。',
2123
'gui.smalruby3.extension.mesh.name': 'メッシュ',
2224
'gui.smalruby3.extension.mesh.description': 'ネットワーク上でユーザー間のやりとりを行う。',

0 commit comments

Comments
 (0)