Skip to content

Commit 9451faf

Browse files
authored
Merge pull request #265 from Open-STEM/FranksDev
Language support for Blockly
2 parents 1feaafb + 7d20195 commit 9451faf

15 files changed

Lines changed: 1072 additions & 628 deletions

File tree

public/CHANGELOG.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Version 2.0.23
1+
# Version 2.0.26
22

33
#### XRPCode V2 Beta What’s New
44
* Rewritten with modern web tools for more solid and expandable future

src/assets/images/privacy.svg

Lines changed: 1 addition & 0 deletions
Loading

src/components/blockly.tsx

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { registerFieldColour } from '@blockly/field-colour';
33
import { BlocklyWorkspace, Workspace } from 'react-blockly';
44
import BlocklyConfigs from '@components/blockly/xrp_blockly_configs';
55
import * as Blockly from 'blockly/core';
6-
import { setBlocklyLocale } from '@/utils/blockly-locales';
6+
import { applyBlocklyLocale, refreshBlocklyWorkspace } from '@/utils/blockly-locales';
77
import AppMgr, { EventType, LoginStatus, Themes } from '@/managers/appmgr';
88
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
99
import { useHotkeys } from 'react-hotkeys-hook';
@@ -214,8 +214,9 @@ function blocklyToPython(ws: Workspace) {
214214
* @returns
215215
*/
216216
function BlocklyEditor({ tabId, tabName }: BlocklyEditorProps) {
217-
/** Bumped when the shared toolbox JSON is updated (e.g. after XRP connect). */
217+
/** Bumped when the shared toolbox JSON is updated (e.g. after XRP connect or language change). */
218218
const [toolboxRevision, setToolboxRevision] = useState(0);
219+
const [language, setLanguage] = useState(i18n.language);
219220
const [isLoading, setIsLoading] = useState<boolean>(true);
220221
const [name, setName] = useState<string>(tabName);
221222
const nameRef = useRef(name);
@@ -259,8 +260,8 @@ function BlocklyEditor({ tabId, tabName }: BlocklyEditorProps) {
259260

260261
// Shallow copy gives react-blockly a new reference so its updateToolbox effect runs.
261262
const toolboxConfiguration = useMemo(
262-
() => ({ ...BlocklyConfigs.ToolboxJson }),
263-
[toolboxRevision],
263+
() => ({ ...BlocklyConfigs.getLocalizedToolboxJson() }) as typeof BlocklyConfigs.ToolboxJson,
264+
[toolboxRevision, language],
264265
);
265266

266267
useEffect(() => {
@@ -438,17 +439,27 @@ function BlocklyEditor({ tabId, tabName }: BlocklyEditorProps) {
438439
}
439440
}
440441

441-
// Set up language based on stored preference
442-
const setupLanguage = () => {
443-
const storedLanguage = localStorage.getItem(StorageKeys.LANGUAGE) || 'en';
444-
setBlocklyLocale(storedLanguage);
445-
};
446-
447-
// Call setupLanguage to initialize Blockly locale
448-
setupLanguage();
442+
applyBlocklyLocale(i18n.language);
449443

450444
}, [saveEditor, tabId, restoreViewportForTab, persistViewport, evaluateEditBlocked]);
451445

446+
useEffect(() => {
447+
const handleLanguageChanged = (lang: string) => {
448+
applyBlocklyLocale(lang);
449+
setLanguage(lang);
450+
setToolboxRevision((prev) => prev + 1);
451+
const ws = EditorMgr.getInstance().getEditorSession(tabId)?.workspace;
452+
if (ws) {
453+
refreshBlocklyWorkspace(ws);
454+
}
455+
};
456+
457+
i18n.on('languageChanged', handleLanguageChanged);
458+
return () => {
459+
i18n.off('languageChanged', handleLanguageChanged);
460+
};
461+
}, [tabId]);
462+
452463
useEffect(() => {
453464
evaluateEditBlocked();
454465
}, [evaluateEditBlocked]);

src/components/blockly/xrp_blockly_configs.ts

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,53 @@
11

22
import '@components/blockly/xrp_blocks';
3-
import '@components/blockly/xrp_blocks_python'
3+
import '@components/blockly/xrp_blocks_python';
4+
import i18n from '@/utils/i18n';
45

56
const InitialJson = {};
67

8+
export const BlocklyCategoryIds = {
9+
INDIVIDUAL_MOTORS: 'individualMotors',
10+
DRIVE_TRAIN: 'driveTrain',
11+
SERVOS: 'servos',
12+
SENSORS: 'sensors',
13+
REFLECTANCE: 'reflectance',
14+
CONTROL_BOARD: 'controlBoard',
15+
GAMEPAD: 'gamepad',
16+
THIRD_PARTY: 'thirdParty',
17+
} as const;
18+
19+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
20+
type ToolboxCategory = Record<string, any>;
21+
22+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
23+
function localizeToolboxCategory(category: ToolboxCategory): ToolboxCategory {
24+
const localized = { ...category };
25+
if (localized.categoryId) {
26+
localized.name = i18n.t(`blockly.toolbox.${localized.categoryId}`);
27+
}
28+
if (Array.isArray(localized.contents)) {
29+
localized.contents = localized.contents.map((item: ToolboxCategory) =>
30+
item.kind === 'CATEGORY' ? localizeToolboxCategory(item) : item,
31+
);
32+
}
33+
return localized;
34+
}
35+
36+
export function getLocalizedToolboxJson() {
37+
return {
38+
...ToolboxJson,
39+
contents: (ToolboxJson.contents as ToolboxCategory[]).map((item) =>
40+
item.kind === 'CATEGORY' ? localizeToolboxCategory(item) : item,
41+
),
42+
};
43+
}
44+
745
const ToolboxJson = {
846
kind: "categoryToolbox",
947
"contents": [
1048
{
1149
"kind": "CATEGORY",
50+
"categoryId": "individualMotors",
1251
"name": "Individual Motors",
1352
"colour": "#a55b65", // crimson red
1453
"contents": [
@@ -50,6 +89,7 @@ const ToolboxJson = {
5089
},
5190
{
5291
"kind": "CATEGORY",
92+
"categoryId": "driveTrain",
5393
"name": "DriveTrain",
5494
"colour": "#a5675b", // rust orange
5595
"contents": [
@@ -113,6 +153,7 @@ const ToolboxJson = {
113153
},
114154
{
115155
"kind": "CATEGORY",
156+
"categoryId": "servos",
116157
"name": "Servos",
117158
"colour": "#a55ba5", // purple/pink
118159
"contents": [
@@ -127,11 +168,13 @@ const ToolboxJson = {
127168
},
128169
{
129170
"kind": "CATEGORY",
171+
"categoryId": "sensors",
130172
"name": "Sensors",
131173
"colour": "#80a55b", // LIGHT GREEN
132174
"contents": [
133175
{
134176
"kind": "CATEGORY",
177+
"categoryId": "distance",
135178
"name": "Distance",
136179
"colour": "#80a55b",
137180
"contents": [
@@ -142,6 +185,7 @@ const ToolboxJson = {
142185
]},
143186
{
144187
"kind": "CATEGORY",
188+
"categoryId": "reflectance",
145189
"name": "Reflectance",
146190
"colour": "#80a55b",
147191
"contents": [
@@ -156,6 +200,7 @@ const ToolboxJson = {
156200
]},
157201
{
158202
"kind": "CATEGORY",
203+
"categoryId": "gyro",
159204
"name": "Gyro",
160205
"colour": "#80a55b",
161206
"contents": [
@@ -174,6 +219,7 @@ const ToolboxJson = {
174219
]},
175220
{
176221
"kind": "CATEGORY",
222+
"categoryId": "accelerometer",
177223
"name": "Accelerometer",
178224
"colour": "#80a55b",
179225
"contents": [
@@ -194,6 +240,7 @@ const ToolboxJson = {
194240
},
195241
{
196242
"kind": "CATEGORY",
243+
"categoryId": "controlBoard",
197244
"name": "Control Board",
198245
"colour": "#5ba580", // cool green
199246
"contents": [
@@ -217,6 +264,7 @@ const ToolboxJson = {
217264
},
218265
{
219266
"kind": "CATEGORY",
267+
"categoryId": "webServer",
220268
"name": "Web Server",
221269
"colour": "#5b99a5", // turquoise
222270
"contents": [
@@ -264,6 +312,7 @@ const ToolboxJson = {
264312
]
265313
},{
266314
"kind": "CATEGORY",
315+
"categoryId": "dashboard",
267316
"name": "Dashboard",
268317
"colour": "#0080ff", // bright blue
269318
"contents": [
@@ -291,6 +340,7 @@ const ToolboxJson = {
291340
},
292341
{
293342
"kind": "CATEGORY",
343+
"categoryId": "gamepad",
294344
"name": "Gamepad",
295345
"colour": "#ff9248", // turquoise
296346
"contents": [
@@ -346,6 +396,7 @@ const ToolboxJson = {
346396
"type": "logic_ternary"
347397
}
348398
],
399+
"categoryId": "logic",
349400
"name": "Logic",
350401
"colour": "#5b80a5" // slate blue
351402
},
@@ -383,6 +434,7 @@ const ToolboxJson = {
383434
"type": "controls_flow_statements"
384435
}
385436
],
437+
"categoryId": "loops",
386438
"name": "Loops",
387439
"colour": "#5ba55b" // grass green
388440
},
@@ -449,6 +501,7 @@ const ToolboxJson = {
449501
"type": "math_random_float"
450502
}
451503
],
504+
"categoryId": "math",
452505
"name": "Math",
453506
"colour": "#5b67a5" // indigo blue
454507
},
@@ -520,6 +573,7 @@ const ToolboxJson = {
520573
"type": "text_prompt_ext"
521574
}
522575
],
576+
"categoryId": "text",
523577
"name": "Text",
524578
"colour": "#5ba58c" // seafoam green
525579
},
@@ -582,17 +636,20 @@ const ToolboxJson = {
582636
"type": "lists_sort"
583637
}
584638
],
639+
"categoryId": "lists",
585640
"name": "Lists",
586641
"colour": "#745ba5" // eggplant purple
587642
},
588643
{
589644
"kind": "CATEGORY",
645+
"categoryId": "variables",
590646
"name": "Variables",
591647
"colour": "#a55b80", // fuschia
592648
"custom": "VARIABLE"
593649
},
594650
{
595651
"kind": "CATEGORY",
652+
"categoryId": "functions",
596653
"name": "Functions",
597654
"colour": "#995ba5", // purple
598655
"custom": "PROCEDURE"
@@ -608,7 +665,9 @@ const ToolboxJson = {
608665

609666
const BlocklyConfigs = {
610667
InitialJson,
611-
ToolboxJson
612-
}
668+
ToolboxJson,
669+
getLocalizedToolboxJson,
670+
BlocklyCategoryIds,
671+
};
613672

614673
export default BlocklyConfigs;

0 commit comments

Comments
 (0)