Skip to content

Commit ee34d3a

Browse files
alan412lizlooney
andauthored
Combine internationalization (wpilibsuite#158)
* First combining of Blockly localization along with react localization * Move some items from our blocks into being translated * Moved translations out of public directory into a place that makes more sense * Translations provided by Copilot (Claude Sonnet 4) * Adding ability to change language dynamically * Changed Auto, Teleop, and Test away from being localized * Update mrc_call_python_function.ts * Update mrc_component.ts * Remove old comments noticed in review * Solve problem with language change not updating block * Add some additional translations * Add localization to toolbox categories * remove spurious import * restored accidentally deleted close brace * add saving of blocks before changing language * Remove unnecessary code now that we reload on language change * Add node that the translations need to be checked * Add Internationalization to test --------- Co-authored-by: Liz Looney <[email protected]>
1 parent c286ba1 commit ee34d3a

31 files changed

+490
-153
lines changed

public/locales/en/translation.json

Lines changed: 0 additions & 24 deletions
This file was deleted.

src/App.tsx

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import * as ChangeFramework from './blocks/utils/change_framework'
4747
import { mutatorOpenListener } from './blocks/mrc_param_container'
4848
import { TOOLBOX_UPDATE_EVENT } from './blocks/mrc_mechanism_component_holder';
4949
import { antdThemeFromString } from './reactComponents/ThemeModal';
50+
import { useTranslation } from 'react-i18next';
5051

5152
/** Storage key for shown toolbox categories. */
5253
const SHOWN_TOOLBOX_CATEGORIES_KEY = 'shownPythonToolboxCategories';
@@ -86,6 +87,8 @@ const LAYOUT_BACKGROUND_COLOR = '#0F0';
8687
* project management, and user interface layout.
8788
*/
8889
const App: React.FC = (): React.JSX.Element => {
90+
const { t, i18n } = useTranslation();
91+
8992
const [alertErrorMessage, setAlertErrorMessage] = React.useState('');
9093
const [storage, setStorage] = React.useState<commonStorage.Storage | null>(null);
9194
const [currentModule, setCurrentModule] = React.useState<commonStorage.Module | null>(null);
@@ -101,7 +104,6 @@ const App: React.FC = (): React.JSX.Element => {
101104
const [rightCollapsed, setRightCollapsed] = React.useState(false);
102105
const [theme, setTheme] = React.useState('dark');
103106

104-
105107
const blocksEditor = React.useRef<editor.Editor | null>(null);
106108
const generatorContext = React.useRef<GeneratorContext | null>(null);
107109
const blocklyComponent = React.useRef<BlocklyComponentType | null>(null);
@@ -232,7 +234,7 @@ const App: React.FC = (): React.JSX.Element => {
232234
const tabs: Tabs.TabItem[] = [
233235
{
234236
key: projectData.robot.modulePath,
235-
title: 'Robot',
237+
title: t('ROBOT'),
236238
type: TabType.ROBOT,
237239
},
238240
];
@@ -261,7 +263,7 @@ const App: React.FC = (): React.JSX.Element => {
261263
if (blocksEditor.current && currentModule) {
262264
blocksEditor.current.updateToolbox(shownPythonToolboxCategories);
263265
}
264-
}, [currentModule, shownPythonToolboxCategories]);
266+
}, [currentModule, shownPythonToolboxCategories, i18n.language]);
265267

266268
// Add event listener for toolbox updates
267269
React.useEffect(() => {
@@ -290,7 +292,31 @@ const App: React.FC = (): React.JSX.Element => {
290292
if (blocksEditor.current) {
291293
blocksEditor.current.loadModuleBlocks(currentModule);
292294
}
293-
}, [currentModule]);
295+
}, [currentModule]);
296+
297+
const setupWorkspace = (newWorkspace: Blockly.WorkspaceSvg) => {
298+
if (!blocklyComponent.current || !storage) {
299+
return;
300+
}
301+
// Recreate workspace when Blockly component is ready
302+
ChangeFramework.setup(newWorkspace);
303+
newWorkspace.addChangeListener(mutatorOpenListener);
304+
newWorkspace.addChangeListener(handleBlocksChanged);
305+
generatorContext.current = createGeneratorContext();
306+
307+
if (currentModule) {
308+
generatorContext.current.setModule(currentModule);
309+
}
310+
311+
blocksEditor.current = new editor.Editor(newWorkspace, generatorContext.current, storage);
312+
313+
// Set the current module in the editor after creating it
314+
if (currentModule) {
315+
blocksEditor.current.loadModuleBlocks(currentModule);
316+
}
317+
318+
blocksEditor.current.updateToolbox(shownPythonToolboxCategories);
319+
};
294320

