Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion src/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
"SPANISH": "Spanish",
"HEBREW": "Hebrew",
"HELP": "Help",
"ABOUT": "About",
"SELECT_HIDDEN": "Select Hidden",
"NO_HIDDEN_MECHANISMS": "No Hidden Mechanisms",
"NO_HIDDEN_OPMODES": "No Hidden Opmodes",
Expand Down Expand Up @@ -69,6 +68,13 @@
"COPY_TYPE_TITLE": "Copy {{type}}: {{title}}",
"RENAME": "Rename",
"NO_FILES_FOUND": "No {{type}} files found",
"DELETE": "Delete",
"CANCEL": "Cancel",
"CLOSE_TAB": "Close Tab",
"CLOSE_OTHER_TABS": "Close Other tabs",
"RENAME_ELLIPSIS": "Rename...",
"DELETE_ELLIPSIS": "Delete...",
"COPY_ELLIPSIS": "Copy...",
"MECHANISMS": "Mechanisms",
"OPMODES": "OpModes",
"ABOUT": {
Expand All @@ -89,6 +95,28 @@
"ERROR_LOADING_DEPENDENCIES": "Error loading dependencies.",
"COPYRIGHT": "© 2025 FIRST. All rights reserved."
},
"INVALID_CLASS_NAME": "{{name}} is not a valid name. Please enter a different name.",
"CLASS_NAME_ALREADY_EXISTS": "Another Mechanism or OpMode is already named {{name}}. Please enter a different name.",
"THEME_MODAL": {
"LIGHT": "Light Theme",
"LIGHT_DESCRIPTION": "Clean and bright interface for daytime use",
"DARK": "Dark Theme",
"DARK_DESCRIPTION": "Easy on the eyes for low-light environments",
"TRITANOPIA": "Tritanopia Theme",
"TRITANOPIA_DESCRIPTION": "Designed for those with Tritanopia color blindness",
"TRITANOPIA_DARK": "Tritanopia Dark",
"TRITANOPIA_DARK_DESCRIPTION": "Dark theme for those with Tritanopia color blindness",
"DEUTERANOPIA": "Deuteranopia Theme",
"DEUTERANOPIA_DESCRIPTION": "Designed for those with Deuteranopia color blindness",
"DEUTERANOPIA_DARK": "Deuteranopia Dark",
"DEUTERANOPIA_DARK_DESCRIPTION": "Dark theme for those with Deuteranopia color blindness",
"SELECTION": "Theme Selection",
"APPLY": "Apply Theme",
"CHOOSE_DESCRIPTION": "Choose a theme that best suits your preference and working environment.",
"PRIMARY_BUTTON": "Primary",
"PREVIEW": "Theme Preview",
"PREVIEW_DESCRIPTION": "The selected theme will be applied to the entire application interface."
},
"BLOCKLY":{
"OF_TYPE": "of type",
"WITH": "with",
Expand Down
30 changes: 29 additions & 1 deletion src/i18n/locales/es/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
"SPANISH": "Español",
"HEBREW": "Hebreo",
"HELP": "Ayuda",
"ABOUT": "Acerca de",
"SELECT_HIDDEN": "Seleccionar Oculto",
"NO_HIDDEN_MECHANISMS": "No Hay Mecanismos Ocultos",
"NO_HIDDEN_OPMODES": "No Hay Opmodes Ocultos",
Expand Down Expand Up @@ -66,6 +65,13 @@
"COPY_TYPE_TITLE": "Copiar {{type}}: {{title}}",
"RENAME": "Renombrar",
"NO_FILES_FOUND": "No se encontraron archivos de {{type}}",
"DELETE": "Eliminar",
"CANCEL": "Cancelar",
"CLOSE_TAB": "Cerrar Pestaña",
"CLOSE_OTHER_TABS": "Cerrar Otras Pestañas",
"RENAME_ELLIPSIS": "Renombrar...",
"DELETE_ELLIPSIS": "Eliminar...",
"COPY_ELLIPSIS": "Copiar...",
"addTabDialog": {
"title": "Agregar Pestaña",
"search": "Buscar..."
Expand All @@ -90,6 +96,28 @@
"ERROR_LOADING_DEPENDENCIES": "Error al cargar dependencias.",
"COPYRIGHT": "© 2025 FIRST. Todos los derechos reservados."
},
"INVALID_CLASS_NAME": "{{name}} no es un nombre válido. Por favor ingrese un nombre diferente.",
"CLASS_NAME_ALREADY_EXISTS": "Otro Mecanismo u OpMode ya se llama {{name}}. Por favor ingrese un nombre diferente.",
"THEME_MODAL": {
"LIGHT": "Tema Claro",
"LIGHT_DESCRIPTION": "Interfaz limpia y brillante para uso diurno",
"DARK": "Tema Oscuro",
"DARK_DESCRIPTION": "Fácil para los ojos en ambientes de poca luz",
"TRITANOPIA": "Tema Tritanopia",
"TRITANOPIA_DESCRIPTION": "Diseñado para personas con daltonismo Tritanopia",
"TRITANOPIA_DARK": "Tritanopia Oscuro",
"TRITANOPIA_DARK_DESCRIPTION": "Tema oscuro para personas con daltonismo Tritanopia",
"DEUTERANOPIA": "Tema Deuteranopia",
"DEUTERANOPIA_DESCRIPTION": "Diseñado para personas con daltonismo Deuteranopia",
"DEUTERANOPIA_DARK": "Deuteranopia Oscuro",
"DEUTERANOPIA_DARK_DESCRIPTION": "Tema oscuro para personas con daltonismo Deuteranopia",
"SELECTION": "Selección de Tema",
"APPLY": "Aplicar Tema",
"CHOOSE_DESCRIPTION": "Elija un tema que mejor se adapte a sus preferencias y entorno de trabajo.",
"PRIMARY_BUTTON": "Primario",
"PREVIEW": "Vista Previa del Tema",
"PREVIEW_DESCRIPTION": "El tema seleccionado se aplicará a toda la interfaz de la aplicación."
},
"BLOCKLY": {
"OF_TYPE": "de tipo",
"WITH": "con",
Expand Down
30 changes: 29 additions & 1 deletion src/i18n/locales/he/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
"SPANISH": "ספרדית",
"HEBREW": "עברית",
"HELP": "עזרה",
"ABOUT": "אודות",
"SELECT_HIDDEN": "בחר נסתר",
"NO_HIDDEN_MECHANISMS": "אין מנגנונים נסתרים",
"NO_HIDDEN_OPMODES": "אין אופמודים נסתרים",
Expand Down Expand Up @@ -69,6 +68,13 @@
"COPY_TYPE_TITLE": "העתקת {{type}}: {{title}}",
"RENAME": "שנה שם",
"NO_FILES_FOUND": "לא נמצאו קבצי {{type}}",
"DELETE": "מחק",
"CANCEL": "בטל",
"CLOSE_TAB": "סגור לשונית",
"CLOSE_OTHER_TABS": "סגור לשוניות אחרות",
"RENAME_ELLIPSIS": "שנה שם...",
"DELETE_ELLIPSIS": "מחק...",
"COPY_ELLIPSIS": "העתק...",
"MECHANISMS": "מנגנונים",
"OPMODES": "אופמודים",
"ABOUT": {
Expand All @@ -89,6 +95,28 @@
"ERROR_LOADING_DEPENDENCIES": "שגיאה בטעינת תלויות.",
"COPYRIGHT": "© 2025 FIRST. כל הזכויות שמורות."
},
"INVALID_CLASS_NAME": "{{name}} אינו שם תקין. אנא הזן שם אחר.",
"CLASS_NAME_ALREADY_EXISTS": "מנגנון או אופמוד אחר כבר נקרא {{name}}. אנא הזן שם אחר.",
"THEME_MODAL": {
"LIGHT": "ערכת נושא בהירה",
"LIGHT_DESCRIPTION": "ממשק נקי ובהיר לשימוש ביום",
"DARK": "ערכת נושא כהה",
"DARK_DESCRIPTION": "קל על העיניים בסביבות עם תאורה נמוכה",
"TRITANOPIA": "ערכת נושא טריטנופיה",
"TRITANOPIA_DESCRIPTION": "מעוצב עבור אנשים עם עיוורון צבעים טריטנופיה",
"TRITANOPIA_DARK": "טריטנופיה כהה",
"TRITANOPIA_DARK_DESCRIPTION": "ערכת נושא כהה עבור אנשים עם עיוורון צבעים טריטנופיה",
"DEUTERANOPIA": "ערכת נושא דיוטרנופיה",
"DEUTERANOPIA_DESCRIPTION": "מעוצב עבור אנשים עם עיוורון צבעים דיוטרנופיה",
"DEUTERANOPIA_DARK": "דיוטרנופיה כהה",
"DEUTERANOPIA_DARK_DESCRIPTION": "ערכת נושא כהה עבור אנשים עם עיוורון צבעים דיוטרנופיה",
"SELECTION": "בחירת ערכת נושא",
"APPLY": "החל ערכת נושא",
"CHOOSE_DESCRIPTION": "בחר ערכת נושא שמתאימה ביותר להעדפותיך וסביבת העבודה שלך.",
"PRIMARY_BUTTON": "ראשי",
"PREVIEW": "תצוגה מקדימה של ערכת נושא",
"PREVIEW_DESCRIPTION": "ערכת הנושא הנבחרת תוחל על כל ממשק האפליקציה."
},
"BLOCKLY": {
"OF_TYPE": "מטיפוס",
"WITH": "עם",
Expand Down
12 changes: 10 additions & 2 deletions src/reactComponents/ClassNameComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import * as I18Next from 'react-i18next';
import * as React from 'react';
import * as commonStorage from '../storage/common_storage';
import * as storageProject from '../storage/project';
import * as storageNames from '../storage/names';

/** Props for the ClassNameComponent. */
interface ClassNameComponentProps {
Expand Down Expand Up @@ -66,8 +67,15 @@ export default function ClassNameComponent(props: ClassNameComponentProps): Reac
return;
}

const {ok, error} = storageProject.isClassNameOk(props.project, newClassName);
if (ok) {
let error = '';

if (!storageNames.isValidClassName(newClassName)) {
error = t('INVALID_CLASS_NAME', { name: newClassName });
} else if (storageProject.findModuleByClassName(props.project!, newClassName) != null) {
error = t('CLASS_NAME_ALREADY_EXISTS', { name: newClassName });
}

if (!error) {
clearError();
props.onAddNewItem();
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/reactComponents/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ function getMenuItems(t: (key: string) => string, project: storageProject.Projec
]),
]),
getItem(t('HELP'), 'help', <QuestionCircleOutlined />, [
getItem(t('ABOUT') + '...', 'about', <InfoCircleOutlined />),
getItem(t('ABOUT.TITLE') + '...', 'about', <InfoCircleOutlined />),
]),
];
}
Expand Down
38 changes: 18 additions & 20 deletions src/reactComponents/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ export function Component(props: TabsProps): React.JSX.Element {
triggerProjectUpdate();
} catch (error) {
console.error('Error renaming module:', error);
props.setAlertErrorMessage('Failed to rename module');
props.setAlertErrorMessage(t('FAILED_TO_RENAME_MODULE'));
}

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

