diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index 0634f76d..86c04aa6 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -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", @@ -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": { @@ -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", diff --git a/src/i18n/locales/es/translation.json b/src/i18n/locales/es/translation.json index c4ecd328..00d654d5 100644 --- a/src/i18n/locales/es/translation.json +++ b/src/i18n/locales/es/translation.json @@ -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", @@ -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..." @@ -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", diff --git a/src/i18n/locales/he/translation.json b/src/i18n/locales/he/translation.json index 709d7bcc..47f337e4 100644 --- a/src/i18n/locales/he/translation.json +++ b/src/i18n/locales/he/translation.json @@ -34,7 +34,6 @@ "SPANISH": "ספרדית", "HEBREW": "עברית", "HELP": "עזרה", - "ABOUT": "אודות", "SELECT_HIDDEN": "בחר נסתר", "NO_HIDDEN_MECHANISMS": "אין מנגנונים נסתרים", "NO_HIDDEN_OPMODES": "אין אופמודים נסתרים", @@ -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": { @@ -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": "עם", diff --git a/src/reactComponents/ClassNameComponent.tsx b/src/reactComponents/ClassNameComponent.tsx index 16111e88..f13a2a97 100644 --- a/src/reactComponents/ClassNameComponent.tsx +++ b/src/reactComponents/ClassNameComponent.tsx @@ -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 { @@ -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 { diff --git a/src/reactComponents/Menu.tsx b/src/reactComponents/Menu.tsx index 68c50f80..66577856 100644 --- a/src/reactComponents/Menu.tsx +++ b/src/reactComponents/Menu.tsx @@ -153,7 +153,7 @@ function getMenuItems(t: (key: string) => string, project: storageProject.Projec ]), ]), getItem(t('HELP'), 'help', , [ - getItem(t('ABOUT') + '...', 'about', ), + getItem(t('ABOUT.TITLE') + '...', 'about', ), ]), ]; } diff --git a/src/reactComponents/Tabs.tsx b/src/reactComponents/Tabs.tsx index dfe96c53..f1fec91f 100644 --- a/src/reactComponents/Tabs.tsx +++ b/src/reactComponents/Tabs.tsx @@ -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); @@ -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; } @@ -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); @@ -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 => { const newTabs = props.tabList.filter((t) => t.key !== tab.key); props.setTabList(newTabs); @@ -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: , }, { 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: , }, { key: 'rename', - label: t('Rename...'), + label: t('RENAME_ELLIPSIS'), disabled: tab.type === TabType.ROBOT, onClick: () => handleOpenRenameModal(tab), icon: , }, { key: 'delete', - label: t('Delete...'), + label: t('DELETE_ELLIPSIS'), disabled: tab.type === TabType.ROBOT, icon: , onClick: () => handleDeleteTab(tab), }, { key: 'copy', - label: t('Copy...'), + label: t('COPY_ELLIPSIS'), disabled: tab.type === TabType.ROBOT, icon: , onClick: () => handleOpenCopyModal(tab), @@ -366,8 +366,7 @@ export function Component(props: TabsProps): React.JSX.Element { /> setRenameModalOpen(false)} onOk={() => { @@ -375,8 +374,8 @@ export function Component(props: TabsProps): React.JSX.Element { handleRename(currentTab.key, name); } }} - okText={t('Rename')} - cancelText={t('Cancel')} + okText={t('RENAME')} + cancelText={t('CANCEL')} > {currentTab && ( setCopyModalOpen(false)} onOk={() => { @@ -405,8 +403,8 @@ export function Component(props: TabsProps): React.JSX.Element { handleCopy(currentTab.key, name); } }} - okText={t('Copy')} - cancelText={t('Cancel')} + okText={t('COPY')} + cancelText={t('CANCEL')} > {currentTab && ( void; } -const THEME_OPTIONS: ThemeOption[] = [ - { - key: 'light', - name: 'Light Theme', - icon: , - description: 'Clean and bright interface for daytime use', - }, - { - key: 'dark', - name: 'Dark Theme', - icon: , - description: 'Easy on the eyes for low-light environments', - }, - { - key: 'tritanopia', - name: 'Tritanopia Theme', - icon: , - description: 'Designed for those with Tritanopia color blindness', - }, - { - key: 'tritanopia-dark', - name: 'Tritanopia Dark', - icon: , - description: 'Dark theme for those with Tritanopia color blindness', - }, - { - key: 'deuteranopia', - name: 'Deuteranopia Theme', - icon: , - description: 'Designed for those with Deuteranopia color blindness', - }, - { - key: 'deuteranopia-dark', - name: 'Deuteranopia Dark', - icon: , - description: 'Dark theme for those with Deuteranopia color blindness', - }, -]; - const ThemeModal: React.FC = ({ open, onClose, currentTheme, onThemeChange, }) => { + const { t } = I18Next.useTranslation(); const [selectedTheme, setSelectedTheme] = React.useState(currentTheme); + const THEME_OPTIONS: ThemeOption[] = [ + { + key: 'light', + name: t('THEME_MODAL.LIGHT'), + icon: , + description: t('THEME_MODAL.LIGHT_DESCRIPTION'), + }, + { + key: 'dark', + name: t('THEME_MODAL.DARK'), + icon: , + description: t('THEME_MODAL.DARK_DESCRIPTION'), + }, + { + key: 'tritanopia', + name: t('THEME_MODAL.TRITANOPIA'), + icon: , + description: t('THEME_MODAL.TRITANOPIA_DESCRIPTION'), + }, + { + key: 'tritanopia-dark', + name: t('THEME_MODAL.TRITANOPIA_DARK'), + icon: , + description: t('THEME_MODAL.TRITANOPIA_DARK_DESCRIPTION'), + }, + { + key: 'deuteranopia', + name: t('THEME_MODAL.DEUTERANOPIA'), + icon: , + description: t('THEME_MODAL.DEUTERANOPIA_DESCRIPTION'), + }, + { + key: 'deuteranopia-dark', + name: t('THEME_MODAL.DEUTERANOPIA_DARK'), + icon: , + description: t('THEME_MODAL.DEUTERANOPIA_DARK_DESCRIPTION'), + }, + ]; + React.useEffect(() => { setSelectedTheme(currentTheme); }, [currentTheme]); @@ -95,14 +98,14 @@ const ThemeModal: React.FC = ({ title={
- Theme Selection + {t('THEME_MODAL.SELECTION')}
} open={open} onCancel={handleCancel} footer={[ - Cancel + {t('CANCEL')} , = ({ onClick={handleApplyTheme} disabled={selectedTheme === currentTheme} > - Apply Theme + {t('THEME_MODAL.APPLY')} , ]} width={600} @@ -118,7 +121,7 @@ const ThemeModal: React.FC = ({ >
- Choose a theme that best suits your preference and working environment. + {t('THEME_MODAL.CHOOSE_DESCRIPTION')} @@ -176,7 +179,7 @@ const ThemeModal: React.FC = ({
- Primary + {t('THEME_MODAL.PRIMARY_BUTTON')}
@@ -190,8 +193,8 @@ const ThemeModal: React.FC = ({