Skip to content

Commit 354df03

Browse files
committed
Merge branch 'main' of github.com:wpilibsuite/systemcore-blocks-interface into react_components_i18n
2 parents dd41427 + 92f4c71 commit 354df03

17 files changed

+1071
-981
lines changed

oss-attribution/attribution.txt

Lines changed: 179 additions & 276 deletions
Large diffs are not rendered by default.

oss-attribution/licenseInfos.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

package-lock.json

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

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
"i18next": "^25.3.2",
1919
"i18next-http-backend": "^3.0.2",
2020
"jszip": "^3.10.1",
21-
"lucide-react": "^0.525.0",
21+
"lucide-react": "^0.544.0",
2222
"re-resizable": "^6.11.2",
2323
"react": "^19.1.0",
2424
"react-dom": "^19.1.0",
25-
"react-i18next": "^15.6.0",
25+
"react-i18next": "^16.0.0",
2626
"react-syntax-highlighter": "^15.6.1",
2727
"semver": "^7.7.2",
2828
"typescript": "^5.9.2",
@@ -60,7 +60,7 @@
6060
"@types/node": "^24.0.15",
6161
"@types/react-syntax-highlighter": "^15.5.13",
6262
"@types/semver": "^7.7.0",
63-
"@vitejs/plugin-react": "^4.7.0",
63+
"@vitejs/plugin-react": "^5.0.4",
6464
"autoprefixer": "^10.4.21",
6565
"playwright": "^1.54.1",
6666
"postcss": "^8.5.6",

src/blocks/mrc_get_python_enum_value.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { createFieldDropdown } from '../fields/FieldDropdown';
3030
import { createFieldNonEditableText } from '../fields/FieldNonEditableText';
3131
import { MRC_STYLE_ENUM } from '../themes/styles'
3232
import * as toolboxItems from '../toolbox/items';
33+
import { replaceTokens } from './tokens';
3334

3435

