Browse Source

feat(i18n): update frontend components to use translations

- Add i18n support to AddSmartPlugModal.tsx
- Add i18n support to ConfigureAmsSlotModal.tsx
- Add i18n support to RichTextEditor.tsx
- Add i18n support to ExternalLinksSettings.tsx
- Replace all hardcoded English strings with t() calls

Co-authored-by: cadtoolbox <12723486+cadtoolbox@users.noreply.github.com>
copilot-swe-agent[bot] 3 months ago
parent
commit
903f43d71a

+ 20 - 18
frontend/src/components/AddSmartPlugModal.tsx

@@ -1,6 +1,7 @@
 import { useState, useEffect, useRef } from 'react';
 import { useMutation, useQueryClient, useQuery } from '@tanstack/react-query';
 import { X, Save, Loader2, Wifi, WifiOff, CheckCircle, Bell, Clock, LayoutGrid, Search, Plug, Power, Home, Radio, Eye } from 'lucide-react';
+import { useTranslation } from 'react-i18next';
 import { api } from '../api/client';
 import type { SmartPlug, SmartPlugCreate, SmartPlugUpdate, DiscoveredTasmotaDevice } from '../api/client';
 import { Button } from './Button';
@@ -11,6 +12,7 @@ interface AddSmartPlugModalProps {
 }
 
 export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
+  const { t } = useTranslation();
   const queryClient = useQueryClient();
   const isEditing = !!plug;
 
@@ -469,7 +471,7 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
               {isScanning && scanProgress.total > 0 && (
                 <div className="space-y-1">
                   <div className="flex justify-between text-xs text-bambu-gray">
-                    <span>Scanning network...</span>
+                    <span>{t('smartPlugs.addSmartPlug.scanningNetwork')}</span>
                     <span>{scanProgress.scanned} / {scanProgress.total}</span>
                   </div>
                   <div className="w-full bg-bambu-dark-tertiary rounded-full h-2">
@@ -538,7 +540,7 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
                       disabled
                       className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-bambu-gray cursor-not-allowed opacity-50"
                     >
-                      <option>Choose an entity...</option>
+                      <option>{t('smartPlugs.addSmartPlug.chooseEntity')}</option>
                     </select>
                   </div>
                 </div>
@@ -585,7 +587,7 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
                               setIsEntityDropdownOpen(true);
                               setHaEntitySearch('');
                             }}
-                            placeholder="Search entities..."
+                            placeholder={t('smartPlugs.addSmartPlug.placeholders.searchEntities')}
                             className="w-full pl-9 pr-8 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none"
                           />
                           {haEntityId && !isEntityDropdownOpen && (
@@ -697,7 +699,7 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
                                   setIsPowerDropdownOpen(true);
                                   setPowerSensorSearch('');
                                 }}
-                                placeholder="Search power sensors..."
+                                placeholder={t('smartPlugs.addSmartPlug.placeholders.searchPowerSensors')}
                                 className="w-full pl-9 pr-8 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none"
                               />
                               {haPowerEntity && !isPowerDropdownOpen && (
@@ -781,7 +783,7 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
                                   setIsEnergyTodayDropdownOpen(true);
                                   setEnergyTodaySearch('');
                                 }}
-                                placeholder="Search energy sensors..."
+                                placeholder={t('smartPlugs.addSmartPlug.placeholders.searchEnergySensors')}
                                 className="w-full pl-9 pr-8 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none"
                               />
                               {haEnergyTodayEntity && !isEnergyTodayDropdownOpen && (
@@ -865,7 +867,7 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
                                   setIsEnergyTotalDropdownOpen(true);
                                   setEnergyTotalSearch('');
                                 }}