if (!originalTab) {
console.error('Original tab not found for copying:', key);
props.setAlertErrorMessage('Original tab not found for copying');
props.setAlertErrorMessage(t('MODULE_NOT_FOUND_FOR_COPYING'));
return;
}

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

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

modal.confirm({
title: `${t('Delete')} ${TabTypeUtils.toString(tab.type)}: ${titleToShow}`,
content: t('Are you sure you want to delete this? This action cannot be undone.'),
okText: t('Delete'),
title: t('DELETE_MODULE_CONFIRM', { title: `${TabTypeUtils.toString(tab.type)}: ${titleToShow}` }),
content: t('DELETE_CANNOT_BE_UNDONE'),
okText: t('DELETE'),
okType: 'danger',
cancelText: t('Cancel'),
cancelText: t('CANCEL'),
onOk: async (): Promise<void> => {
const newTabs = props.tabList.filter((t) => t.key !== tab.key);
props.setTabList(newTabs);
Expand All @@ -290,35 +290,35 @@ export function Component(props: TabsProps): React.JSX.Element {
const createTabContextMenuItems = (tab: TabItem): any[] => [
{
key: 'close',
label: t('Close Tab'),
label: t('CLOSE_TAB'),
onClick: () => handleTabEdit(tab.key, 'remove'),
disabled: tab.type === TabType.ROBOT,
icon: <CloseOutlined />,
},
{
key: 'close-others',
label: t('Close Other tabs'),
label: t('CLOSE_OTHER_TABS'),
onClick: () => handleCloseOtherTabs(tab.key),
disabled: props.tabList.length <= getMinTabsForCloseOthers(tab.type),
icon: <CloseCircleOutlined />,
},
{
key: 'rename',
label: t('Rename...'),
label: t('RENAME_ELLIPSIS'),
disabled: tab.type === TabType.ROBOT,
onClick: () => handleOpenRenameModal(tab),
icon: <EditOutlined />,
},
{
key: 'delete',
label: t('Delete...'),
label: t('DELETE_ELLIPSIS'),
disabled: tab.type === TabType.ROBOT,
icon: <DeleteOutlined />,
onClick: () => handleDeleteTab(tab),
},
{
key: 'copy',
label: t('Copy...'),
label: t('COPY_ELLIPSIS'),
disabled: tab.type === TabType.ROBOT,
icon: <CopyOutlined />,
onClick: () => handleOpenCopyModal(tab),
Expand Down Expand Up @@ -366,17 +366,16 @@ export function Component(props: TabsProps): React.JSX.Element {
/>

<Antd.Modal
title={`Rename ${currentTab ? TabTypeUtils.toString(currentTab.type) : ''}: ${currentTab ? currentTab.title : ''
}`}
title={t('RENAME_TYPE_TITLE', { type: currentTab ? TabTypeUtils.toString(currentTab.type) : '', title: currentTab ? currentTab.title : '' })}
open={renameModalOpen}
onCancel={() => setRenameModalOpen(false)}
onOk={() => {
if (currentTab) {
handleRename(currentTab.key, name);
}
}}
okText={t('Rename')}
cancelText={t('Cancel')}
okText={t('RENAME')}
cancelText={t('CANCEL')}
>
{currentTab && (
<ClassNameComponent
Expand All @@ -396,17 +395,16 @@ export function Component(props: TabsProps): React.JSX.Element {
</Antd.Modal>

<Antd.Modal
title={`Copy ${currentTab ? TabTypeUtils.toString(currentTab.type) : ''}: ${currentTab ? currentTab.title : ''
}`}
title={t('COPY_TYPE_TITLE', { type: currentTab ? TabTypeUtils.toString(currentTab.type) : '', title: currentTab ? currentTab.title : '' })}
open={copyModalOpen}
onCancel={() => setCopyModalOpen(false)}
onOk={() => {
if (currentTab) {
handleCopy(currentTab.key, name);
}
}}
okText={t('Copy')}
cancelText={t('Cancel')}
okText={t('COPY')}
cancelText={t('CANCEL')}
>
{currentTab && (
<ClassNameComponent
Expand Down
Loading