3536
// A block to access a python enum.
@@ -75,7 +76,10 @@ const GET_PYTHON_ENUM_VALUE = {
7576
this.setTooltip(() => {
7677
const enumClassName = this.getFieldValue(FIELD_ENUM_CLASS_NAME);
7778
const enumValue = this.getFieldValue(FIELD_ENUM_VALUE);
78-
let tooltip = 'Gets the enum value ' + enumClassName + '.' + enumValue + '.';
79+
let tooltip = replaceTokens(Blockly.Msg['GET_ENUM_VALUE_TOOLTIP'], {
80+
enumName: enumClassName,
81+
valueName: enumValue
82+
});
7983
const enumTooltip = PythonEnumTooltips[enumClassName]
8084
if (enumTooltip) {
8185
if (typeof enumTooltip === 'string') {

src/blocks/mrc_get_python_variable.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { createFieldDropdown } from '../fields/FieldDropdown';
3434
import { createFieldNonEditableText } from '../fields/FieldNonEditableText';
3535
import { MRC_STYLE_VARIABLES } from '../themes/styles';
3636
import * as toolboxItems from '../toolbox/items';
37+
import { replaceTokens } from './tokens';
3738

3839

3940
// A block to get a python variable.
@@ -132,7 +133,7 @@ const GET_PYTHON_VARIABLE = {
132133
*/
133134
init: function(this: GetPythonVariableBlock): void {
134135
this.appendDummyInput('VAR')
135-
.appendField('get')
136+
.appendField(Blockly.Msg['GET'])
136137
.appendField(createFieldNonEditableText(''), FIELD_MODULE_OR_CLASS_NAME)
137138
.appendField('.');
138139
this.setStyle(MRC_STYLE_VARIABLES);
@@ -142,21 +143,30 @@ const GET_PYTHON_VARIABLE = {
142143
switch (this.mrcVarKind) {
143144
case VariableKind.MODULE: {
144145
const moduleName = this.getFieldValue(FIELD_MODULE_OR_CLASS_NAME);
145-
tooltip = 'Gets the variable ' + moduleName + '.' + varName + '.';
146+
tooltip = replaceTokens(Blockly.Msg['GET_MODULE_VARIABLE_TOOLTIP'], {
147+
moduleName: moduleName,
148+
varName: varName
149+
});
146150
break;
147151
}
148152
case VariableKind.CLASS: {
149153
const className = this.getFieldValue(FIELD_MODULE_OR_CLASS_NAME);
150-
tooltip = 'Gets the variable ' + className + '.' + varName + '.';
154+
tooltip = replaceTokens(Blockly.Msg['GET_CLASS_VARIABLE_TOOLTIP'], {
155+
className: className,
156+
varName: varName
157+
});
151158
break;
152159
}
153160
case VariableKind.INSTANCE: {
154161
const className = this.getFieldValue(FIELD_MODULE_OR_CLASS_NAME);
155-
tooltip = 'Gets the variable ' + varName + ' for the given ' + className + ' object.';
162+
tooltip = replaceTokens(Blockly.Msg['GET_INSTANCE_VARIABLE_TOOLTIP'], {
163+
varName: varName,
164+
className: className
165+
});
156166
break;
157167
}
158168
default:
159-
throw new Error('mrcVarKind must be "module", "class", or "instance".')
169+
throw new Error(Blockly.Msg['VAR_KIND_MUST_BE_MODULE_CLASS_OR_INSTANCE']);
160170
}
161171
const varTooltips = PythonVariableGetterTooltips[this.mrcKey];
162172
if (varTooltips) {
@@ -286,7 +296,7 @@ export const pythonFromBlock = function(
286296
return [code, Order.MEMBER];
287297
}
288298
default:
289-
throw new Error('mrcVarKind must be "module", "class", or "instance".')
299+
throw new Error(Blockly.Msg['VAR_KIND_MUST_BE_MODULE_CLASS_OR_INSTANCE']);
290300
}
291301
};
292302

src/blocks/mrc_mechanism.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ const MECHANISM = {
7474
nameField.setValidator(this.mrcNameFieldValidator.bind(this, nameField));
7575
this.appendDummyInput()
7676
.appendField(nameField, FIELD_NAME)
77-
.appendField(Blockly.Msg.OF_TYPE)
77+
.appendField(Blockly.Msg['OF_TYPE'])
7878
.appendField(createFieldNonEditableText(''), FIELD_TYPE);
7979
this.setPreviousStatement(true, OUTPUT_NAME);
8080
this.setNextStatement(true, OUTPUT_NAME);
@@ -240,7 +240,7 @@ const MECHANISM = {
240240
this.updateBlock_();
241241
} else {
242242
// Did not find the mechanism.
243-
warnings.push('This block refers to a mechanism that no longer exists.');
243+
warnings.push(Blockly.Msg['MECHANISM_NOT_FOUND_WARNING']);
244244
}
245245
}
246246

src/blocks/mrc_set_python_variable.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { createFieldDropdown } from '../fields/FieldDropdown';
3131
import { createFieldNonEditableText } from '../fields/FieldNonEditableText';
3232
import { MRC_STYLE_VARIABLES } from '../themes/styles';
3333
import * as toolboxItems from '../toolbox/items';
34+
import { replaceTokens } from './tokens';
3435

3536

3637
// A block to set a python variable.
@@ -123,7 +124,7 @@ const SET_PYTHON_VARIABLE = {
123124
*/
124125
init: function(this: SetPythonVariableBlock): void {
125126
this.appendValueInput('VALUE')
126-
.appendField('set')
127+
.appendField(Blockly.Msg['SET'])
127128
.appendField(createFieldNonEditableText(''), FIELD_MODULE_OR_CLASS_NAME)
128129
.appendField('.');
129130
this.setStyle(MRC_STYLE_VARIABLES);
@@ -133,21 +134,30 @@ const SET_PYTHON_VARIABLE = {
133134
switch (this.mrcVarKind) {
134135
case VariableKind.MODULE: {
135136
const moduleName = this.getFieldValue(FIELD_MODULE_OR_CLASS_NAME);
136-
tooltip = 'Sets the variable ' + moduleName + '.' + varName + '.';
137+
tooltip = replaceTokens(Blockly.Msg['SET_MODULE_VARIABLE_TOOLTIP'], {
138+
moduleName: moduleName,
139+
varName: varName
140+
});
137141
break;
138142
}
139143
case VariableKind.CLASS: {
140144
const className = this.getFieldValue(FIELD_MODULE_OR_CLASS_NAME);
141-
tooltip = 'Sets the variable ' + className + '.' + varName + '.';
145+
tooltip = replaceTokens(Blockly.Msg['SET_CLASS_VARIABLE_TOOLTIP'], {
146+
className: className,
147+
varName: varName
148+
});
142149
break;
143150
}
144151
case VariableKind.INSTANCE: {
145152
const className = this.getFieldValue(FIELD_MODULE_OR_CLASS_NAME);
146-
tooltip = 'Sets the variable ' + varName + ' for the given ' + className + ' object.';
153+
tooltip = replaceTokens(Blockly.Msg['SET_INSTANCE_VARIABLE_TOOLTIP'], {
154+
varName: varName,
155+
className: className
156+
});
147157
break;
148158
}
149159
default:
150-
throw new Error('mrcVarKind must be "module", "class", or "instance".')
160+
throw new Error(Blockly.Msg['VAR_KIND_MUST_BE_MODULE_CLASS_OR_INSTANCE']);
151161
}
152162
const varTooltips = PythonVariableSetterTooltips[this.mrcKey];
153163
if (varTooltips) {
@@ -226,7 +236,7 @@ const SET_PYTHON_VARIABLE = {
226236
} else {
227237
input.appendField(createFieldNonEditableText(''), FIELD_VARIABLE_NAME);
228238
}
229-
input.appendField('to');
239+
input.appendField(Blockly.Msg['TO']);
230240
if (this.mrcVarType) {
231241
input.setCheck(getAllowedTypesForSetCheck(this.mrcVarType));
232242
}
@@ -279,7 +289,7 @@ export const pythonFromBlock = function(
279289
return code;
280290
}
281291
default:
282-
throw new Error('mrcVarKind must be "module", "class", or "instance".')
292+
throw new Error(Blockly.Msg['VAR_KIND_MUST_BE_MODULE_CLASS_OR_INSTANCE']);
283293
}
284294
};
285295

src/blocks/tokens.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,18 @@ export function customTokens(t: (key: string) => string): typeof Blockly.Msg {
7171
ROBOT_LOWER_CASE: t('BLOCKLY.ROBOT_LOWER_CASE'),
7272
CREATE: t('BLOCKLY.CREATE'),
7373
FIRE: t('BLOCKLY.FIRE'),
74+
GET: t('BLOCKLY.GET'),
75+
GET_MODULE_VARIABLE_TOOLTIP: t('BLOCKLY.TOOLTIP.GET_MODULE_VARIABLE'),
76+
GET_CLASS_VARIABLE_TOOLTIP: t('BLOCKLY.TOOLTIP.GET_CLASS_VARIABLE'),
77+
GET_INSTANCE_VARIABLE_TOOLTIP: t('BLOCKLY.TOOLTIP.GET_INSTANCE_VARIABLE'),
78+
GET_ENUM_VALUE_TOOLTIP: t('BLOCKLY.TOOLTIP.GET_ENUM_VALUE'),
79+
SET: t('BLOCKLY.SET'),
80+
TO: t('BLOCKLY.TO'),
81+
SET_MODULE_VARIABLE_TOOLTIP: t('BLOCKLY.TOOLTIP.SET_MODULE_VARIABLE'),
82+
SET_CLASS_VARIABLE_TOOLTIP: t('BLOCKLY.TOOLTIP.SET_CLASS_VARIABLE'),
83+
SET_INSTANCE_VARIABLE_TOOLTIP: t('BLOCKLY.TOOLTIP.SET_INSTANCE_VARIABLE'),
84+
VAR_KIND_MUST_BE_MODULE_CLASS_OR_INSTANCE: t('BLOCKLY.ERROR.VAR_KIND_MUST_BE_MODULE_CLASS_OR_INSTANCE'),
85+
MECHANISM_NOT_FOUND_WARNING: t('BLOCKLY.WARNING.MECHANISM_NOT_FOUND'),
7486
WARNING_CALL_COMPONENT_INSTANCE_METHOD_PRIVATE_COMPONENT: t('BLOCKLY.WARNING.CALL_COMPONENT_INSTANCE_METHOD_PRIVATE_COMPONENT'),
7587
WARNING_CALL_COMPONENT_INSTANCE_METHOD_MISSING_COMPONENT: t('BLOCKLY.WARNING.CALL_COMPONENT_INSTANCE_METHOD_MISSING_COMPONENT'),
7688
WARNING_CALL_MECHANISM_COMPONENT_INSTANCE_METHOD_MISSING_MECHANISM: t('BLOCKLY.WARNING.CALL_MECHANISM_COMPONENT_INSTANCE_METHOD_MISSING_MECHANISM'),
@@ -108,5 +120,23 @@ export function customTokens(t: (key: string) => string): typeof Blockly.Msg {
108120
MRC_CATEGORY_ADD_COMPONENT: t('BLOCKLY.CATEGORY.ADD_COMPONENT'),
109121
MRC_CATEGORY_TEST: t('BLOCKLY.CATEGORY.TEST'),
110122
MRC_PRINT: t('BLOCKLY.PRINT'),
123+
CUSTOM_EVENTS_LABEL: t('BLOCKLY.CUSTOM_EVENTS_LABEL'),
124+
CUSTOM_METHODS_LABEL: t('BLOCKLY.CUSTOM_METHODS_LABEL'),
125+
MORE_ROBOT_METHODS_LABEL: t('BLOCKLY.MORE_ROBOT_METHODS_LABEL'),
126+
MORE_MECHANISM_METHODS_LABEL: t('BLOCKLY.MORE_MECHANISM_METHODS_LABEL'),
127+
MORE_OPMODE_METHODS_LABEL: t('BLOCKLY.MORE_OPMODE_METHODS_LABEL'),
128+
COMMENT_DEFAULT_TEXT: t('BLOCKLY.COMMENT_DEFAULT_TEXT'),
111129
}
112-
};
130+
};
131+
132+
/**
133+
* Replaces tokens in a string with values from a dictionary.
134+
* @param template String containing tokens in the format {{token}}
135+
* @param values Dictionary mapping token names to their replacement values
136+
* @return String with all tokens replaced by their corresponding values
137+
*/
138+
export function replaceTokens(template: string, values: Record<string, string>): string {
139+
return template.replace(/\{\{(\w+)\}\}/g, (match, token) => {
140+
return values[token] !== undefined ? values[token] : match;
141+
});
142+
}

src/i18n/locales/en/translation.json

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
"SPANISH": "Spanish",
3535
"HEBREW": "Hebrew",
3636
"HELP": "Help",
37-
"ABOUT": "About",
3837
"SELECT_HIDDEN": "Select Hidden",
3938
"NO_HIDDEN_MECHANISMS": "No Hidden Mechanisms",
4039
"NO_HIDDEN_OPMODES": "No Hidden Opmodes",
@@ -78,6 +77,24 @@
7877
"COPY_ELLIPSIS": "Copy...",
7978
"MECHANISMS": "Mechanisms",
8079
"OPMODES": "OpModes",
80+
"ABOUT": {
81+
"TITLE": "About",
82+
"OK": "OK",
83+
"VERSION": "Version",
84+
"NAME": "Name",
85+
"AUTHORS": "Authors",
86+
"URL": "URL",
87+
"LICENSE": "License",
88+
"TAB_ATTRIBUTIONS": "Third-Party Attributions",
89+
"TAB_DEPENDENCIES": "Dependencies",
90+
"LOADING_ATTRIBUTIONS": "Loading attributions...",
91+
"LOADING_DEPENDENCIES": "Loading dependencies...",
92+
"ATTRIBUTIONS_NOT_FOUND": "Attributions file not found.",
93+
"ERROR_LOADING_ATTRIBUTIONS": "Error loading attributions.",
94+
"DEPENDENCIES_NOT_AVAILABLE": "Dependencies information not available.",
95+
"ERROR_LOADING_DEPENDENCIES": "Error loading dependencies.",
96+
"COPYRIGHT": "© 2025 FIRST. All rights reserved."
97+
},
8198
"INVALID_CLASS_NAME": "{{name}} is not a valid name. Please enter a different name.",
8299
"CLASS_NAME_ALREADY_EXISTS": "Another Mechanism or OpMode is already named {{name}}. Please enter a different name.",
83100
"THEME_MODAL": {
@@ -125,6 +142,15 @@
125142
"ROBOT": "robot",
126143
"CREATE": "create",
127144
"FIRE": "fire",
145+
"GET": "get",
146+
"SET": "set",
147+
"TO": "to",
148+
"CUSTOM_EVENTS_LABEL": "Custom Events",
149+
"CUSTOM_METHODS_LABEL": "Custom Methods",
150+
"MORE_ROBOT_METHODS_LABEL": "More Robot Methods",
151+
"MORE_MECHANISM_METHODS_LABEL": "More Mechanism Methods",
152+
"MORE_OPMODE_METHODS_LABEL": "More OpMode Methods",
153+
"COMMENT_DEFAULT_TEXT": "Enter your comment here!",
128154
"TOOLTIP":{
129155
"EVALUATE_BUT_IGNORE_RESULT": "Executes the connected block and ignores the result. Allows you to call a function and ignore the return value.",
130156
"NONE": "Returns None.",
@@ -144,7 +170,14 @@
144170
"CALL_MECHANISM_COMPONENT_INSTANCE_METHOD": "Calls the instance method {{className}}.{{functionName}} on the component named {{componentName}} in the mechanism named {{mechanismName}}.",
145171
"CALL_COMPONENT_INSTANCE_METHOD": "Calls the instance method {{className}}.{{functionName}} on the component named {{componentName}}.",
146172
"CALL_ROBOT_INSTANCE_METHOD": "Calls the robot method {{functionName}}.",
147-
"CALL_MECHANISM_INSTANCE_METHOD": "Calls the instance method {{className}}.{{functionName}} on the mechanism named {{mechanismName}}."
173+
"CALL_MECHANISM_INSTANCE_METHOD": "Calls the instance method {{className}}.{{functionName}} on the mechanism named {{mechanismName}}.",
174+
"GET_MODULE_VARIABLE": "Gets the variable {{moduleName}}.{{varName}}.",
175+
"GET_CLASS_VARIABLE": "Gets the variable {{className}}.{{varName}}.",
176+
"GET_INSTANCE_VARIABLE": "Gets the variable {{varName}} for the given {{className}} object.",
177+
"GET_ENUM_VALUE": "Gets the enum value {{enumName}}.{{valueName}}.",
178+
"SET_MODULE_VARIABLE": "Sets the variable {{moduleName}}.{{varName}}.",
179+
"SET_CLASS_VARIABLE": "Sets the variable {{className}}.{{varName}}.",
180+
"SET_INSTANCE_VARIABLE": "Sets the variable {{varName}} for the given {{className}} object."
148181
},
149182
"CATEGORY":{
150183
"LISTS": "Lists",
@@ -174,7 +207,11 @@
174207
"CALL_MECHANISM_INSTANCE_METHOD_INSIDE_MECHANISM": "This block is not allowed to be used inside a mechanism.",
175208
"CALL_MECHANISM_INSTANCE_METHOD_MISSING_METHOD": "This block calls a method that no longer exists in the mechanism.",
176209
"CALL_MECHANISM_INSTANCE_METHOD_MISSING_MECHANISM": "This block calls a method in a mechanism that no longer exists.",
177-
"EVENT_NOT_IN_HOLDER": "This block can only go in the events section of the robot or mechanism."
210+
"EVENT_NOT_IN_HOLDER": "This block can only go in the events section of the robot or mechanism.",
211+
"MECHANISM_NOT_FOUND": "This block refers to a mechanism that no longer exists."
212+
},
213+
"ERROR":{
214+
"VAR_KIND_MUST_BE_MODULE_CLASS_OR_INSTANCE": "mrcVarKind must be \"module\", \"class\", or \"instance\"."
178215
}
179216
}
180217
}

0 commit comments

Comments
 (0)