295321
// Initialize Blockly workspace and editor when component and storage are ready
296322
React.useEffect(() => {
@@ -300,17 +326,8 @@ const App: React.FC = (): React.JSX.Element => {
300326

301327
const blocklyWorkspace = blocklyComponent.current.getBlocklyWorkspace();
302328
if (blocklyWorkspace) {
303-
ChangeFramework.setup(blocklyWorkspace);
304-
blocklyWorkspace.addChangeListener(mutatorOpenListener);
305-
blocklyWorkspace.addChangeListener(handleBlocksChanged);
329+
setupWorkspace(blocklyWorkspace);
306330
}
307-
308-
generatorContext.current = createGeneratorContext();
309-
if (currentModule) {
310-
generatorContext.current.setModule(currentModule);
311-
}
312-
313-
blocksEditor.current = new editor.Editor(blocklyWorkspace, generatorContext.current, storage);
314331
}, [blocklyComponent, storage]);
315332

316333
// Generate code when module or regeneration trigger changes
@@ -342,6 +359,31 @@ const App: React.FC = (): React.JSX.Element => {
342359
}
343360
}, [project]);
344361

362+
// Handle language changes with automatic saving
363+
React.useEffect(() => {
364+
const handleLanguageChange = async () => {
365+
// Save current blocks before language change
366+
if (currentModule && areBlocksModified()) {
367+
try {
368+
await saveBlocks();
369+
} catch (e) {
370+
console.error('Failed to save blocks before language change:', e);
371+
}
372+
}
373+
374+
// Update toolbox after language change
375+
if (blocksEditor.current) {
376+
blocksEditor.current.updateToolbox(shownPythonToolboxCategories);
377+
}
378+
};
379+
380+
i18n.on('languageChanged', handleLanguageChange);
381+
382+
return () => {
383+
i18n.off('languageChanged', handleLanguageChange);
384+
};
385+
}, [currentModule, shownPythonToolboxCategories, i18n]);
386+
345387
const { Sider, Content } = Antd.Layout;
346388