-                                placeholder="Search energy sensors..."
+                                placeholder={t('smartPlugs.addSmartPlug.placeholders.searchEnergySensors')}
                                 className="w-full pl-9 pr-8 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none"
                               />
                               {haEnergyTotalEntity && !isEnergyTotalDropdownOpen && (
@@ -957,7 +959,7 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
                         type="text"
                         value={mqttPowerTopic}
                         onChange={(e) => setMqttPowerTopic(e.target.value)}
-                        placeholder="zigbee2mqtt/shelly-working-room"
+                        placeholder={t('smartPlugs.addSmartPlug.placeholders.mqttPowerTopic')}
                         className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none"
                       />
                     </div>
@@ -968,7 +970,7 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
                           type="text"
                           value={mqttPowerPath}
                           onChange={(e) => setMqttPowerPath(e.target.value)}
-                          placeholder="power_l1"
+                          placeholder={t('smartPlugs.addSmartPlug.placeholders.mqttPowerPath')}
                           className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none"
                         />
                       </div>
@@ -1009,7 +1011,7 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
                           type="text"
                           value={mqttEnergyPath}
                           onChange={(e) => setMqttEnergyPath(e.target.value)}
-                          placeholder="energy_l1"
+                          placeholder={t('smartPlugs.addSmartPlug.placeholders.mqttEnergyPath')}
                           className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none"
                         />
                       </div>
@@ -1050,7 +1052,7 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
                           type="text"
                           value={mqttStatePath}
                           onChange={(e) => setMqttStatePath(e.target.value)}
-                          placeholder="state_l1"
+                          placeholder={t('smartPlugs.addSmartPlug.placeholders.mqttStatePath')}
                           className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none"
                         />
                       </div>
@@ -1060,7 +1062,7 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
                           type="text"
                           value={mqttStateOnValue}
                           onChange={(e) => setMqttStateOnValue(e.target.value)}
-                          placeholder="ON, true, 1"
+                          placeholder={t('smartPlugs.addSmartPlug.placeholders.mqttStateOnValue')}
                           className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none"
                         />
                       </div>
@@ -1087,7 +1089,7 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
                     setIpAddress(e.target.value);
                     setTestResult(null);
                   }}
-                  placeholder="192.168.1.100"
+                  placeholder={t('smartPlugs.addSmartPlug.placeholders.ipAddress')}
                   className="flex-1 px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"
                 />
                 <Button
@@ -1128,7 +1130,7 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
               ) : (
                 <>
                   <WifiOff className="w-5 h-5" />
-                  <span>Connection failed</span>
+                  <span>{t('smartPlugs.addSmartPlug.connectionFailed')}</span>
                 </>
               )}
             </div>
@@ -1141,7 +1143,7 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
               type="text"
               value={name}
               onChange={(e) => setName(e.target.value)}
-              placeholder="Living Room Plug"
+              placeholder={t('smartPlugs.addSmartPlug.placeholders.name')}
               className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"
             />
           </div>
@@ -1156,7 +1158,7 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
                     type="text"
                     value={username}
                     onChange={(e) => setUsername(e.target.value)}
-                    placeholder="admin"
+                    placeholder={t('smartPlugs.addSmartPlug.placeholders.username')}
                     className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"
                   />
                 </div>
@@ -1166,7 +1168,7 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
                     type="password"
                     value={password}
                     onChange={(e) => setPassword(e.target.value)}
-                    placeholder="********"
+                    placeholder={t('smartPlugs.addSmartPlug.placeholders.password')}
                     className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"
                   />
                 </div>
@@ -1225,7 +1227,7 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
                       type="number"
                       value={powerAlertHigh}
                       onChange={(e) => setPowerAlertHigh(e.target.value)}
-                      placeholder="e.g. 200"
+                      placeholder={t('smartPlugs.addSmartPlug.placeholders.powerAlertHigh')}
                       min="0"
                       max="5000"
                       className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"
@@ -1237,7 +1239,7 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
                       type="number"
                       value={powerAlertLow}
                       onChange={(e) => setPowerAlertLow(e.target.value)}
-                      placeholder="e.g. 10"
+                      placeholder={t('smartPlugs.addSmartPlug.placeholders.powerAlertLow')}
                       min="0"
                       max="5000"
                       className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"

+ 7 - 5
frontend/src/components/ConfigureAmsSlotModal.tsx

@@ -1,6 +1,7 @@
 import { useState, useMemo, useEffect, useCallback } from 'react';
 import { useQuery, useMutation } from '@tanstack/react-query';
 import { X, Loader2, Settings2, ChevronDown, CheckCircle2, RotateCcw } from 'lucide-react';
+import { useTranslation } from 'react-i18next';
 import { api } from '../api/client';
 import type { KProfile } from '../api/client';
 import { Button } from './Button';
