Skip to content

Commit 28c41c7

Browse files
authored
React components i18n (#268)
* Add i18n support for Tabs * Add i18n support for ThemeModal * Added translations for the error that can come from project.ts * Change back to Copy suffix * Refactor isClassNameOk into ClassNameComponent
1 parent 9565afd commit 28c41c7

File tree

8 files changed

+165
-96
lines changed

8 files changed

+165
-96
lines changed

src/i18n/locales/en/translation.json

Lines changed: 29 additions & 1 deletion
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",
@@ -69,6 +68,13 @@
6968
"COPY_TYPE_TITLE": "Copy {{type}}: {{title}}",
7069
"RENAME": "Rename",
7170
"NO_FILES_FOUND": "No {{type}} files found",
71+
"DELETE": "Delete",
72+
"CANCEL": "Cancel",
73+
"CLOSE_TAB": "Close Tab",
74+
"CLOSE_OTHER_TABS": "Close Other tabs",
75+
"RENAME_ELLIPSIS": "Rename...",
76+
"DELETE_ELLIPSIS": "Delete...",
77+
"COPY_ELLIPSIS": "Copy...",
7278
"MECHANISMS": "Mechanisms",
7379
"OPMODES": "OpModes",
7480
"ABOUT": {
@@ -89,6 +95,28 @@
8995
"ERROR_LOADING_DEPENDENCIES": "Error loading dependencies.",
9096
"COPYRIGHT": "© 2025 FIRST. All rights reserved."
9197
},
98+
"INVALID_CLASS_NAME": "{{name}} is not a valid name. Please enter a different name.",
99+
"CLASS_NAME_ALREADY_EXISTS": "Another Mechanism or OpMode is already named {{name}}. Please enter a different name.",
100+
"THEME_MODAL": {
101+
"LIGHT": "Light Theme",
102+
"LIGHT_DESCRIPTION": "Clean and bright interface for daytime use",
103+
"DARK": "Dark Theme",
104+
"DARK_DESCRIPTION": "Easy on the eyes for low-light environments",
105+
"TRITANOPIA": "Tritanopia Theme",
106+
"TRITANOPIA_DESCRIPTION": "Designed for those with Tritanopia color blindness",
107+
"TRITANOPIA_DARK": "Tritanopia Dark",
108+
"TRITANOPIA_DARK_DESCRIPTION": "Dark theme for those with Tritanopia color blindness",
109+
"DEUTERANOPIA": "Deuteranopia Theme",
110+
"DEUTERANOPIA_DESCRIPTION": "Designed for those with Deuteranopia color blindness",
111+
"DEUTERANOPIA_DARK": "Deuteranopia Dark",
112+
"DEUTERANOPIA_DARK_DESCRIPTION": "Dark theme for those with Deuteranopia color blindness",
113+
"SELECTION": "Theme Selection",
114+
"APPLY": "Apply Theme",
115+
"CHOOSE_DESCRIPTION": "Choose a theme that best suits your preference and working environment.",
116+
"PRIMARY_BUTTON": "Primary",
117+
"PREVIEW": "Theme Preview",
118+
"PREVIEW_DESCRIPTION": "The selected theme will be applied to the entire application interface."
119+
},
92120
"BLOCKLY":{
93121
"OF_TYPE": "of type",
94122
"WITH": "with",

src/i18n/locales/es/translation.json

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
"SPANISH": "Español",
3232
"HEBREW": "Hebreo",
3333
"HELP": "Ayuda",
34-
"ABOUT": "Acerca de",
3534
"SELECT_HIDDEN": "Seleccionar Oculto",
3635
"NO_HIDDEN_MECHANISMS": "No Hay Mecanismos Ocultos",
3736
"NO_HIDDEN_OPMODES": "No Hay Opmodes Ocultos",
@@ -66,6 +65,13 @@
6665
"COPY_TYPE_TITLE": "Copiar {{type}}: {{title}}",
6766
"RENAME": "Renombrar",
6867
"NO_FILES_FOUND": "No se encontraron archivos de {{type}}",
68+
"DELETE": "Eliminar",
69+
"CANCEL": "Cancelar",
70+
"CLOSE_TAB": "Cerrar Pestaña",
71+
"CLOSE_OTHER_TABS": "Cerrar Otras Pestañas",
72+
"RENAME_ELLIPSIS": "Renombrar...",
73+
"DELETE_ELLIPSIS": "Eliminar...",
74+
"COPY_ELLIPSIS": "Copiar...",
6975
"addTabDialog": {
7076
"title": "Agregar Pestaña",
7177
"search": "Buscar..."
@@ -90,6 +96,28 @@
9096
"ERROR_LOADING_DEPENDENCIES": "Error al cargar dependencias.",
9197
"COPYRIGHT": "© 2025 FIRST. Todos los derechos reservados."
9298
},
99+
"INVALID_CLASS_NAME": "{{name}} no es un nombre válido. Por favor ingrese un nombre diferente.",
100+
"CLASS_NAME_ALREADY_EXISTS": "Otro Mecanismo u OpMode ya se llama {{name}}. Por favor ingrese un nombre diferente.",
101+
"THEME_MODAL": {
102+
"LIGHT": "Tema Claro",
103+
"LIGHT_DESCRIPTION": "Interfaz limpia y brillante para uso diurno",
104+
"DARK": "Tema Oscuro",
105+
"DARK_DESCRIPTION": "Fácil para los ojos en ambientes de poca luz",
106+
"TRITANOPIA": "Tema Tritanopia",
107+
"TRITANOPIA_DESCRIPTION": "Diseñado para personas con daltonismo Tritanopia",
108+
"TRITANOPIA_DARK": "Tritanopia Oscuro",
109+
"TRITANOPIA_DARK_DESCRIPTION": "Tema oscuro para personas con daltonismo Tritanopia",
110+
"DEUTERANOPIA": "Tema Deuteranopia",
111+
"DEUTERANOPIA_DESCRIPTION": "Diseñado para personas con daltonismo Deuteranopia",
112+
"DEUTERANOPIA_DARK": "Deuteranopia Oscuro",
113+
"DEUTERANOPIA_DARK_DESCRIPTION": "Tema oscuro para personas con daltonismo Deuteranopia",
114+
"SELECTION": "Selección de Tema",
115+
"APPLY": "Aplicar Tema",
116+
"CHOOSE_DESCRIPTION": "Elija un tema que mejor se adapte a sus preferencias y entorno de trabajo.",
117+
"PRIMARY_BUTTON": "Primario",
118+
"PREVIEW": "Vista Previa del Tema",
119+
"PREVIEW_DESCRIPTION": "El tema seleccionado se aplicará a toda la interfaz de la aplicación."
120+
},
93121
"BLOCKLY": {
94122
"OF_TYPE": "de tipo",
95123
"WITH": "con",

src/i18n/locales/he/translation.json

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
"SPANISH": "ספרדית",
3535
"HEBREW": "עברית",
3636
"HELP": "עזרה",
37-
"ABOUT": "אודות",
3837
"SELECT_HIDDEN": "בחר נסתר",
3938
"NO_HIDDEN_MECHANISMS": "אין מנגנונים נסתרים",
4039
"NO_HIDDEN_OPMODES": "אין אופמודים נסתרים",
@@ -69,6 +68,13 @@
6968
"COPY_TYPE_TITLE": "העתקת {{type}}: {{title}}",
7069
"RENAME": "שנה שם",
7170
"NO_FILES_FOUND": "לא נמצאו קבצי {{type}}",
71+
"DELETE": "מחק",
72+
"CANCEL": "בטל",
73+
"CLOSE_TAB": "סגור לשונית",
74+
"CLOSE_OTHER_TABS": "סגור לשוניות אחרות",
75+
"RENAME_ELLIPSIS": "שנה שם...",
76+
"DELETE_ELLIPSIS": "מחק...",
77+
"COPY_ELLIPSIS": "העתק...",
7278
"MECHANISMS": "מנגנונים",
7379
"OPMODES": "אופמודים",
7480
"ABOUT": {
@@ -89,6 +95,28 @@
8995
"ERROR_LOADING_DEPENDENCIES": "שגיאה בטעינת תלויות.",
9096
"COPYRIGHT": "© 2025 FIRST. כל הזכויות שמורות."
9197
},
98+
"INVALID_CLASS_NAME": "{{name}} אינו שם תקין. אנא הזן שם אחר.",
99+
"CLASS_NAME_ALREADY_EXISTS": "מנגנון או אופמוד אחר כבר נקרא {{name}}. אנא הזן שם אחר.",
100+
"THEME_MODAL": {
101+
"LIGHT": "ערכת נושא בהירה",
102+
"LIGHT_DESCRIPTION": "ממשק נקי ובהיר לשימוש ביום",
103+
"DARK": "ערכת נושא כהה",
104+
"DARK_DESCRIPTION": "קל על העיניים בסביבות עם תאורה נמוכה",
105+
"TRITANOPIA": "ערכת נושא טריטנופיה",
106+
"TRITANOPIA_DESCRIPTION": "מעוצב עבור אנשים עם עיוורון צבעים טריטנופיה",
107+
"TRITANOPIA_DARK": "טריטנופיה כהה",
108+
"TRITANOPIA_DARK_DESCRIPTION": "ערכת נושא כהה עבור אנשים עם עיוורון צבעים טריטנופיה",
109+
"DEUTERANOPIA": "ערכת נושא דיוטרנופיה",
110+
"DEUTERANOPIA_DESCRIPTION": "מעוצב עבור אנשים עם עיוורון צבעים דיוטרנופיה",
111+
"DEUTERANOPIA_DARK": "דיוטרנופיה כהה",
112+
"DEUTERANOPIA_DARK_DESCRIPTION": "ערכת נושא כהה עבור אנשים עם עיוורון צבעים דיוטרנופיה",
113+
"SELECTION": "בחירת ערכת נושא",
114+
"APPLY": "החל ערכת נושא",
115+
"CHOOSE_DESCRIPTION": "בחר ערכת נושא שמתאימה ביותר להעדפותיך וסביבת העבודה שלך.",
116+
"PRIMARY_BUTTON": "ראשי",
117+
"PREVIEW": "תצוגה מקדימה של ערכת נושא",
118+
"PREVIEW_DESCRIPTION": "ערכת הנושא הנבחרת תוחל על כל ממשק האפליקציה."
119+
},
92120
"BLOCKLY": {
93121
"OF_TYPE": "מטיפוס",
94122
"WITH": "עם",

src/reactComponents/ClassNameComponent.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import * as I18Next from 'react-i18next';
2525
import * as React from 'react';
2626
import * as commonStorage from '../storage/common_storage';
2727
import * as storageProject from '../storage/project';
28+
import * as storageNames from '../storage/names';
2829

2930
/** Props for the ClassNameComponent. */
3031
interface ClassNameComponentProps {
@@ -66,8 +67,15 @@ export default function ClassNameComponent(props: ClassNameComponentProps): Reac
6667
return;
6768
}
6869

69-
const {ok, error} = storageProject.isClassNameOk(props.project, newClassName);
70-
if (ok) {
70+
let error = '';
71+
72+
if (!storageNames.isValidClassName(newClassName)) {
73+
error = t('INVALID_CLASS_NAME', { name: newClassName });
74+
} else if (storageProject.findModuleByClassName(props.project!, newClassName) != null) {
75+
error = t('CLASS_NAME_ALREADY_EXISTS', { name: newClassName });
76+
}
77+
78+
if (!error) {
7179
clearError();
7280
props.onAddNewItem();
7381
} else {

src/reactComponents/Menu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ function getMenuItems(t: (key: string) => string, project: storageProject.Projec
153153
]),
154154
]),
155155
getItem(t('HELP'), 'help', <QuestionCircleOutlined />, [
156-
getItem(t('ABOUT') + '...', 'about', <InfoCircleOutlined />),
156+
getItem(t('ABOUT.TITLE') + '...', 'about', <InfoCircleOutlined />),
157157
]),
158158
];
159159
}

src/reactComponents/Tabs.tsx

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ export function Component(props: TabsProps): React.JSX.Element {
186186
triggerProjectUpdate();
187187
} catch (error) {
188188
console.error('Error renaming module:', error);
189-
props.setAlertErrorMessage('Failed to rename module');
189+
props.setAlertErrorMessage(t('FAILED_TO_RENAME_MODULE'));
190190
}
191191

192192
setRenameModalOpen(false);
@@ -213,7 +213,7 @@ export function Component(props: TabsProps): React.JSX.Element {
213213

214214
if (!originalTab) {
215215
console.error('Original tab not found for copying:', key);
216-
props.setAlertErrorMessage('Original tab not found for copying');
216+
props.setAlertErrorMessage(t('MODULE_NOT_FOUND_FOR_COPYING'));
217217
return;
218218
}
219219

@@ -223,7 +223,7 @@ export function Component(props: TabsProps): React.JSX.Element {
223223
triggerProjectUpdate();
224224
} catch (error) {
225225
console.error('Error copying module:', error);
226-
props.setAlertErrorMessage('Failed to copy module');
226+
props.setAlertErrorMessage(t('FAILED_TO_COPY_MODULE'));
227227
}
228228

229229
setCopyModalOpen(false);
@@ -260,11 +260,11 @@ export function Component(props: TabsProps): React.JSX.Element {
260260
const titleToShow = currentTab ? currentTab.title : tab.title;
261261

262262
modal.confirm({
263-
title: `${t('Delete')} ${TabTypeUtils.toString(tab.type)}: ${titleToShow}`,
264-
content: t('Are you sure you want to delete this? This action cannot be undone.'),
265-
okText: t('Delete'),
263+
title: t('DELETE_MODULE_CONFIRM', { title: `${TabTypeUtils.toString(tab.type)}: ${titleToShow}` }),
264+
content: t('DELETE_CANNOT_BE_UNDONE'),
265+
okText: t('DELETE'),
266266
okType: 'danger',
267-
cancelText: t('Cancel'),
267+
cancelText: t('CANCEL'),
268268
onOk: async (): Promise<void> => {
269269
const newTabs = props.tabList.filter((t) => t.key !== tab.key);
270270
props.setTabList(newTabs);
@@ -290,35 +290,35 @@ export function Component(props: TabsProps): React.JSX.Element {
290290
const createTabContextMenuItems = (tab: TabItem): any[] => [
291291
{
292292
key: 'close',
293-
label: t('Close Tab'),
293+
label: t('CLOSE_TAB'),
294294
onClick: () => handleTabEdit(tab.key, 'remove'),
295295
disabled: tab.type === TabType.ROBOT,
296296
icon: <CloseOutlined />,
297297
},
298298
{
299299
key: 'close-others',
300-
label: t('Close Other tabs'),
300+
label: t('CLOSE_OTHER_TABS'),
301301
onClick: () => handleCloseOtherTabs(tab.key),
302302
disabled: props.tabList.length <= getMinTabsForCloseOthers(tab.type),
303303
icon: <CloseCircleOutlined />,
304304
},
305305
{
306306
key: 'rename',
307-
label: t('Rename...'),
307+
label: t('RENAME_ELLIPSIS'),
308308
disabled: tab.type === TabType.ROBOT,
309309
onClick: () => handleOpenRenameModal(tab),
310310
icon: <EditOutlined />,
311311
},
312312
{
313313
key: 'delete',
314-
label: t('Delete...'),
314+
label: t('DELETE_ELLIPSIS'),
315315
disabled: tab.type === TabType.ROBOT,
316316
icon: <DeleteOutlined />,
317317
onClick: () => handleDeleteTab(tab),
318318
},
319319
{
320320
key: 'copy',
321-
label: t('Copy...'),
321+
label: t('COPY_ELLIPSIS'),
322322
disabled: tab.type === TabType.ROBOT,
323323
icon: <CopyOutlined />,
324324
onClick: () => handleOpenCopyModal(tab),
@@ -366,17 +366,16 @@ export function Component(props: TabsProps): React.JSX.Element {
366366
/>
367367

368368
<Antd.Modal
369-
title={`Rename ${currentTab ? TabTypeUtils.toString(currentTab.type) : ''}: ${currentTab ? currentTab.title : ''
370-
}`}
369+
title={t('RENAME_TYPE_TITLE', { type: currentTab ? TabTypeUtils.toString(currentTab.type) : '', title: currentTab ? currentTab.title : '' })}
371370
open={renameModalOpen}
372371
onCancel={() => setRenameModalOpen(false)}
373372
onOk={() => {
374373
if (currentTab) {
375374
handleRename(currentTab.key, name);
376375
}
377376
}}
378-
okText={t('Rename')}
379-
cancelText={t('Cancel')}
377+
okText={t('RENAME')}
378+
cancelText={t('CANCEL')}
380379
>
381380
{currentTab && (
382381
<ClassNameComponent
@@ -396,17 +395,16 @@ export function Component(props: TabsProps): React.JSX.Element {
396395
</Antd.Modal>
397396

398397
<Antd.Modal
399-
title={`Copy ${currentTab ? TabTypeUtils.toString(currentTab.type) : ''}: ${currentTab ? currentTab.title : ''
400-
}`}
398+
title={t('COPY_TYPE_TITLE', { type: currentTab ? TabTypeUtils.toString(currentTab.type) : '', title: currentTab ? currentTab.title : '' })}
401399
open={copyModalOpen}
402400
onCancel={() => setCopyModalOpen(false)}
403401
onOk={() => {
404402
if (currentTab) {
405403
handleCopy(currentTab.key, name);
406404
}
407405
}}
408-
okText={t('Copy')}
409-
cancelText={t('Cancel')}
406+
okText={t('COPY')}
407+
cancelText={t('CANCEL')}
410408
>
411409
{currentTab && (
412410
<ClassNameComponent

0 commit comments

Comments
 (0)