347389
return (
@@ -392,6 +434,7 @@ const App: React.FC = (): React.JSX.Element => {
392434
<Content>
393435
<BlocklyComponent
394436
theme={theme}
437+
onWorkspaceRecreated={setupWorkspace}
395438
ref={blocklyComponent}
396439
/>
397440
</Content>

src/blocks/mrc_call_python_function.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -481,12 +481,14 @@ const CALL_PYTHON_FUNCTION = {
481481
returnType: this.mrcReturnType,
482482
args: [],
483483
};
484-
this.mrcArgs.forEach((arg) => {
485-
extraState.args.push({
486-
'name': arg.name,
487-
'type': arg.type,
484+
if (this.mrcArgs){
485+
this.mrcArgs.forEach((arg) => {
486+
extraState.args.push({
487+
'name': arg.name,
488+
'type': arg.type,
489+
});
488490
});
489-
});
491+
}
490492
if (this.mrcTooltip) {
491493
extraState.tooltip = this.mrcTooltip;
492494
}

src/blocks/mrc_class_method_def.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ const CLASS_METHOD_DEF = {
9595
this.mrcParameters = [];
9696
this.setPreviousStatement(false);
9797
this.setNextStatement(false);
98+
this.updateBlock_();
9899
},
99100
/**
100101
* Returns the state of this block as a JSON serializable object.

src/blocks/mrc_component.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ const COMPONENT = {
6969
this.setStyle(MRC_STYLE_COMPONENTS);
7070
this.appendDummyInput()
7171
.appendField(new Blockly.FieldTextInput(''), FIELD_NAME)
72-
.appendField('of type')
72+
.appendField(Blockly.Msg.OF_TYPE)
7373
.appendField(createFieldNonEditableText(''), FIELD_TYPE);
7474
this.setPreviousStatement(true, OUTPUT_NAME);
7575
this.setNextStatement(true, OUTPUT_NAME);
@@ -82,12 +82,14 @@ const COMPONENT = {
8282
const extraState: ComponentExtraState = {
8383
};
8484
extraState.params = [];
85-
this.mrcArgs.forEach((arg) => {
86-
extraState.params!.push({
87-
'name': arg.name,
88-
'type': arg.type,
85+
if (this.mrcArgs){
86+
this.mrcArgs.forEach((arg) => {
87+
extraState.params!.push({
88+
'name': arg.name,
89+
'type': arg.type,
90+
});
8991
});
90-
});
92+
}
9193
if (this.mrcImportModule) {
9294
extraState.importModule = this.mrcImportModule;
9395
}

src/blocks/mrc_event_handler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,11 @@ const EVENT_HANDLER = {
6565
*/
6666
init(this: EventHandlerBlock): void {
6767
this.appendDummyInput('TITLE')
68-
.appendField('When')
68+
.appendField(Blockly.Msg.WHEN)
6969
.appendField(createFieldNonEditableText('sender'), 'SENDER')
7070
.appendField(createFieldNonEditableText('eventName'), 'EVENT_NAME');
7171
this.appendDummyInput('PARAMS')
72-
.appendField('with');
72+
.appendField(Blockly.Msg.WITH);
7373
this.setOutput(false);
7474
this.setStyle(MRC_STYLE_EVENT_HANDLER);
7575
this.appendStatementInput('STACK').appendField('');

src/blocks/mrc_get_parameter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const GET_PARAMETER_BLOCK = {
4949
init: function(this: GetParameterBlock): void {
5050
this.setStyle(MRC_STYLE_VARIABLES);
5151
this.appendDummyInput()
52-
.appendField('parameter')
52+
.appendField(Blockly.Msg.PARAMETER)
5353
.appendField(createFieldNonEditableText('parameter'), 'PARAMETER_NAME');
5454

5555
this.setOutput(true, this.parameterType);
@@ -89,7 +89,7 @@ const GET_PARAMETER_BLOCK = {
8989
}
9090
// If we end up here it shouldn't be allowed
9191
block.unplug(true);
92-
blockBlock.setWarningText('Parameters can only go in their method\'s block.');
92+
blockBlock.setWarningText(Blockly.Msg.PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK);
9393
}
9494
},
9595
};

src/blocks/mrc_mechanism.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ const MECHANISM = {
5555
this.setStyle(MRC_STYLE_MECHANISMS);
5656
this.appendDummyInput()
5757
.appendField(new Blockly.FieldTextInput('my_mech'), 'NAME')
58-
.appendField('of type')
58+
.appendField(Blockly.Msg.OF_TYPE)
5959
.appendField(createFieldNonEditableText(''), 'TYPE');
6060
this.setPreviousStatement(true, OUTPUT_NAME);
6161
this.setNextStatement(true, OUTPUT_NAME);

src/blocks/mrc_mechanism_component_holder.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@ const MECHANISM_COMPONENT_HOLDER = {
7272
*/
7373
init: function (this: MechanismComponentHolderBlock): void {
7474
this.setInputsInline(false);
75-
this.appendStatementInput('MECHANISMS').setCheck(MECHANISM_OUTPUT).appendField('Mechanisms');
76-
this.appendStatementInput('COMPONENTS').setCheck(COMPONENT_OUTPUT).appendField('Components');
77-
this.appendStatementInput('EVENTS').setCheck(EVENT_OUTPUT).appendField('Events');
75+
this.appendStatementInput('MECHANISMS').setCheck(MECHANISM_OUTPUT).appendField(Blockly.Msg.MECHANISMS);
76+
this.appendStatementInput('COMPONENTS').setCheck(COMPONENT_OUTPUT).appendField(Blockly.Msg.COMPONENTS);
77+
this.appendStatementInput('EVENTS').setCheck(EVENT_OUTPUT).appendField(Blockly.Msg.EVENTS);
7878

7979

8080
this.setOutput(false);

src/blocks/mrc_misc_evaluate_but_ignore_result.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,13 @@ export const setup = function() {
3131
Blockly.Blocks[BLOCK_NAME] = {
3232
init: function() {
3333
this.appendValueInput('VALUE')
34-
.appendField('evaluate but ignore result')
34+
.appendField(Blockly.Msg.EVALUATE_BUT_IGNORE_RESULT)
3535
.setAlign(Blockly.inputs.Align.RIGHT);
3636
this.setPreviousStatement(true);
3737
this.setNextStatement(true);
3838
this.setStyle(MRC_STYLE_MISC);
3939
this.setTooltip(
40-
'Executes the connected block and ignores the result. ' +
41-
'Allows you to call a function and ignore the return value.');
40+
Blockly.Msg.EVALUATE_BUT_IGNORE_RESULT_TOOLTIP);
4241
},
4342
};
4443
};

0 commit comments

Comments
 (0)