@@ -216,6 +217,7 @@ export function ConfigureAmsSlotModal({
   nozzleDiameter = '0.4',
   onSuccess,
 }: ConfigureAmsSlotModalProps) {
+  const { t } = useTranslation();
   const [selectedPresetId, setSelectedPresetId] = useState<string>('');
   const [selectedKProfile, setSelectedKProfile] = useState<KProfile | null>(null);
   const [colorHex, setColorHex] = useState<string>(''); // Just the 6-char hex, no alpha
@@ -569,7 +571,7 @@ export function ConfigureAmsSlotModal({
               <div className="text-center space-y-3">
                 <CheckCircle2 className="w-16 h-16 text-bambu-green mx-auto" />
                 <p className="text-lg font-semibold text-white">Slot Configured!</p>
-                <p className="text-sm text-bambu-gray">Settings sent to printer</p>
+                <p className="text-sm text-bambu-gray">{t('configureAmsSlot.settingsSentToPrinter')}</p>
               </div>
             </div>
           )}
@@ -607,7 +609,7 @@ export function ConfigureAmsSlotModal({
                 <div className="relative">
                   <input
                     type="text"
-                    placeholder="Search presets..."
+                    placeholder={t('configureAmsSlot.searchPresets')}
                     value={searchQuery}
                     onChange={(e) => setSearchQuery(e.target.value)}
                     className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white placeholder:text-bambu-gray focus:border-bambu-green focus:outline-none mb-2"
@@ -634,7 +636,7 @@ export function ConfigureAmsSlotModal({
                             <span className="text-white text-sm truncate">{preset.name}</span>
                             {isUserPreset(preset.setting_id) && (
                               <span className="text-xs px-1.5 py-0.5 rounded bg-bambu-blue/20 text-bambu-blue">
-                                Custom
+                                {t('configureAmsSlot.custom')}
                               </span>
                             )}
                           </div>
@@ -750,7 +752,7 @@ export function ConfigureAmsSlotModal({
                   />
                   <input
                     type="text"
-                    placeholder="Color name or hex (e.g., brown, FF8800)"
+                    placeholder={t('configureAmsSlot.colorPlaceholder')}
                     value={colorInput}
                     onChange={(e) => {
                       const input = e.target.value;
@@ -780,7 +782,7 @@ export function ConfigureAmsSlotModal({
                         setColorInput('');
                       }}
                       className="px-2 py-1 text-xs text-bambu-gray hover:text-white bg-bambu-dark-tertiary rounded"
-                      title="Clear custom color"
+                      title={t('configureAmsSlot.clearCustomColor')}
                     >
                       Clear
                     </button>

+ 5 - 3
frontend/src/components/ExternalLinksSettings.tsx

@@ -1,6 +1,7 @@
 import { useState } from 'react';
 import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
 import { Link2, Plus, Pencil, Trash2, GripVertical, Loader2, ExternalLink as ExternalLinkIcon } from 'lucide-react';
+import { useTranslation } from 'react-i18next';
 import { api } from '../api/client';
 import type { ExternalLink } from '../api/client';
 import { Card, CardContent, CardHeader } from './Card';
@@ -10,6 +11,7 @@ import { ConfirmModal } from './ConfirmModal';
 import { getIconByName } from './IconPicker';
 
 export function ExternalLinksSettings() {
+  const { t } = useTranslation();
   const queryClient = useQueryClient();
   const [showAddModal, setShowAddModal] = useState(false);
   const [editingLink, setEditingLink] = useState<ExternalLink | null>(null);
@@ -132,7 +134,7 @@ export function ExternalLinksSettings() {
                       <button
                         onClick={() => setEditingLink(link)}
                         className="p-2 rounded-lg hover:bg-bambu-dark-tertiary text-bambu-gray hover:text-white transition-colors"
-                        title="Edit"
+                        title={t('common.edit')}
                       >
                         <Pencil className="w-4 h-4" />
                       </button>
@@ -140,7 +142,7 @@ export function ExternalLinksSettings() {
                         onClick={() => handleDelete(link)}
                         disabled={deleteMutation.isPending}
                         className="p-2 rounded-lg hover:bg-red-500/20 text-bambu-gray hover:text-red-400 transition-colors disabled:opacity-50"
-                        title="Delete"
+                        title={t('externalLinks.deleteLink')}
                       >
                         <Trash2 className="w-4 h-4" />
                       </button>
@@ -152,7 +154,7 @@ export function ExternalLinksSettings() {
           ) : (
             <div className="text-center py-8 text-bambu-gray">
               <Link2 className="w-8 h-8 mx-auto mb-2 opacity-50" />
-              <p>No external links configured</p>
+              <p>{t('externalLinks.noLinksConfigured')}</p>
               <p className="text-sm">Click "Add Link" to add one</p>
             </div>
           )}

+ 12 - 10
frontend/src/components/RichTextEditor.tsx

@@ -18,6 +18,7 @@ import {
   Link as LinkIcon,
   Unlink,
 } from 'lucide-react';
+import { useTranslation } from 'react-i18next';
 
 interface RichTextEditorProps {
   content: string;
@@ -26,6 +27,7 @@ interface RichTextEditorProps {
 }
 
 export function RichTextEditor({ content, onChange, placeholder }: RichTextEditorProps) {
+  const { t } = useTranslation();
   const editor = useEditor({
     extensions: [
       StarterKit.configure({
@@ -105,21 +107,21 @@ export function RichTextEditor({ content, onChange, placeholder }: RichTextEdito
         <ToolbarButton
           onClick={() => editor.chain().focus().toggleBold().run()}
           isActive={editor.isActive('bold')}
-          title="Bold"
+          title={t('richTextEditor.bold')}
         >
           <Bold className="w-4 h-4" />
         </ToolbarButton>
         <ToolbarButton
           onClick={() => editor.chain().focus().toggleItalic().run()}
           isActive={editor.isActive('italic')}
-          title="Italic"
+          title={t('richTextEditor.italic')}
         >
           <Italic className="w-4 h-4" />
         </ToolbarButton>
         <ToolbarButton
           onClick={() => editor.chain().focus().toggleUnderline().run()}
           isActive={editor.isActive('underline')}
-          title="Underline"
+          title={t('richTextEditor.underline')}
         >
           <UnderlineIcon className="w-4 h-4" />
         </ToolbarButton>
@@ -129,14 +131,14 @@ export function RichTextEditor({ content, onChange, placeholder }: RichTextEdito
         <ToolbarButton
           onClick={() => editor.chain().focus().toggleBulletList().run()}
           isActive={editor.isActive('bulletList')}
-          title="Bullet List"
+          title={t('richTextEditor.bulletList')}
         >
           <List className="w-4 h-4" />
         </ToolbarButton>
         <ToolbarButton
           onClick={() => editor.chain().focus().toggleOrderedList().run()}
           isActive={editor.isActive('orderedList')}
-          title="Numbered List"
+          title={t('richTextEditor.numberedList')}
         >
           <ListOrdered className="w-4 h-4" />
         </ToolbarButton>
@@ -146,21 +148,21 @@ export function RichTextEditor({ content, onChange, placeholder }: RichTextEdito
         <ToolbarButton
           onClick={() => editor.chain().focus().setTextAlign('left').run()}
           isActive={editor.isActive({ textAlign: 'left' })}
-          title="Align Left"
+          title={t('richTextEditor.alignLeft')}
         >
           <AlignLeft className="w-4 h-4" />
         </ToolbarButton>
         <ToolbarButton
           onClick={() => editor.chain().focus().setTextAlign('center').run()}
           isActive={editor.isActive({ textAlign: 'center' })}
-          title="Align Center"
+          title={t('richTextEditor.alignCenter')}
         >
           <AlignCenter className="w-4 h-4" />
         </ToolbarButton>
         <ToolbarButton
           onClick={() => editor.chain().focus().setTextAlign('right').run()}
           isActive={editor.isActive({ textAlign: 'right' })}
-          title="Align Right"
+          title={t('richTextEditor.alignRight')}
         >
           <AlignRight className="w-4 h-4" />
         </ToolbarButton>
@@ -170,14 +172,14 @@ export function RichTextEditor({ content, onChange, placeholder }: RichTextEdito
         <ToolbarButton
           onClick={setLink}
           isActive={editor.isActive('link')}
-          title="Add Link"
+          title={t('richTextEditor.addLink')}
         >
           <LinkIcon className="w-4 h-4" />
         </ToolbarButton>
         {editor.isActive('link') && (
           <ToolbarButton
             onClick={() => editor.chain().focus().unsetLink().run()}
-            title="Remove Link"
+            title={t('richTextEditor.removeLink')}
           >
             <Unlink className="w-4 h-4" />
           </ToolbarButton>

File diff suppressed because it is too large
+ 0 - 0
static/assets/index-BA-rHKiq.js


+ 1 - 1
static/index.html

@@ -23,7 +23,7 @@
 
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-HOR_-s_H.js"></script>
+    <script type="module" crossorigin src="/assets/index-BA-rHKiq.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-BwfbnBQ9.css">
   </head>
   <body>

Some files were not shown because too many files changed in this diff