Skip to content

Commit da233a3

Browse files
committed
Adding ability to change language dynamically
1 parent 7e2ed44 commit da233a3

File tree

8 files changed

+133
-34
lines changed

8 files changed

+133
-34
lines changed

src/App.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +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 i18n from './i18n/config';
50+
import { useTranslation } from 'react-i18next';
5151

5252
/** Storage key for shown toolbox categories. */
5353
const SHOWN_TOOLBOX_CATEGORIES_KEY = 'shownPythonToolboxCategories';
@@ -87,6 +87,8 @@ const LAYOUT_BACKGROUND_COLOR = '#0F0';
8787
* project management, and user interface layout.
8888
*/
8989
const App: React.FC = (): React.JSX.Element => {
90+
const { t } = useTranslation();
91+
9092
const [alertErrorMessage, setAlertErrorMessage] = React.useState('');
9193
const [storage, setStorage] = React.useState<commonStorage.Storage | null>(null);
9294
const [currentModule, setCurrentModule] = React.useState<commonStorage.Module | null>(null);
@@ -102,7 +104,6 @@ const App: React.FC = (): React.JSX.Element => {
102104
const [rightCollapsed, setRightCollapsed] = React.useState(false);
103105
const [theme, setTheme] = React.useState('dark');
104106

105-
106107
const blocksEditor = React.useRef<editor.Editor | null>(null);
107108
const generatorContext = React.useRef<GeneratorContext | null>(null);
108109
const blocklyComponent = React.useRef<BlocklyComponentType | null>(null);
@@ -291,7 +292,8 @@ const App: React.FC = (): React.JSX.Element => {
291292
if (blocksEditor.current) {
292293
blocksEditor.current.loadModuleBlocks(currentModule);
293294
}
294-
}, [currentModule]);
295+
}, [currentModule]);
296+
295297

