diff --git a/src/ActuatorPanel.js b/src/ActuatorPanel.js
index bed210a..ad20fe3 100644
--- a/src/ActuatorPanel.js
+++ b/src/ActuatorPanel.js
@@ -10,6 +10,7 @@ import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import PauseIcon from '@mui/icons-material/Pause';
import CheckBoxIcon from '@mui/icons-material/CheckBox';
import SettingsIcon from '@mui/icons-material/Settings';
+import { useTranslation } from './i18n/LanguageContext';
const MAX_ACTUATOR_IDS = 256;
@@ -50,6 +51,7 @@ const ActuatorPanel = () => {
const [showIdSelector, setShowIdSelector] = useState(false);
const nodeId = 0;
+ const { t } = useTranslation();
const activeActuatorIds = enabledActuatorIds
.map((enabled, id) => enabled ? id : null)
@@ -282,9 +284,9 @@ const ActuatorPanel = () => {
const renderIdSelectorDialog = () => (
@@ -316,11 +318,11 @@ const ActuatorPanel = () => {
fullWidth
>
- Command Type Range Settings
+ {t('act.range_title')}
- Configure default ranges for each command type. These settings can be applied to all actuators.
+ {t('act.range_instruction')}
@@ -328,7 +330,7 @@ const ActuatorPanel = () => {
{COMMAND_TYPE_LABELS[0]}
- Unitless command range is fixed at -1 to 1
+ {t('act.unitless_fixed')}
@@ -339,7 +341,7 @@ const ActuatorPanel = () => {
{
{
onClick={() => applyRangesToAllOfType(1)}
fullWidth
>
- Apply
+ {t('act.apply')}
@@ -379,7 +381,7 @@ const ActuatorPanel = () => {
{
{
onClick={() => applyRangesToAllOfType(2)}
fullWidth
>
- Apply
+ {t('act.apply')}
@@ -419,7 +421,7 @@ const ActuatorPanel = () => {
{
{
onClick={() => applyRangesToAllOfType(3)}
fullWidth
>
- Apply
+ {t('act.apply')}
@@ -458,13 +460,13 @@ const ActuatorPanel = () => {
color="primary"
variant="contained"
>
- Apply All Ranges
+ {t('act.apply_all')}
@@ -553,7 +555,7 @@ const ActuatorPanel = () => {
onClick={() => setShowIdSelector(true)}
sx={{ textTransform: 'none' }}
>
- Actuator IDs ({activeActuatorIds.length})
+ {t('act.ids', { count: activeActuatorIds.length })}
@@ -566,14 +568,14 @@ const ActuatorPanel = () => {
onClick={() => setShowSettingsModal(true)}
sx={{ textTransform: 'none' }}
>
- Range Settings
+ {t('act.range_settings')}
- Broadcast Rate:
+ {t('act.broadcast_rate')}
{
width: '50%'
}}>
- ID: {actuator.actuator_id}
+ {t('act.id')} {actuator.actuator_id}
- Pos: {actuator.position !== null ? actuator.position.toFixed(3) : "NC"}
+ {t('act.pos')} {actuator.position !== null ? actuator.position.toFixed(3) : t('act.nc')}
- Force: {actuator.force !== null ? `${actuator.force.toFixed(2)} N` : "NC"}
+ {t('act.force')} {actuator.force !== null ? `${actuator.force.toFixed(2)} N` : t('act.nc')}
- Speed: {actuator.speed !== null ? `${actuator.speed.toFixed(2)} rad/s` : "NC"}
+ {t('act.speed')} {actuator.speed !== null ? `${actuator.speed.toFixed(2)} rad/s` : t('act.nc')}
- RAT: {actuator.power_rating_pct !== null
- ? actuator.power_rating_pct === 127
- ? "unknown"
- : `${actuator.power_rating_pct.toFixed(1)} %`
- : "NC"}
+ {t('act.rat')} {actuator.power_rating_pct !== null
+ ? actuator.power_rating_pct === 127
+ ? t('act.unknown')
+ : `${actuator.power_rating_pct.toFixed(1)} %`
+ : t('act.nc')}
@@ -668,10 +670,10 @@ const ActuatorPanel = () => {
variant="outlined"
sx={{ height: '30px', fontSize: '0.8rem' }}
>
-
-
-
-
+
+
+
+
@@ -700,7 +702,7 @@ const ActuatorPanel = () => {
fullWidth
size="small"
>
- Zero
+ {t('act.zero')}
@@ -749,7 +751,7 @@ const ActuatorPanel = () => {
}}>
- cmd: [{
+ {t('act.cmd')} [{
activeActuatorIds
.map(id => {
const type = commandTypes[id] || COMMAND_TYPES.UNITLESS;
@@ -768,7 +770,7 @@ const ActuatorPanel = () => {
startIcon={}
onClick={handleZeroAll}
>
- Zero All
+ {t('act.zero_all')}
diff --git a/src/App.js b/src/App.js
index 1adaf6d..0b009cc 100644
--- a/src/App.js
+++ b/src/App.js
@@ -17,8 +17,10 @@ import './css/index.css';
import ConnectionIndicators from './ConnectionIndicators';
import DnsIcon from '@mui/icons-material/Dns';
import LanIcon from '@mui/icons-material/Lan';
+import LanguageIcon from '@mui/icons-material/Language';
import CompactSidebar from './CompactSidebar';
import DynamicNodeIdServer from './services/DynamicNodeIdServer';
+import { LanguageProvider, useTranslation } from './i18n/LanguageContext';
window.mavlinkSession = new MavlinkSession();
window.localNode = new dronecan.Node({name: "com.vimdrones.web_gui"});
@@ -32,6 +34,17 @@ localNode.on('uavcan.protocol.file.Read.Request', (transfer) => {
});
const App = () => {
+ return (
+
+
+
+
+
+ );
+};
+
+const AppContent = () => {
+ const { t, language, setLanguage } = useTranslation();
const [nodes, setNodes] = useState({});
const [nodesUpdateTimestamp, setNodesUpdateTimestamp] = useState(0);
const [isConnected, setIsConnected] = useState(false);
@@ -75,7 +88,7 @@ const App = () => {
const handleConnectionStatusChange = (isConnected) => {
setIsConnected(isConnected);
showMessage(
- isConnected ? 'Successfully connected to device' : 'Disconnected from device',
+ isConnected ? t('app.connected') : t('app.disconnected'),
isConnected ? 'success' : 'info'
);
};
@@ -87,7 +100,7 @@ const App = () => {
if (window.localNode) {
window.localNode.changeBus(newBus);
- showMessage(`Switched to CAN bus ${newBus}`, 'info');
+ showMessage(t('app.bus_switched', { bus: newBus }), 'info');
}
};
@@ -119,16 +132,16 @@ const App = () => {
if (window.dnaServer.getStatus().isActive) {
window.dnaServer.stop();
setDnaServerActive(false);
- showMessage('DNA server stopped', 'info');
+ showMessage(t('app.dna_stopped'), 'info');
} else {
const success = window.dnaServer.start(1, 125);
setDnaServerActive(success);
- showMessage(success ? 'DNA server started' : 'Failed to start DNA server', success ? 'success' : 'error');
+ showMessage(success ? t('app.dna_started') : t('app.dna_failed'), success ? 'success' : 'error');
}
};
return (
-
+ <>
@@ -142,7 +155,7 @@ const App = () => {
- DroneCAN Web Tools
+ {t('app.title')}
@@ -175,10 +188,10 @@ const App = () => {
backgroundColor: 'background.paper',
}}
>
-
-
-
-
+
+
+
+
@@ -206,16 +219,27 @@ const App = () => {
}
} : {}}
>
- DNA
+ {t('app.dna')}
-
@@ -294,7 +318,7 @@ const App = () => {
{snackbarMessage}
-
+ >
);
};
diff --git a/src/BusMonitor.js b/src/BusMonitor.js
index 5ec1bb3..dad4220 100644
--- a/src/BusMonitor.js
+++ b/src/BusMonitor.js
@@ -10,6 +10,7 @@ import PauseIcon from '@mui/icons-material/Pause';
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import SaveIcon from '@mui/icons-material/Save';
import { toYaml } from './dronecan/message_format_utils';
+import { useTranslation } from './i18n/LanguageContext';
const BusMonitor = () => {
const [transfers, setTransfers] = useState([]);
@@ -18,6 +19,7 @@ const BusMonitor = () => {
const [selectedTransfer, setSelectedTransfer] = useState(null);
const [detailsOpen, setDetailsOpen] = useState(false);
const [messageYaml, setMessageYaml] = useState('');
+ const { t } = useTranslation();
const tableContainerRef = useRef(null);
const maxTransfers = 1000; // Maximum number of transfers to store
@@ -29,23 +31,23 @@ const BusMonitor = () => {
let yamlText = '';
if (transfer.data && transfer.data.toObj) {
const msgObj = transfer.data.toObj();
- yamlText = `### Message details\n`;
- yamlText += `Direction: ${transfer.direction}\n`;
- yamlText += `Time: ${transfer.timestamp}\n`;
- yamlText += `CAN ID: ${transfer.frameId}\n`;
- yamlText += `Source Node: ${transfer.sourceNodeId}\n`;
- yamlText += `Destination Node: ${transfer.destNodeId || 'Broadcast'}\n`;
- yamlText += `Data Type: ${transfer.dataType}\n\n`;
- yamlText += `### Message Payload\n`;
+ yamlText = `${t('bus.details_heading')}\n`;
+ yamlText += `${t('bus.detail_direction')} ${transfer.direction}\n`;
+ yamlText += `${t('bus.detail_time')} ${transfer.timestamp}\n`;
+ yamlText += `${t('bus.detail_can_id')} ${transfer.frameId}\n`;
+ yamlText += `${t('bus.detail_source')} ${transfer.sourceNodeId}\n`;
+ yamlText += `${t('bus.detail_dest')} ${transfer.destNodeId || t('bus.broadcast')}\n`;
+ yamlText += `${t('bus.detail_data_type')} ${transfer.dataType}\n\n`;
+ yamlText += `${t('bus.payload_heading')}\n`;
yamlText += toYaml(msgObj);
} else {
- yamlText = `No detailed payload data available for this transfer.\n\n`;
- yamlText += `Direction: ${transfer.direction}\n`;
- yamlText += `Time: ${transfer.timestamp}\n`;
- yamlText += `CAN ID: ${transfer.frameId}\n`;
- yamlText += `Hex Data: ${transfer.hexData}\n`;
- yamlText += `Source Node: ${transfer.sourceNodeId}\n`;
- yamlText += `Destination Node: ${transfer.destNodeId || 'Broadcast'}\n`;
+ yamlText = `${t('bus.no_payload')}\n\n`;
+ yamlText += `${t('bus.detail_direction')} ${transfer.direction}\n`;
+ yamlText += `${t('bus.detail_time')} ${transfer.timestamp}\n`;
+ yamlText += `${t('bus.detail_can_id')} ${transfer.frameId}\n`;
+ yamlText += `${t('bus.detail_hex_data')} ${transfer.hexData}\n`;
+ yamlText += `${t('bus.detail_source')} ${transfer.sourceNodeId}\n`;
+ yamlText += `${t('bus.detail_dest')} ${transfer.destNodeId || t('bus.broadcast')}\n`;
}
setMessageYaml(yamlText);
@@ -139,7 +141,7 @@ const BusMonitor = () => {
};
const exportToCSV = () => {
- const headers = ['Direction', 'Timestamp', 'CAN ID (Hex)', 'Hex Data', 'Src Node ID', 'Dst Node ID', 'Data Type', 'Raw Data'];
+ const headers = [t('bus.csv_direction'), t('bus.csv_timestamp'), t('bus.csv_can_id'), t('bus.csv_hex_data'), t('bus.csv_src'), t('bus.csv_dst'), t('bus.csv_data_type'), t('bus.csv_raw')];
const csvRows = [
headers.join(','),
...transfers.map(transfer => {
@@ -182,7 +184,7 @@ const BusMonitor = () => {
- Bus Monitor
+ {t('bus.title')}
{
size="small"
/>
}
- label={Auto Scroll}
+ label={{t('bus.auto_scroll')}}
labelPlacement="start"
/>
{
variant="outlined"
sx={{ ml: 1 }}
>
- Export
+ {t('bus.export')}
@@ -232,13 +234,13 @@ const BusMonitor = () => {
- Dir
- Time
- CAN ID
- Hex Data
- Src
- Dst
- Data Type
+ {t('bus.col_dir')}
+ {t('bus.col_time')}
+ {t('bus.col_can_id')}
+ {t('bus.col_hex_data')}
+ {t('bus.col_src')}
+ {t('bus.col_dst')}
+ {t('bus.col_data_type')}
@@ -297,7 +299,7 @@ const BusMonitor = () => {
}}
>
- Showing {transfers.length} of max {maxTransfers} transfers
+ {t('bus.showing', { count: transfers.length, max: maxTransfers })}
{isPaused && (
{
gap: '4px'
}}
>
- PAUSED
+ {t('bus.paused')}
)}
@@ -322,7 +324,7 @@ const BusMonitor = () => {
fullWidth
>
- Message Details
+ {t('bus.message_details')}
{selectedTransfer && (
{selectedTransfer.dataType} - {selectedTransfer.frameId}
@@ -347,7 +349,7 @@ const BusMonitor = () => {
- Close
+ {t('bus.close')}
diff --git a/src/ConfirmRestartModal.js b/src/ConfirmRestartModal.js
index 7ee92d5..098d616 100644
--- a/src/ConfirmRestartModal.js
+++ b/src/ConfirmRestartModal.js
@@ -1,21 +1,23 @@
import React from 'react';
import { Dialog, DialogTitle, DialogContent, DialogActions, Button, Typography } from '@mui/material';
+import { useTranslation } from './i18n/LanguageContext';
const ConfirmRestartModal = ({ open, onClose, onConfirm }) => {
+ const { t } = useTranslation();
return (
diff --git a/src/ConnectionSettingsModal.js b/src/ConnectionSettingsModal.js
index 6e7f6a5..05e8a56 100644
--- a/src/ConnectionSettingsModal.js
+++ b/src/ConnectionSettingsModal.js
@@ -11,6 +11,7 @@ import CloseIcon from '@mui/icons-material/Close';
import Visibility from '@mui/icons-material/Visibility';
import VisibilityOff from '@mui/icons-material/VisibilityOff';
import WebSerial from './web_serial';
+import { useTranslation } from './i18n/LanguageContext';
// Add this constant at the top of your file, outside the component
const USB_DEVICE_NAMES = {
@@ -115,10 +116,11 @@ const ConnectionSettingsModal = ({
// Add this state variable to track connection attempts in progress
const [connectionInProgress, setConnectionInProgress] = useState(false);
+ const { t } = useTranslation();
// Create a function to identify and index duplicate devices
const getPortDisplayName = (port, allPorts) => {
- if (!port) return "No port selected";
+ if (!port) return t('conn.no_port_selected');
// Try to extract the most user-friendly name possible
if (port.info && port.info.product) {
@@ -196,7 +198,7 @@ const ConnectionSettingsModal = ({
}
// Fallback for ports without specific info
- return "Serial Port";
+ return t('conn.serial_port');
};
// Moved from App.js - Lists available ports
@@ -249,7 +251,7 @@ const ConnectionSettingsModal = ({
setActiveConnection(null);
setActiveSerialProtocol(null);
onConnectionStatusChange(false);
- showMessage('Serial connection closed', 'info');
+ showMessage(t('conn.serial_closed'), 'info');
// Clear the forwarding interval
if (forwardingInterval) {
@@ -293,12 +295,12 @@ const ConnectionSettingsModal = ({
setActiveSerialProtocol(serialProtocol);
setConnectionInProgress(false);
onConnectionStatusChange(true);
- showMessage(`Serial ${serialProtocol === SERIAL_PROTOCOLS.SLCAN ? 'SLCAN' : 'MAVLink'} connection established`, 'success');
+ showMessage(serialProtocol === SERIAL_PROTOCOLS.SLCAN ? t('conn.serial_slcan_ok') : t('conn.serial_mavlink_ok'), 'success');
})
window.mavlinkSession.addWebSerialErrorHandler((error) => {
console.error('Serial connection error:', error);
- showMessage(`Serial connection failed: ${error.message || 'Could not connect to port'}`, 'error');
+ showMessage(t('conn.serial_failed', { error: error.message || t('conn.could_not_connect') }), 'error');
// Reset in-progress state on error
setConnectionInProgress(false);
});
@@ -306,7 +308,7 @@ const ConnectionSettingsModal = ({
window.mavlinkSession.webSerialConnect();
} catch (error) {
console.error('Serial connection error:', error);
- showMessage(`Serial connection failed: ${error.message || 'Could not connect to port'}`, 'error');
+ showMessage(t('conn.serial_failed', { error: error.message || t('conn.could_not_connect') }), 'error');
// Reset in-progress state on error
setConnectionInProgress(false);
}
@@ -317,7 +319,7 @@ const ConnectionSettingsModal = ({
}
} catch (error) {
console.error('Error with serial connection:', error);
- showMessage(`Serial error: ${error.message || 'Unknown error'}`, 'error');
+ showMessage(t('conn.serial_error', { error: error.message || t('conn.unknown_error') }), 'error');
// Reset in-progress state on any error
setConnectionInProgress(false);
}
@@ -327,7 +329,7 @@ const ConnectionSettingsModal = ({
const validateIpAddress = (input) => {
// Check if empty
if (!input) {
- return 'IP address is required';
+ return t('conn.ip_required');
}
// Allow "localhost"
@@ -342,7 +344,7 @@ const ConnectionSettingsModal = ({
for (const octet of octets) {
const num = parseInt(octet, 10);
if (isNaN(num) || num < 0 || num > 255 || octet !== num.toString()) {
- return 'Each part must be a number between 0-255';
+ return t('conn.ip_invalid_parts');
}
}
return '';
@@ -356,17 +358,17 @@ const ConnectionSettingsModal = ({
return '';
}
- return 'Invalid IP address or hostname';
+ return t('conn.ip_invalid');
};
const validatePort = (input) => {
if (!input) {
- return 'Port is required';
+ return t('conn.port_required');
}
const port = parseInt(input, 10);
if (isNaN(port) || port < 1 || port > 65535) {
- return 'Port must be between 1-65535';
+ return t('conn.port_range');
}
return '';
@@ -375,7 +377,7 @@ const ConnectionSettingsModal = ({
// Add validation for nodeId
const validateNodeId = (value) => {
const id = parseInt(value, 10);
- return (isNaN(id) || id < 1 || id > 127) ? 'Node ID must be between 1-127' : '';
+ return (isNaN(id) || id < 1 || id > 127) ? t('conn.node_id_range') : '';
};
// Update handler to propagate changes to parent
@@ -417,7 +419,7 @@ const ConnectionSettingsModal = ({
setActiveConnection(null);
setActiveSerialProtocol(null);
onConnectionStatusChange(false);
- showMessage('WebSocket connection closed', 'info');
+ showMessage(t('conn.ws_closed'), 'info');
if (forwardingInterval) {
clearInterval(forwardingInterval);
setForwardingInterval(null);
@@ -462,7 +464,7 @@ const ConnectionSettingsModal = ({
setActiveSerialProtocol(null);
onConnectionStatusChange(true);
setConnectionInProgress(false);
- showMessage('WebSocket connection established', 'success');
+ showMessage(t('conn.ws_connected'), 'success');
});
window.mavlinkSession.addWebSocketErrorHandler((error) => {
@@ -474,11 +476,11 @@ const ConnectionSettingsModal = ({
setActiveConnection(null);
onConnectionStatusChange(false);
setConnectionInProgress(false);
- let errorMsg = 'Connection failed';
+ let errorMsg = t('conn.ws_failed');
if (error && error.message) {
- errorMsg = `Connection failed: ${error.message}`;
+ errorMsg = t('conn.ws_failed_detail', { error: error.message });
} else if (typeof error === 'string') {
- errorMsg = `Connection failed: ${error}`;
+ errorMsg = t('conn.ws_failed_detail', { error: error });
}
showMessage(errorMsg, 'error');
});
@@ -490,7 +492,7 @@ const ConnectionSettingsModal = ({
}
} catch (error) {
console.error('Error with WebSocket connection:', error);
- showMessage(`WebSocket error: ${error.message || 'Unknown error'}`, 'error');
+ showMessage(t('conn.ws_error', { error: error.message || t('conn.unknown_error') }), 'error');
setConnectionInProgress(false);
}
};
@@ -521,11 +523,11 @@ const ConnectionSettingsModal = ({
>
- Adapter Settings
+ {t('conn.title')}
{activeConnection && (
- Serial Connection
+ {t('conn.serial_section')}
{/* Reduce gap */}
{/* Port and Baud Selection in same row */}
{/* Port Selection - takes more space */}
- Port
+ {t('conn.port')}
@@ -657,11 +659,11 @@ const ConnectionSettingsModal = ({
width: '100%',
bgcolor: activeConnection === 'websocket' ? 'rgba(0, 200, 83, 0.1)' : 'inherit'
}}>
- WebSocket Connection
+ {t('conn.ws_section')}
{/* Reduce gap */}
- {activeConnection === 'websocket' ? 'Disconnect' :
- connectionInProgress && !activeConnection ? 'Connecting...' : 'Connect'}
+ {activeConnection === 'websocket' ? t('conn.disconnect') :
+ connectionInProgress && !activeConnection ? t('conn.connecting') : t('conn.connect')}
@@ -712,7 +714,7 @@ const ConnectionSettingsModal = ({
{
const [serverEnabled, setServerEnabled] = useState(false);
@@ -23,6 +24,7 @@ const DNAServerModal = ({ open, onClose, showMessage }) => {
const [server, setServer] = useState(null);
const [operationInProgress, setOperationInProgress] = useState(false);
const [refreshInterval, setRefreshInterval] = useState(null);
+ const { t } = useTranslation();
const handleAllocationUpdate = () => {
console.log("Allocation update detected, refreshing list");
@@ -84,9 +86,9 @@ const DNAServerModal = ({ open, onClose, showMessage }) => {
}, 1000); // Check every 5 seconds
setRefreshInterval(interval);
- showMessage("DNA server started successfully", "success");
+ showMessage(t('dna.started'), "success");
} else {
- showMessage("Failed to start DNA server", "error");
+ showMessage(t('dna.failed_start'), "error");
}
} else {
// Stop the server
@@ -99,11 +101,11 @@ const DNAServerModal = ({ open, onClose, showMessage }) => {
setRefreshInterval(null);
}
- showMessage("DNA server stopped", "info");
+ showMessage(t('dna.stopped'), "info");
}
} catch (error) {
console.error("Error toggling DNA server:", error);
- showMessage(`Error: ${error.message}`, "error");
+ showMessage(t('dna.error', { error: error.message }), "error");
} finally {
setOperationInProgress(false);
}
@@ -124,7 +126,7 @@ const DNAServerModal = ({ open, onClose, showMessage }) => {
};
const validateNodeIdRange = () => {
- return minNodeId >= maxNodeId ? "Min ID must be less than Max ID" : "";
+ return minNodeId >= maxNodeId ? t('dna.invalid_range') : "";
};
const handleDeleteAllocation = (nodeId) => {
@@ -133,15 +135,15 @@ const DNAServerModal = ({ open, onClose, showMessage }) => {
const success = server.deleteAllocation(nodeId);
if (success) {
fetchCurrentAllocations();
- showMessage(`Node ID ${nodeId} allocation revoked`, "info");
+ showMessage(t('dna.revoked', { id: nodeId }), "info");
} else {
- showMessage(`Failed to revoke allocation for node ID ${nodeId}`, "error");
+ showMessage(t('dna.revoke_failed', { id: nodeId }), "error");
}
};
const handleRefreshAllocations = () => {
fetchCurrentAllocations();
- showMessage("Allocations refreshed", "info");
+ showMessage(t('dna.refreshed'), "info");
};
// Modify the modal close handler to not stop the server
@@ -208,11 +210,11 @@ const DNAServerModal = ({ open, onClose, showMessage }) => {
>
- Dynamic Node ID Allocation Server
+ {t('dna.title')}
{serverEnabled && (
{
bgcolor: serverEnabled ? 'rgba(0, 200, 83, 0.1)' : 'inherit'
}}>
- Server Control
+ {t('dna.control')}
: }
@@ -251,8 +253,8 @@ const DNAServerModal = ({ open, onClose, showMessage }) => {
disabled={operationInProgress}
size="small"
>
- {operationInProgress ? "Processing..." :
- serverEnabled ? "Stop" : "Start"}
+ {operationInProgress ? t('dna.processing') :
+ serverEnabled ? t('dna.stop') : t('dna.start')}
@@ -262,7 +264,7 @@ const DNAServerModal = ({ open, onClose, showMessage }) => {
{/* Min Node ID */}
{
}}
size="small"
error={minNodeId >= maxNodeId}
- helperText={minNodeId >= maxNodeId ? "Must be < Max" : ""}
+ helperText={minNodeId >= maxNodeId ? t('dna.must_lt_max') : ""}
/>
{/* Max Node ID */}
{
}}
size="small"
error={minNodeId >= maxNodeId}
- helperText={minNodeId >= maxNodeId ? "Must be > Min" : ""}
+ helperText={minNodeId >= maxNodeId ? t('dna.must_gt_min') : ""}
/>
@@ -315,8 +317,8 @@ const DNAServerModal = ({ open, onClose, showMessage }) => {
}
label={
- Persist Allocations
-
+ {t('dna.persist')}
+
@@ -339,9 +341,9 @@ const DNAServerModal = ({ open, onClose, showMessage }) => {
variant="body1" // Using body1 for smaller font size instead of subtitle1
sx={{ fontWeight: 500 }} // Adding some weight to make it still look like a title
>
- Allocated Node IDs ({allocatedNodes.length})
+ {t('dna.allocated', { count: allocatedNodes.length })}
-
+
{
{allocatedNodes.length === 0 ? (
- No node IDs allocated
+ {t('dna.no_allocations')}
) : (
@@ -374,9 +376,9 @@ const DNAServerModal = ({ open, onClose, showMessage }) => {
- NID
- UUID
- Action
+ {t('dna.col_nid')}
+ {t('dna.col_uuid')}
+ {t('dna.col_action')}
diff --git a/src/EditParamModal.js b/src/EditParamModal.js
index 3252de4..fe3c765 100644
--- a/src/EditParamModal.js
+++ b/src/EditParamModal.js
@@ -9,6 +9,7 @@ import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import StopIcon from '@mui/icons-material/Stop'; // Add this import for the stop button
import MusicNoteIcon from '@mui/icons-material/MusicNote';
import AM32_Rtttl from './am32_rtttl'; // Updated import to match class name
+import { useTranslation } from './i18n/LanguageContext';
const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
// Add a new state for tracking whether a tune is currently playing
@@ -21,6 +22,7 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
const [errorMessage, setErrorMessage] = useState(''); // For validation error messages
const [isValid, setIsValid] = useState(true); // Add a new state variable to track validation status
const [paramName, setParamName] = useState(""); // Add paramName to the component state
+ const { t } = useTranslation();
useEffect(() => {
const localNode = window.localNode;
@@ -84,7 +86,7 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
if (!isValidFormat) {
// Warn user but continue with a default tune
- setErrorMessage('Warning: Invalid RTTTL format! Using a default empty tune instead.');
+ setErrorMessage(t('edit.rtttl_warning'));
// Continue with a minimal valid RTTTL string
const tuneToParse = "Empty:d=4,o=5,b=120:";
result = AM32_Rtttl.to_am32_startup_melody(tuneToParse);
@@ -102,7 +104,7 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
console.log("Binary array values:", Array.from(result.data).slice(0, 30));
} catch (err) {
console.error("Error converting RTTTL to binary:", err);
- setErrorMessage(`Error saving tune: ${err.message || 'Unknown error'}`);
+ setErrorMessage(t('edit.error_saving', { error: err.message || t('edit.unknown') }));
// Provide an empty binary string (all zeros) as fallback
const emptyArray = new Uint8Array(128);
valueToSave = String.fromCharCode.apply(null, emptyArray);
@@ -126,30 +128,30 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
try {
// First validate that the tune has the basic RTTTL format (name:defaults:notes)
if (!tuneToPlay || !tuneToPlay.includes(':') || tuneToPlay.split(':').length !== 3) {
- setErrorMessage('Invalid RTTTL format! Format should be: name:defaults:notes');
+ setErrorMessage(t('edit.rtttl_invalid'));
return;
}
-
+
// Clear any previous error when successful
setErrorMessage('');
-
+
// Stop any currently playing tune before starting a new one
AM32_Rtttl.stopMelody();
-
+
// Play the new tune
AM32_Rtttl.playMelody(tuneToPlay);
setPreviewTune(tuneToPlay);
setIsPlaying(true);
-
+
// Set up an event listener to detect when audio context is closed or ends
const estimatedDuration = estimateTuneDuration(tuneToPlay);
setTimeout(() => {
setIsPlaying(false);
}, estimatedDuration + 500); // Add a small buffer
-
+
} catch (err) {
console.error("Error playing tune:", err);
- setErrorMessage(`Error playing tune: ${err.message || 'Unknown error'}`);
+ setErrorMessage(t('edit.error_playing', { error: err.message || t('edit.unknown') }));
setIsPlaying(false);
}
};
@@ -224,7 +226,7 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
const isValidFormat = stringValue.includes(':') && stringValue.split(':').length === 3;
if (!isValidFormat) {
- setErrorMessage('Invalid RTTTL format! Format should be: name:defaults:notes');
+ setErrorMessage(t('edit.rtttl_invalid'));
return false;
}
@@ -275,13 +277,13 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
// Validate against min/max if they exist
if ((min !== null && numericValue < min) || (max !== null && numericValue > max)) {
setIsValid(false);
- setErrorMessage(`Value must be between ${min !== null ? min : '-∞'} and ${max !== null ? max : '∞'}`);
+ setErrorMessage(t('edit.value_range', { min: min !== null ? min : '-∞', max: max !== null ? max : '∞' }));
} else {
setIsValid(true);
setErrorMessage('');
}
}
-
+
setValue(newValue);
};
@@ -322,7 +324,7 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
// Validate against min/max if they exist
if ((min !== null && numericValue < min) || (max !== null && numericValue > max)) {
setIsValid(false);
- setErrorMessage(`Value must be between ${min !== null ? min : '-∞'} and ${max !== null ? max : '∞'}`);
+ setErrorMessage(t('edit.value_range', { min: min !== null ? min : '-∞', max: max !== null ? max : '∞' }));
} else {
setIsValid(true);
setErrorMessage('');
@@ -340,14 +342,14 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
- Select Preset Tune
+ {t('edit.select_preset')}
handleValueChange(e.target.value)}
fullWidth
margin="dense"
multiline
rows={3}
- placeholder="Format: name:d=duration,o=octave,b=bpm:notes"
+ placeholder={t('edit.rtttl_placeholder')}
error={!isValid && value !== ''}
// Remove helperText to avoid layout issues
/>
-
+
{
{/* Add a simple instruction text below the field */}
- Enter RTTTL format tune or select a preset
+ {t('edit.rtttl_instruction')}
- RTTTL Format Guide
+ {t('edit.rtttl_guide_title')}
- • d=duration (1=whole, 2=half, 4=quarter, 8=eighth, 16=16th note)
- • o=octave (4-7 where 5 is default)
- • b=tempo (beats per minute)
- • Notes are: c, c#, d, d#, e, f, f#, g, g#, a, a#, b or h
- • Example: Beep:d=4,o=5,b=120:c
+ • {t('edit.rtttl_guide_duration')}
+ • {t('edit.rtttl_guide_octave')}
+ • {t('edit.rtttl_guide_tempo')}
+ • {t('edit.rtttl_guide_notes')}
+ • {t('edit.rtttl_guide_example')}
@@ -431,7 +433,7 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
if (param.fields.value.msg.fields.boolean_value) {
return (
- Enable/Disable:
+ {t('edit.enable_disable')}
setValue(e.target.checked ? 1 : 0)}
@@ -462,7 +464,7 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
return (
{
onChange={(e) => handleValueChange(e.target.value)}
fullWidth
margin="dense"
- helperText={isOutOfBounds ?
- `Value must be between ${min !== "" ? min : '-∞'} and ${max !== "" ? max : '∞'}` :
+ helperText={isOutOfBounds ?
+ t('edit.value_range', { min: min !== "" ? min : '-∞', max: max !== "" ? max : '∞' }) :
null
}
/>
@@ -506,8 +508,8 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
const renderParamNameField = (name) => (
{
/>
) : (
- {value !== undefined && value !== "" && value !== null ? value : "Unknown"}
+ {value !== undefined && value !== "" && value !== null ? value : t('edit.unknown')}
)}
@@ -554,7 +556,7 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
onClose={onClose}
sx={{ '& .MuiDialog-paper': { minWidth: isRTTTLEditor ? '600px' : '400px' } }}
>
- Edit Parameter
+ {t('edit.title')}
@@ -565,7 +567,7 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
{isString && !isRTTTLEditor && (
setValue(e.target.value)}
fullWidth
@@ -580,7 +582,7 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
{paramName === "STARTUP_TUNE" && isString ? (
- renderInfoField("Current RTTTL", (() => {
+ renderInfoField(t('edit.current_rtttl'), (() => {
try {
// Get the binary string value
const binaryString = paramValueField.toString();
@@ -595,27 +597,27 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
return AM32_Rtttl.from_am32_startup_melody(binaryData, "Tune");
} catch (err) {
console.error("Error converting binary data to RTTTL:", err);
- return "Error parsing melody data";
+ return t('edit.error_parsing_melody');
}
})(), true) // Pass true to indicate this is an RTTTL value
) : (
renderInfoField(
- "Current Value",
- isBoolean
- ? (paramValueField.value ? "True" : "False")
+ t('edit.current_value'),
+ isBoolean
+ ? (paramValueField.value ? t('edit.true') : t('edit.false'))
: isString
? paramValueField.toString()
: paramValueField.value
)
)}
{/* Only show default value when not STARTUP_TUNE */}
- {paramName !== "STARTUP_TUNE" && renderInfoField("Default Value", isBoolean ? (paramDefaultValue ? "True" : "False") : paramDefaultValue)}
+ {paramName !== "STARTUP_TUNE" && renderInfoField(t('edit.default_value'), isBoolean ? (paramDefaultValue ? t('param.true') : t('param.false')) : paramDefaultValue)}
{!isBoolean && !isString && !isRTTTLEditor && (
- {renderInfoField("Min Value", paramMinValue)}
- {renderInfoField("Max Value", paramMaxValue)}
+ {renderInfoField(t('edit.min_value'), paramMinValue)}
+ {renderInfoField(t('edit.max_value'), paramMaxValue)}
)}
{errorMessage && (
@@ -629,14 +631,14 @@ const EditParamModal = ({ open, onClose, nodeId, paramIndex }) => {
- Cancel
+ {t('edit.cancel')}
- Save
+ {t('edit.save')}
diff --git a/src/EscPanel.js b/src/EscPanel.js
index 520f344..75cea8c 100644
--- a/src/EscPanel.js
+++ b/src/EscPanel.js
@@ -4,6 +4,7 @@ import PanToolIcon from '@mui/icons-material/PanTool';
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import PauseIcon from '@mui/icons-material/Pause';
import dronecan from './dronecan';
+import { useTranslation } from './i18n/LanguageContext';
const commandValueType = new dronecan.DSDL.uavcan_equipment_esc_RawCommand().fields.cmd.value_type;
const CMD_MAX = Number(commandValueType.value_range.max);
@@ -27,6 +28,7 @@ const EscPanel = () => {
const [sendArming, setSendArming] = useState(false);
const [broadcastRate, setBroadcastRate] = useState(10); // Changed default to 10
const [isPaused, setIsPaused] = useState(false); // New state for pause toggle
+ const { t } = useTranslation();
// Add toggle pause function
const togglePause = () => {
@@ -269,7 +271,7 @@ const EscPanel = () => {
- Channels:
+ {t('esc.channels')}
{
fontSize: '0.6rem'
}}
>
- ⚠️ REMOVE PROPELLERS!
+ {t('esc.remove_propellers')}
@@ -317,27 +319,27 @@ const EscPanel = () => {
sx={{ p: 0.5 }}
/>
}
- label={Send Safety}
+ label={{t('esc.send_safety')}}
labelPlacement="start"
sx={{ ml: 0, mr: 1 }}
/>
- setSendArming(e.target.checked)}
+ setSendArming(e.target.checked)}
size="small"
sx={{ p: 0.5 }}
/>
- }
- label={Send Arming}
+ }
+ label={{t('esc.send_arming')}}
labelPlacement="start"
sx={{ ml: 0, mr: 2 }}
/>
{/* Broadcast Rate moved to the right */}
- Broadcast Rate:
+ {t('esc.broadcast_rate')}
{
flexGrow: 1,
}}>
- Index: {esc.esc_index}
- Err: {esc.error_count !== null ? esc.error_count : "NC"}
+ {t('esc.index')} {esc.esc_index}
+ {t('esc.error')} {esc.error_count !== null ? esc.error_count : t('esc.nc')}
- Temp: {esc.temperature !== null ? `${(esc.temperature - 273.15).toFixed(1)} °C` : "NC"}
+ {t('esc.temp')} {esc.temperature !== null ? `${(esc.temperature - 273.15).toFixed(1)} °C` : t('esc.nc')}
- Volt: {esc.voltage !== null ? `${esc.voltage.toFixed(2)} V` : "NC"}
+ {t('esc.volt')} {esc.voltage !== null ? `${esc.voltage.toFixed(2)} V` : t('esc.nc')}
- Curr: {esc.current !== null ? `${esc.current.toFixed(2)} A` : "NC"}
+ {t('esc.curr')} {esc.current !== null ? `${esc.current.toFixed(2)} A` : t('esc.nc')}
- RPM: {esc.rpm !== null ? Math.round(esc.rpm) : "NC"}
+ {t('esc.rpm')} {esc.rpm !== null ? Math.round(esc.rpm) : t('esc.nc')}
- RAT: {esc.power_rating_pct !== null ? `${esc.power_rating_pct.toFixed(1)} %` : "NC"}
+ {t('esc.rat')} {esc.power_rating_pct !== null ? `${esc.power_rating_pct.toFixed(1)} %` : t('esc.nc')}
@@ -446,7 +448,7 @@ const EscPanel = () => {
fullWidth
size="small"
>
- Stop
+ {t('esc.stop')}
@@ -493,7 +495,7 @@ const EscPanel = () => {
}}>
- cmd: [{getScaledCommands(thrustValues).join(', ')}]
+ {t('esc.cmd')} [{getScaledCommands(thrustValues).join(', ')}]
@@ -504,7 +506,7 @@ const EscPanel = () => {
startIcon={}
onClick={handleStopAll}
>
- Stop All
+ {t('esc.stop_all')}
diff --git a/src/FirmwareUpdateModal.js b/src/FirmwareUpdateModal.js
index bfcec13..6a2b36b 100644
--- a/src/FirmwareUpdateModal.js
+++ b/src/FirmwareUpdateModal.js
@@ -1,10 +1,11 @@
import React, { useState } from 'react';
-import {
- Dialog, DialogTitle, DialogContent, DialogActions,
+import {
+ Dialog, DialogTitle, DialogContent, DialogActions,
Button, Typography, LinearProgress, Box, Alert
} from '@mui/material';
import FileUploadIcon from '@mui/icons-material/FileUpload';
import FileServer from './FileServer';
+import { useTranslation } from './i18n/LanguageContext';
const FirmwareUpdateModal = ({ open, onClose, targetNodeId }) => {
const [firmwareFile, setFirmwareFile] = useState(null);
@@ -12,6 +13,7 @@ const FirmwareUpdateModal = ({ open, onClose, targetNodeId }) => {
const [updateProgress, setUpdateProgress] = useState(0);
const [updateStatus, setUpdateStatus] = useState(null); // 'idle', 'updating', 'success', 'error'
const [statusMessage, setStatusMessage] = useState('');
+ const { t } = useTranslation();
const handleFileChange = (event) => {
const file = event.target.files[0];
@@ -21,7 +23,7 @@ const FirmwareUpdateModal = ({ open, onClose, targetNodeId }) => {
const fileExtension = file.name.split('.').pop().toLowerCase();
if (fileExtension !== 'bin' && fileExtension !== 'hex') {
setUpdateStatus('error');
- setStatusMessage('Invalid file type. Please select a .bin or .hex firmware file.');
+ setStatusMessage(t('fw.invalid_file'));
return;
}
@@ -42,7 +44,7 @@ const FirmwareUpdateModal = ({ open, onClose, targetNodeId }) => {
.catch(error => {
console.error('Error loading firmware:', error);
setUpdateStatus('error');
- setStatusMessage('Failed to load firmware file');
+ setStatusMessage(t('fw.load_failed'));
});
};
@@ -79,13 +81,13 @@ const FirmwareUpdateModal = ({ open, onClose, targetNodeId }) => {
if (!localNode) {
setUpdateStatus('error');
- setStatusMessage('Local node not available');
+ setStatusMessage(t('fw.node_unavailable'));
return;
}
// Update UI state
setUpdateStatus('updating');
- setStatusMessage('Starting firmware update...');
+ setStatusMessage(t('fw.starting'));
setUpdateProgress(0);
// Register a progress callback with the FileServer
@@ -95,12 +97,12 @@ const FirmwareUpdateModal = ({ open, onClose, targetNodeId }) => {
setUpdateProgress(progress * 100);
// Update status message
- setStatusMessage(`Updating firmware: ${Math.round(progress * 100)}% (${offset}/${total} bytes)`);
+ setStatusMessage(t('fw.updating', { progress: Math.round(progress * 100), offset: offset, total: total }));
// When complete
if (eof) {
setUpdateStatus('success');
- setStatusMessage('Firmware update completed successfully!');
+ setStatusMessage(t('fw.success'));
// Clean up progress tracking
setTimeout(() => {
@@ -123,7 +125,7 @@ const FirmwareUpdateModal = ({ open, onClose, targetNodeId }) => {
if (!msg || msg.fields.error.value > 0) {
// Handle update failure
setUpdateStatus('error');
- setStatusMessage(`Update failed: code: ${msg.fields.error.value} ${msg.fields.optional_error_message.toString() || 'Unknown error'}`);
+ setStatusMessage(t('fw.update_failed', { code: msg.fields.error.value, message: msg.fields.optional_error_message.toString() || t('edit.unknown') }));
FileServer.unregisterProgressCallback(firmwarePath);
} else {
setUpdateStatus('updating');
@@ -134,16 +136,16 @@ const FirmwareUpdateModal = ({ open, onClose, targetNodeId }) => {
} catch (error) {
console.error('Error initiating firmware update:', error);
setUpdateStatus('error');
- setStatusMessage(`Failed to start update: ${error.message || 'Unknown error'}`);
+ setStatusMessage(t('fw.start_failed', { error: error.message || t('edit.unknown') }));
}
};
return (