Skip to content

Commit decee4a

Browse files
committed
feat: i18n error message when converting ruby to block.
1 parent 434f8f5 commit decee4a

File tree

10 files changed

+111
-33
lines changed

10 files changed

+111
-33
lines changed

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: 36 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,10 @@ 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(loc.$line(), loc.$column(), e.message, this._getSource(this._context.currentNode));
179204
} else {
180205
error = this._toErrorAnnotation(1, 0, e.message);
181206
}
@@ -319,23 +344,21 @@ class RubyToBlocksConverter {
319344
});
320345
}
321346

322-
_toErrorAnnotation (row, column, message) {
347+
_toErrorAnnotation (row, column, message, source) {
323348
if (row === Opal.nil) {
324349
row = 0;
325350
} else {
326351
row -= 1;
327352
}
328-
let columnText = '';
329353
if (column === Opal.nil) {
330354
column = 0;
331-
} else {
332-
columnText = `${column}: `;
333355
}
334356
return {
335357
row: row,
336358
column: column,
337359
type: 'error',
338-
text: `${columnText}${message}`
360+
text: message,
361+
source: source
339362
};
340363
}
341364

@@ -1249,8 +1272,11 @@ const NullRubyToBlocksConverter = {
12491272
apply: () => Promise.resolve()
12501273
};
12511274

1252-
const targetCodeToBlocks = function (vm, target, code) {
1275+
const targetCodeToBlocks = function (vm, target, code, intl) {
12531276
const converter = new RubyToBlocksConverter(vm);
1277+
if (intl) {
1278+
converter.setTranslatorFunction(intl.formatMessage);
1279+
}
12541280
converter.result = converter.targetCodeToBlocks(target, code);
12551281
if (converter.result) {
12561282
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': 'ネットワーク上でユーザー間のやりとりを行う。',

src/playground/index.ejs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@
1919
<!-- /Sentry -->
2020
<% } %>
2121
<script src="static/javascripts/setup-opal.js"></script>
22+
<style type="text/css">
23+
<!--
24+
.ruby-error {
25+
background-color: pink;
26+
position: absolute;
27+
z-index: 3;
28+
}
29+
-->
30+
</style>
2231
</head>
2332
<body>
2433
</body>

0 commit comments

Comments
 (0)