296298
// Initialize Blockly workspace and editor when component and storage are ready
297299
React.useEffect(() => {

src/blocks/mrc_call_python_function.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -421,12 +421,14 @@ const CALL_PYTHON_FUNCTION = {
421421
returnType: this.mrcReturnType,
422422
args: [],
423423
};
424-
this.mrcArgs.forEach((arg) => {
425-
extraState.args.push({
426-
'name': arg.name,
427-
'type': arg.type,
424+
if(this.mrcArgs){
425+
this.mrcArgs.forEach((arg) => {
426+
extraState.args.push({
427+
'name': arg.name,
428+
'type': arg.type,
429+
});
428430
});
429-
});
431+
}
430432
if (this.mrcTooltip) {
431433
extraState.tooltip = this.mrcTooltip;
432434
}

src/blocks/mrc_component.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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/i18n/locales/en/translation.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,20 @@
2020
"newItemPlaceholder": "Add Module",
2121
"search": "Search..."
2222
},
23+
"PROJECT": "Project",
24+
"SAVE": "Save",
25+
"DEPLOY": "Deploy",
26+
"MANAGE": "Manage",
27+
"EXPLORER": "Explorer",
28+
"ROBOT": "Robot",
29+
"SETTINGS": "Settings",
30+
"WPI_TOOLBOX": "WPI Toolbox",
31+
"THEME": "Theme",
32+
"LANGUAGE": "Language",
33+
"ENGLISH": "English",
34+
"SPANISH": "Spanish",
35+
"HELP": "Help",
36+
"ABOUT": "About",
2337
"BLOCKLY":{
2438
"OF_TYPE": "of type",
2539
"WITH": "with",

src/i18n/locales/es/translation.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,20 @@
1515
"example_mechanism": "Por ejemplo: DisparadorDePiezas",
1616
"example_opmode": "Por ejemplo: AutoEstacionarYDisparar",
1717
"example_project": "Por ejemplo: RobotRuedasLocas",
18+
"PROJECT": "Proyecto",
19+
"SAVE": "Guardar",
20+
"DEPLOY": "Desplegar",
21+
"MANAGE": "Gestionar",
22+
"EXPLORER": "Explorador",
23+
"ROBOT": "Robot",
24+
"SETTINGS": "Configuración",
25+
"WPI_TOOLBOX": "Caja de Herramientas WPI",
26+
"THEME": "Tema",
27+
"LANGUAGE": "Idioma",
28+
"ENGLISH": "Inglés",
29+
"SPANISH": "Español",
30+
"HELP": "Ayuda",
31+
"ABOUT": "Acerca de",
1832
"addTabDialog": {
1933
"title": "Agregar Pestaña",
2034
"newItemPlaceholder": "Agregar Módulo",

src/reactComponents/BlocklyComponent.tsx

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ const BlocklyComponent = React.forwardRef<BlocklyComponentType | null, BlocklyCo
8484
const workspaceRef = React.useRef<Blockly.WorkspaceSvg | null>(null);
8585

8686
const { t, i18n } = useTranslation();
87-
// const { t } = useTranslation();
8887

8988

9089
const getBlocklyTheme = (): Blockly.Theme => {
@@ -135,6 +134,46 @@ const BlocklyComponent = React.forwardRef<BlocklyComponentType | null, BlocklyCo
135134
},
136135
});
137136

137+
/** Updates the Blockly locale when the language changes. */
138+
const updateBlocklyLocale = (): void => {
139+
if (!workspaceRef.current) {
140+
return;
141+
}
142+
143+
// Save the current workspace state
144+
const workspaceXml = Blockly.Xml.workspaceToDom(workspaceRef.current);
145+
146+
// Dispose of the current workspace
147+
workspaceRef.current.dispose();
148+
149+
// Set new locale
150+
switch (i18n.language) {
151+
case 'es':
152+
Blockly.setLocale(Es as any);
153+
break;
154+
case 'en':
155+
Blockly.setLocale(En as any);
156+
break;
157+
default:
158+
Blockly.setLocale(En as any);
159+
break;
160+
}
161+
162+
// Apply custom tokens
163+
Blockly.setLocale(customTokens(t));
164+
165+
// Re-create workspace with new locale
166+
const workspaceConfig = createWorkspaceConfig();
167+
const newWorkspace = Blockly.inject(blocklyDiv.current!, workspaceConfig);
168+
workspaceRef.current = newWorkspace;
169+
170+
// Restore the workspace state (this will create blocks with new locale)
171+
Blockly.Xml.domToWorkspace(workspaceXml, newWorkspace);
172+
173+
// Re-apply any event listeners that were on the original workspace
174+
// You may need to call your setup functions here again
175+
};
176+
138177
/** Initializes the Blockly workspace. */
139178
const initializeWorkspace = (): void => {
140179
if (!blocklyDiv.current) {
@@ -154,6 +193,7 @@ const BlocklyComponent = React.forwardRef<BlocklyComponentType | null, BlocklyCo
154193
break;
155194
}
156195
Blockly.setLocale(customTokens(t));
196+
157197
// Create workspace
158198
const workspaceConfig = createWorkspaceConfig();
159199
const workspace = Blockly.inject(blocklyDiv.current, workspaceConfig);
@@ -212,6 +252,12 @@ const BlocklyComponent = React.forwardRef<BlocklyComponentType | null, BlocklyCo
212252
}
213253
}, [theme]);
214254

255+
// Add this: Update locale when language changes
256+
React.useEffect(() => {
257+
console.log('Updating Blockly locale for language:', i18n.language);
258+
updateBlocklyLocale();
259+
}, [i18n.language, t]);
260+
215261
// Handle workspace resize
216262
React.useEffect(() => {
217263
return setupResizeObserver();

src/reactComponents/Header.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import * as Antd from 'antd';
2222
import * as commonStorage from '../storage/common_storage';
2323
import * as React from 'react';
24+
import { useTranslation } from 'react-i18next';
2425

2526
/** Function type for setting string values. */
2627
type StringFunction = (input: string) => void;
@@ -50,6 +51,7 @@ const TEXT_PADDING_LEFT = 20;
5051
*/
5152
export default function Header(props: HeaderProps): React.JSX.Element {
5253
const { token } = Antd.theme.useToken();
54+
const { t } = useTranslation();
5355

5456
const isDarkTheme = token.colorBgLayout === '#000000';
5557

@@ -104,7 +106,7 @@ export default function Header(props: HeaderProps): React.JSX.Element {
104106
fontWeight: 'normal',
105107
}}
106108
>
107-
Project: {getProjectName()}
109+
{t("PROJECT")}: {getProjectName()}
108110
</Antd.Typography>
109111
{renderErrorAlert()}
110112
</Antd.Flex>

src/reactComponents/Menu.tsx

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import {
3535
QuestionCircleOutlined,
3636
InfoCircleOutlined,
3737
BgColorsOutlined,
38+
GlobalOutlined,
39+
CheckOutlined,
3840
} from '@ant-design/icons';
3941
import FileManageModal from './FileManageModal';
4042
import ProjectManageModal from './ProjectManageModal';
@@ -92,7 +94,7 @@ function getDivider(): MenuItem {
9294
/**
9395
* Generates menu items for a given project.
9496
*/
95-
function getMenuItems(t: (key: string) => string, project: commonStorage.Project): MenuItem[] {
97+
function getMenuItems(t: (key: string) => string, project: commonStorage.Project, currentLanguage: string): MenuItem[] {
9698
const mechanisms: MenuItem[] = [];
9799
const opmodes: MenuItem[] = [];
98100

@@ -123,24 +125,35 @@ function getMenuItems(t: (key: string) => string, project: commonStorage.Project
123125
opmodes.push(getItem('Manage...', 'manageOpmodes'));
124126

125127
return [
126-
getItem(t('Project'), 'project', <FolderOutlined />, [
127-
getItem(t('Save'), 'save', <SaveOutlined />),
128-
getItem(t('Deploy'), 'deploy', undefined, undefined, true),
128+
getItem(t('PROJECT'), 'project', <FolderOutlined />, [
129+
getItem(t('SAVE'), 'save', <SaveOutlined />),
130+
getItem(t('DEPLOY'), 'deploy', undefined, undefined, true),
129131
getDivider(),
130-
getItem(t('Manage') + '...', 'manageProjects'),
132+
getItem(t('MANAGE') + '...', 'manageProjects'),
131133
]),
132-
getItem(t('Explorer'), 'explorer', <FileOutlined />, [
133-
getItem(t('Robot'), project.robot.modulePath, <RobotOutlined />),
134-
getItem(t('Mechanisms'), 'mechanisms', <BlockOutlined />, mechanisms),
135-
getItem(t('OpModes'), 'opmodes', <CodeOutlined />, opmodes),
134+
getItem(t('EXPLORER'), 'explorer', <FileOutlined />, [
135+
getItem(t('ROBOT'), project.robot.modulePath, <RobotOutlined />),
136+
getItem(t('MECHANISMS'), 'mechanisms', <BlockOutlined />, mechanisms),
137+
getItem(t('OPMODES'), 'opmodes', <CodeOutlined />, opmodes),
136138
]),
137-
getItem(t('Settings'), 'settings', <SettingOutlined />, [
138-
getItem(t('WPI toolbox'), 'wpi_toolbox'),
139-
getItem(t('Theme') + '...', 'theme', <BgColorsOutlined />)
139+
getItem(t('SETTINGS'), 'settings', <SettingOutlined />, [
140+
getItem(t('WPI_TOOLBOX'), 'wpi_toolbox'),
141+
getItem(t('THEME') + '...', 'theme', <BgColorsOutlined />),
142+
getItem(t('LANGUAGE'), 'language', <GlobalOutlined />, [
143+
getItem(
144+
t('ENGLISH'),
145+
'setlang:en',
146+
currentLanguage === 'en' ? <CheckOutlined /> : undefined
147+
),
148+
getItem(
149+
t('SPANISH'),
150+
'setlang:es',
151+
currentLanguage === 'es' ? <CheckOutlined /> : undefined
152+
),
153+
]),
140154
]),
141-
getItem(t('Help'), 'help', <QuestionCircleOutlined />, [
142-
getItem(t('About') + '...', 'about', <InfoCircleOutlined />
143-
),
155+
getItem(t('HELP'), 'help', <QuestionCircleOutlined />, [
156+
getItem(t('ABOUT') + '...', 'about', <InfoCircleOutlined />),
144157
]),
145158
];
146159
}
@@ -150,7 +163,7 @@ function getMenuItems(t: (key: string) => string, project: commonStorage.Project
150163
* Provides access to mechanisms, opmodes, and project management functionality.
151164
*/
152165
export function Component(props: MenuProps): React.JSX.Element {
153-
const {t} = I18Next.useTranslation();
166+
const {t, i18n} = I18Next.useTranslation();
154167

155168
const [projects, setProjects] = React.useState<commonStorage.Project[]>([]);
156169
const [menuItems, setMenuItems] = React.useState<MenuItem[]>([]);
@@ -264,8 +277,12 @@ export function Component(props: MenuProps): React.JSX.Element {
264277
props.openWPIToolboxSettings();
265278
} else if (key === 'theme') {
266279
setThemeModalOpen(true);
280+
} else if (key.startsWith('setlang:')) {
281+
const lang = key.split(':')[1];
282+
i18n.changeLanguage(lang);
267283
} else {
268284
// TODO: Handle other menu actions
285+
269286
console.log(`Selected key that wasn't module: ${key}`);
270287
}
271288
};
@@ -294,14 +311,14 @@ export function Component(props: MenuProps): React.JSX.Element {
294311
fetchMostRecentProject();
295312
}, [projects]);
296313

297-
// Update menu items and save project when project changes
314+
// Update menu items and save project when project or language changes
298315
React.useEffect(() => {
299316
if (props.project) {
300317
setMostRecentProject();
301-
setMenuItems(getMenuItems(t, props.project));
318+
setMenuItems(getMenuItems(t, props.project, i18n.language));
302319
setNoProjects(false);
303320
}
304-
}, [props.project]);
321+
}, [props.project, i18n.language]); // Add i18n.language dependency
305322

306323
return (
307324
<>

0 commit comments

Comments
 (0)