mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-21 10:15:04 +02:00
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>
This commit is contained in:
co-authored by
cadtoolbox
parent
e57ab8bf7e
commit
903f43d71a
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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 one or more lines are too long
+1
-1
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user