- {spool.color_name || 'Unknown color'}
+ {colorName || t('spoolbuddy.spool.unknownColor')}
#{spool.id}
diff --git a/frontend/src/components/spoolbuddy/LinkSpoolModal.tsx b/frontend/src/components/spoolbuddy/LinkSpoolModal.tsx
index 014695f84..ad75b11e4 100644
--- a/frontend/src/components/spoolbuddy/LinkSpoolModal.tsx
+++ b/frontend/src/components/spoolbuddy/LinkSpoolModal.tsx
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
import { X } from 'lucide-react';
import type { InventorySpool } from '../../api/client';
import { SpoolIcon } from './SpoolIcon';
-import { spoolColorString } from '../../utils/colors';
+import { resolveSpoolColorName, spoolColorString } from '../../utils/colors';
interface LinkSpoolModalProps {
isOpen: boolean;
@@ -108,7 +108,7 @@ export function LinkSpoolModal({
- {spool.color_name || 'Unknown color'}
+ {resolveSpoolColorName(spool.color_name, spool.rgba, spool.color_name_is_synthesized) || t('spoolbuddy.spool.unknownColor')}
#{spool.id}
diff --git a/frontend/src/components/spoolbuddy/SpoolInfoCard.tsx b/frontend/src/components/spoolbuddy/SpoolInfoCard.tsx
index 3b01dea22..9bdeff719 100644
--- a/frontend/src/components/spoolbuddy/SpoolInfoCard.tsx
+++ b/frontend/src/components/spoolbuddy/SpoolInfoCard.tsx
@@ -4,7 +4,7 @@ import { Check, AlertTriangle, RefreshCw, Unlink } from 'lucide-react';
import type { MatchedSpool } from '../../hooks/useSpoolBuddyState';
import { spoolbuddyApi } from '../../api/client';
import { SpoolIcon } from './SpoolIcon';
-import { spoolColorString } from '../../utils/colors';
+import { resolveSpoolColorName, spoolColorString } from '../../utils/colors';
// Storage key for default core weight
const DEFAULT_CORE_WEIGHT_KEY = 'spoolbuddy-default-core-weight';
@@ -38,6 +38,10 @@ export function SpoolInfoCard({ spool, scaleWeight, onClose, onSyncWeight, onAss
const [synced, setSynced] = useState(false);
const colorHex = spoolColorString(spool.rgba);
+ // The stored name is not the displayed one: Bambu tags often carry no
+ // colour name at all, or an internal code, and Spoolman has no field for
+ // one — the catalog resolves the swatch's hex instead (#3090, #857).
+ const colorName = resolveSpoolColorName(spool.color_name, spool.rgba, spool.color_name_is_synthesized);
// Use spool's core_weight if set, otherwise fall back to default
const coreWeight = (spool.core_weight && spool.core_weight > 0)
@@ -104,7 +108,7 @@ export function SpoolInfoCard({ spool, scaleWeight, onClose, onSyncWeight, onAss
- {spool.color_name || 'Unknown color'}
+ {colorName || t('spoolbuddy.spool.unknownColor')}
#{spool.id}
diff --git a/frontend/src/components/spoolbuddy/TagDetectedModal.tsx b/frontend/src/components/spoolbuddy/TagDetectedModal.tsx
index 577268d13..10491ef60 100644
--- a/frontend/src/components/spoolbuddy/TagDetectedModal.tsx
+++ b/frontend/src/components/spoolbuddy/TagDetectedModal.tsx
@@ -4,7 +4,7 @@ import { Check, RefreshCw, AlertTriangle, X } from 'lucide-react';
import type { MatchedSpool } from '../../hooks/useSpoolBuddyState';
import { spoolbuddyApi } from '../../api/client';
import { SpoolIcon } from './SpoolIcon';
-import { spoolColorString } from '../../utils/colors';
+import { resolveSpoolColorName, spoolColorString } from '../../utils/colors';
// Storage key for default core weight (shared with SpoolInfoCard)
const DEFAULT_CORE_WEIGHT_KEY = 'spoolbuddy-default-core-weight';
@@ -136,6 +136,10 @@ interface KnownSpoolViewProps {
function KnownSpoolView({ spool, scaleWeight, weightStable, syncing, synced, onSyncWeight, onAssignToAms, onClose }: KnownSpoolViewProps) {
const { t } = useTranslation();
const colorHex = spoolColorString(spool.rgba);
+ // The stored name is not the displayed one: Bambu tags often carry no
+ // colour name at all, or an internal code, and Spoolman has no field for
+ // one — the catalog resolves the swatch's hex instead (#3090, #857).
+ const colorName = resolveSpoolColorName(spool.color_name, spool.rgba, spool.color_name_is_synthesized);
const coreWeight = (spool.core_weight && spool.core_weight > 0)
? spool.core_weight
@@ -190,7 +194,7 @@ function KnownSpoolView({ spool, scaleWeight, weightStable, syncing, synced, onS
- {spool.color_name || 'Unknown color'}
+ {colorName || t('spoolbuddy.spool.unknownColor')}
#{spool.id}
diff --git a/frontend/src/hooks/useCameraStreamToken.ts b/frontend/src/hooks/useCameraStreamToken.ts
index e61bf7ab1..7591c8780 100644
--- a/frontend/src/hooks/useCameraStreamToken.ts
+++ b/frontend/src/hooks/useCameraStreamToken.ts
@@ -1,15 +1,36 @@
import { useEffect, useRef } from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query';
-import { api, setStreamToken, getStreamToken, withStreamToken } from '../api/client';
+import {
+ api,
+ setStreamToken,
+ getStreamToken,
+ setMediaToken,
+ getMediaToken,
+ withStreamToken,
+ withMediaToken,
+} from '../api/client';
import { useAuth } from '../contexts/AuthContext';
+/** True for the three live-camera routes, which take the camera stream token.
+ * Everything else under /api/v1/ that a browser loads as an element src is
+ * media and takes the media token (#3025). */
+export function isCameraUrl(src: string): boolean {
+ return src.includes('/camera/');
+}
+
/**
* Walks the DOM and updates every
/
pointing at /api/v1/ so its
- * src carries the current stream token. Exported for unit testing; called
- * from useStreamTokenSync when the token arrives after first render.
+ * src carries the right token: the camera token for live-camera URLs, the
+ * media token for everything else. Either may be null -- a user without
+ * camera:view has no camera token, and their thumbnails must still be
+ * rewritten. Exported for unit testing; called from useStreamTokenSync when a
+ * token arrives after first render.
*/
-export function rewriteMediaSrcWithToken(root: ParentNode, token: string): number {
- const tokenParam = `token=${encodeURIComponent(token)}`;
+export function rewriteMediaSrcWithToken(
+ root: ParentNode,
+ mediaToken: string | null,
+ cameraToken: string | null
+): number {
let updated = 0;
root
.querySelectorAll(
@@ -17,6 +38,9 @@ export function rewriteMediaSrcWithToken(root: ParentNode, token: string): numbe
)
.forEach((el) => {
const src = el.getAttribute('src') || '';
+ const token = isCameraUrl(src) ? cameraToken : mediaToken;
+ if (!token) return;
+ const tokenParam = `token=${encodeURIComponent(token)}`;
if (src.includes(tokenParam)) return;
const withoutToken = src.replace(/([?&])token=[^&]*(&|$)/, (_m, pre, post) =>
post === '&' ? pre : pre === '?' ? '' : ''
@@ -29,23 +53,30 @@ export function rewriteMediaSrcWithToken(root: ParentNode, token: string): numbe
}
/**
- * Fetches and caches a stream token for / src URLs.
- * Stores the token globally via setStreamToken() so URL generators
- * in client.ts can use withStreamToken() automatically.
+ * Fetches and caches the query-param tokens / src URLs need, and
+ * publishes them through setMediaToken() / setStreamToken() so the URL
+ * generators in client.ts pick them up automatically.
*
- * Also listens for global image load errors on token-protected URLs
- * and automatically refreshes the token (e.g., after backend restart
- * invalidates in-memory tokens).
+ * Two tokens, fetched independently (#3025):
*
- * Mount this hook once near the app root (e.g., in App.tsx or a layout component).
- * Components that need token-protected URLs can import withStreamToken directly.
+ * media — every signed-in user gets one. Thumbnails, plate previews,
+ * timelapses, cover images and link icons ride on it.
+ * camera — only users with camera:view, because that is what minting one
+ * costs. Asking for it unconditionally would 403 on every page
+ * load for everyone else.
+ *
+ * Also listens for global image/video load errors on token-protected URLs and
+ * refreshes the matching token (e.g. after a backend restart drops them).
+ *
+ * Mount this hook once near the app root. Components that need token-protected
+ * URLs can import withMediaToken / withStreamToken directly.
*/
export function useStreamTokenSync() {
- const { authEnabled, user, loading: authLoading } = useAuth();
+ const { authEnabled, user, loading: authLoading, hasPermission } = useAuth();
const queryClient = useQueryClient();
const refreshingRef = useRef(false);
- // Key the token by user id so a login/logout invalidates the cache
+ // Key the tokens by user id so a login/logout invalidates the cache
// automatically — otherwise a failed anonymous fetch on the login page
// would be cached and never retried after sign-in.
//
@@ -55,31 +86,53 @@ export function useStreamTokenSync() {
// ``true`` on first render because ``authEnabled`` defaults to false,
// firing a 401 POST on the login page before AuthContext had a chance
// to settle on ``authEnabled=true, user=null``.
- const { data } = useQuery({
- queryKey: ['camera-stream-token', user?.id ?? null],
- queryFn: () => api.getCameraStreamToken(),
- enabled: !authLoading && (!authEnabled || user !== null),
+ const signedIn = !authLoading && (!authEnabled || user !== null);
+
+ const { data: mediaData } = useQuery({
+ queryKey: ['media-token', user?.id ?? null],
+ queryFn: () => api.getMediaToken(),
+ enabled: signedIn,
staleTime: 50 * 60 * 1000, // refresh at 50 min (tokens expire at 60)
refetchInterval: 50 * 60 * 1000,
});
- useEffect(() => {
- const newToken = data?.token ?? null;
- setStreamToken(newToken);
+ // Only ask for a camera token when the user may actually have one. When auth
+ // is disabled hasPermission() is vacuously true, which is correct — the mint
+ // endpoint is open then too.
+ const canViewCamera = !authEnabled || hasPermission('camera:view');
- // Images/videos that rendered before the token arrived have src URLs
+ const { data: cameraData } = useQuery({
+ queryKey: ['camera-stream-token', user?.id ?? null],
+ queryFn: () => api.getCameraStreamToken(),
+ enabled: signedIn && canViewCamera,
+ staleTime: 50 * 60 * 1000,
+ refetchInterval: 50 * 60 * 1000,
+ });
+
+ const mediaTokenValue = mediaData?.token ?? null;
+ const cameraTokenValue = cameraData?.token ?? null;
+
+ useEffect(() => {
+ setMediaToken(mediaTokenValue);
+ setStreamToken(cameraTokenValue);
+
+ // Images/videos that rendered before a token arrived have src URLs
// without ?token=…; update them in place so they reload with auth.
- if (newToken) {
- rewriteMediaSrcWithToken(document, newToken);
+ if (mediaTokenValue || cameraTokenValue) {
+ rewriteMediaSrcWithToken(document, mediaTokenValue, cameraTokenValue);
}
- return () => setStreamToken(null);
- }, [data?.token]);
+ return () => {
+ setMediaToken(null);
+ setStreamToken(null);
+ };
+ }, [mediaTokenValue, cameraTokenValue]);
// Listen for image/video load errors on token-protected URLs.
- // When the backend restarts, in-memory stream tokens are lost and all
+ // When the backend restarts, in-memory tokens are lost and all
// thumbnail/stream requests return 401. This handler detects that and
- // forces a token refresh so images recover without a page reload.
+ // forces a refresh of whichever token the failing URL used, so images
+ // recover without a page reload.
useEffect(() => {
if (!authEnabled) return;
@@ -88,14 +141,17 @@ export function useStreamTokenSync() {
if (!(el instanceof HTMLImageElement || el instanceof HTMLVideoElement)) return;
const src = el.src || '';
- const token = getStreamToken();
+ const camera = isCameraUrl(src);
+ const token = camera ? getStreamToken() : getMediaToken();
if (!token || !src.includes(`token=${encodeURIComponent(token)}`)) return;
- // This image/video used our stream token and failed — token likely invalid
+ // This image/video used one of our tokens and failed — likely invalid
if (refreshingRef.current) return;
refreshingRef.current = true;
- queryClient.invalidateQueries({ queryKey: ['camera-stream-token'] });
+ queryClient.invalidateQueries({
+ queryKey: camera ? ['camera-stream-token'] : ['media-token'],
+ });
// Reset after a delay so future errors can trigger another refresh
setTimeout(() => {
@@ -110,9 +166,16 @@ export function useStreamTokenSync() {
}
/**
- * Hook for components that need to wrap URLs with the stream token.
+ * Hook for components that need to wrap camera URLs with the stream token.
* Returns a withToken function that appends ?token=xxx when auth is enabled.
*/
export function useCameraStreamToken() {
return { withToken: withStreamToken };
}
+
+/**
+ * Hook for components that need to wrap media URLs with the media token.
+ */
+export function useMediaToken() {
+ return { withToken: withMediaToken };
+}
diff --git a/frontend/src/hooks/useSpoolBuddyState.ts b/frontend/src/hooks/useSpoolBuddyState.ts
index 85ea16305..b238ffb72 100644
--- a/frontend/src/hooks/useSpoolBuddyState.ts
+++ b/frontend/src/hooks/useSpoolBuddyState.ts
@@ -6,6 +6,9 @@ export interface MatchedSpool {
material: string;
subtype: string | null;
color_name: string | null;
+ // True when the backend had no stored colour name and put the subtype there
+ // instead — Spoolman-backed inventory only, which has no such field (#3090).
+ color_name_is_synthesized?: boolean;
rgba: string | null;
brand: string | null;
label_weight: number;
@@ -122,6 +125,7 @@ export function useSpoolBuddyState() {
material: spool.material ?? '',
subtype: spool.subtype ?? null,
color_name: spool.color_name ?? null,
+ color_name_is_synthesized: spool.color_name_is_synthesized ?? false,
rgba: spool.rgba ?? null,
brand: spool.brand ?? null,
label_weight: spool.label_weight ?? 0,
diff --git a/frontend/src/i18n/index.ts b/frontend/src/i18n/index.ts
index 8775a1ab6..e20593888 100644
--- a/frontend/src/i18n/index.ts
+++ b/frontend/src/i18n/index.ts
@@ -14,6 +14,7 @@ import ko from './locales/ko';
import ptBR from './locales/pt-BR';
import zhCN from './locales/zh-CN';
import zhTW from './locales/zh-TW';
+import sv from './locales/sv';
import tr from './locales/tr';
import ru from './locales/ru';
import uk from './locales/uk';
@@ -30,12 +31,13 @@ const resources = {
'pt-BR': { translation: ptBR },
'zh-CN': { translation: zhCN },
'zh-TW': { translation: zhTW },
+ sv: { translation: sv },
tr: { translation: tr },
ru: { translation: ru },
uk: { translation: uk },
};
-const SUPPORTED_LNGS = ['en', 'de', 'es', 'fr', 'ja', 'it', 'ko', 'nl', 'pt-BR', 'ru', 'tr', 'uk', 'zh-CN', 'zh-TW'];
+const SUPPORTED_LNGS = ['en', 'de', 'es', 'fr', 'ja', 'it', 'ko', 'nl', 'pt-BR', 'ru', 'sv', 'tr', 'uk', 'zh-CN', 'zh-TW'];
const APPLIANCE_CONSUMED_KEY = 'bambuddy_appliance_locale_consumed';
i18n
@@ -113,6 +115,7 @@ export const availableLanguages = [
{ code: 'pt-BR', name: 'Portuguese (Brazil)', nativeName: 'Português (Brasil)' },
{ code: 'zh-CN', name: 'Chinese (Simplified)', nativeName: '简体中文' },
{ code: 'zh-TW', name: 'Chinese (Traditional)', nativeName: '繁體中文' },
+ { code: 'sv', name: 'Swedish', nativeName: 'Svenska' },
{ code: 'tr', name: 'Turkish', nativeName: 'Türkçe' },
{ code: 'ru', name: 'Russian', nativeName: 'Русский' },
{ code: 'uk', name: 'Ukrainian', nativeName: 'Українська' },
diff --git a/frontend/src/i18n/locales/de.ts b/frontend/src/i18n/locales/de.ts
index 0420ca79e..67c9bac5c 100644
--- a/frontend/src/i18n/locales/de.ts
+++ b/frontend/src/i18n/locales/de.ts
@@ -534,6 +534,8 @@ export default {
step: 'Schritt (mm)',
up: 'Platte hoch',
down: 'Platte runter',
+ toolheadUp: 'Druckkopf hoch',
+ toolheadDown: 'Druckkopf runter',
disabledWhilePrinting: 'Während des Drucks deaktiviert',
notHomedTitle: 'Drucker ist nicht referenziert',
notHomedMessage: 'Der Drucker wurde seit dem letzten Druck nicht referenziert. Führen Sie zuerst die automatische Referenzfahrt aus (parkt den Werkzeugkopf und referenziert dann X, Y und Z) oder bewegen Sie trotzdem — die Software-Endschalter werden dabei umgangen.',
@@ -901,11 +903,15 @@ export default {
docsLink: 'Installationsschritt 4 anzeigen',
docsLinkInternalStorage: 'Warum das passiert',
titleInternalStorage: 'Einige kürzliche Drucke blieben im internen Speicher des Druckers',
- bodyInternalStorage: 'Bambu Studio hat die geslicte Datei im internen Speicher des Druckers statt auf der Karte abgelegt, daher gab es für Bambuddy nichts über FTP zu lesen. Bei der H2-Serie und dem P2S macht die Schaltfläche "Drucken" das immer — nur "Senden" bietet eine Auswahl, und auch die steht standardmäßig auf "Cache". Diese Drucke werden weiterhin mit Namen und Zeiten archiviert, nur ohne Vorschaubild und Slicer-Metadaten. Für vollständige Archive den Druck aus Bambuddy starten oder in OrcaSlicer slicen — oder in Bambu Studio "Senden" mit "Extern" verwenden und den Druck danach starten. Alle setzen eine Karte oder einen Stick im Drucker voraus.',
+ bodyInternalStorage: 'Bambu Studio hat die geslicte Datei im internen Speicher des Druckers statt auf der Karte abgelegt, daher gab es für Bambuddy nichts über FTP zu lesen. Bei der H2-Serie, dem P2S und dem X2D macht die Schaltfläche "Drucken" das immer — nur "Senden" bietet eine Auswahl, und auch die steht standardmäßig auf "Cache". Diese Drucke werden weiterhin mit Namen und Zeiten archiviert, nur ohne Vorschaubild und Slicer-Metadaten. Für vollständige Archive den Druck aus Bambuddy starten oder in OrcaSlicer slicen — oder in Bambu Studio "Senden" mit "Extern" verwenden und den Druck danach starten. Alle setzen eine Karte oder einen Stick im Drucker voraus.',
titleNoExternalStorage: 'Einige kürzliche Drucke konnten nicht archiviert werden — kein Speicher im Drucker',
bodyNoExternalStorage: 'Der Drucker meldet weder Karte noch Stick im Steckplatz, daher hatte die geslicte Datei keinen Ablageort und Bambuddy nichts zu lesen. Legen Sie einen ein, dann wird der nächste Druck vollständig archiviert.',
titleInternalHistory: 'Einige kürzliche Drucke wurden aus einer Datei gestartet, die bereits auf dem Drucker lag',
bodyInternalHistory: 'Diese Drucke liefen aus der eigenen Bibliothek des Druckers — ein erneuter Druck über sein Display, ein Start aus Handy oder eine früher gesendete und später gedruckte Datei. Bambuddy liest Druckdateien über FTP, und das bedient nur Karte oder Stick, während der Drucker diese Bibliothek in einem Bereich ablegt, den FTP nicht erreicht — es gab also keine 3MF zu lesen. Keine Slicer-Einstellung ändert das, denn für diese Drucke wurde nichts gesendet. Sie werden weiterhin mit Namen und Zeiten archiviert, und unter "Archiv bearbeiten" lässt sich das verbrauchte Filament von Hand eintragen. Für ein vollständiges Archiv starten Sie den Druck stattdessen aus Bambuddy oder aus Ihrem Slicer.',
+ titleFtpsCooloff: 'Einige kürzliche Drucke konnten nicht archiviert werden — der Drucker hat die Dateiverbindung abgewiesen',
+ bodyFtpsCooloff: 'Bambuddy hat den Dateiübertragungs-Port des Druckers (FTPS 990) geöffnet, und der Drucker hat mit etwas geantwortet, das kein TLS ist. Deshalb ließ sich nichts von ihm lesen. Diese Drucke sind weiterhin mit Namen und Zeiten archiviert, nur ohne Vorschaubild und Slicer-Metadaten. Das ist keine Slicer-Einstellung und nichts, was Sie geändert haben — dieselben Modelle und Firmware-Stände laufen auf anderen Installationen normal, und ein betroffener Drucker arbeitet später meist von selbst wieder. Nach einem solchen Fehler pausiert Bambuddy die Übertragungen zu diesem Drucker fünf Minuten lang und holt die Datei nach Ablauf der Pause erneut; eine kurze Episode behebt sich damit von selbst, eine weiterhin leere Karte bedeutet, dass die Abweisung länger anhielt als der zweite Versuch. Was das auslöst, ist noch nicht bekannt. Wenn es wiederholt auftritt, aktivieren Sie auf der Seite System die Debug-Protokollierung und hängen Sie ein Support-Paket an Ihre Meldung an.',
+ titleFtpTransferFailed: 'Einige aktuelle Drucke konnten nicht archiviert werden — die Dateiübertragung hat zu lange gedauert',
+ bodyFtpTransferFailed: 'Die geslicete Datei lag auf der Karte des Druckers und der Drucker hat sie auch ausgeliefert, aber die Übertragung wurde nicht in der erlaubten Zeit fertig. Zum Druckstart bedient der Drucker gleichzeitig seine Kamera, seine Statusmeldungen und den Auftrags-Upload, und eine große 3MF kommt dagegen nicht immer durch. Diese Drucke sind weiterhin mit Name und Zeiten archiviert, nur ohne Vorschaubild und Slicer-Daten. Das ist keine Slicer-Einstellung und nichts, was Sie geändert haben. Bambuddy holt die Datei in den folgenden zehn Minuten noch dreimal nach, die meisten Fälle erledigen sich also von selbst; eine weiterhin leere Karte bedeutet, dass auch jeder dieser Versuche zu lange gedauert hat. Wenn es häufiger vorkommt, erhöhen Sie Verbindungs-Timeout unter Einstellungen > Netzwerk > FTP-Wiederholung.',
dismissLabel: 'Hinweis schließen',
},
searchPlaceholder: 'Archiv durchsuchen...',
@@ -1487,7 +1493,7 @@ export default {
// Time
time: {
etaIfStartedNow: 'Fertigstellungszeit, wenn dieser Auftrag jetzt starten würde',
- asap: 'Sofort',
+ whenFree: 'Sobald ein Drucker frei ist',
overdue: 'Überfällig',
now: 'Jetzt',
lessThanMinute: 'In weniger als einer Minute',
@@ -1511,6 +1517,7 @@ export default {
staged: 'Bereitgestellt (manueller Start)',
autoPowerOff: 'Nach Druck automatisch ausschalten',
requirePrevious: 'Vorherigen Erfolg erfordern',
+ gcodeInjection: 'G-Code einfügen',
printOptions: 'Druckoptionen',
bedLevelling: 'Bett-Nivellierung',
flowCalibration: 'Fluss-Kalibrierung',
@@ -2330,7 +2337,7 @@ export default {
preheatEnabled: 'Vorheizen & Soak aktivieren',
preheatEnabledDesc: 'Wenn aus, starten Drucke aus der Warteschlange sofort. Jeder Warteschlangeneintrag kann das pro Druck überschreiben.',
preheatFilamentTargetsLabel: 'Kammer-Ziel je Filament (°C)',
- preheatFilamentTargetsHint: 'Bambuddy wählt das höchste Ziel über die geladenen AMS-Slots; reine PLA-Drucke ergeben 0 und überspringen die Kammerphase automatisch.',
+ preheatFilamentTargetsHint: 'Bambuddy wählt das höchste Ziel über die geladenen AMS-Slots. Ein Druck, dessen Slots alle 0 ergeben – PLA, PETG, TPU, PVA –, überspringt das Vorheizen vollständig und startet sofort.',
preheatFilamentTargetsReset: 'Auf Standardwerte zurücksetzen',
preheatFilamentTargetsDefaultRow: 'Sonstige / nicht zugeordnet',
preheatMaxWait: 'Max. Wartezeit (Sekunden)',
@@ -2347,6 +2354,7 @@ export default {
calibrationMode_on: 'An',
calibrationMode_auto: 'Auto',
preheatTargetOverride: 'Kammer-Ziel überschreiben (°C, leer = Filament-Standard)',
+ preheatTargetOverrideHelp: '0 heizt das Bett und führt das Halten ohne Kammer aus. Bleibt das Feld leer, überspringt ein Druck ohne Kammerbedarf das Vorheizen vollständig.',
plateClear: 'Druckplatte-Bestätigung',
requirePlateClear: 'Druckplatte-Bestätigung erforderlich',
requirePlateClearDescription: 'Wenn aktiviert, wartet der Scheduler auf eine Druckplatten-Bestätigung pro Drucker, bevor geplante Drucke auf Druckern mit abgeschlossenen Aufträgen gestartet werden. Wenn dies deaktiviert ist, werden auch das Druckplatten-Status-Badge und die Schaltfläche "Druckplatte als freigegeben markieren" auf den Druckerkarten ausgeblendet.',
@@ -4915,7 +4923,7 @@ export default {
linkToSpool: 'Mit Spule verknüpfen',
tagLinked: 'Tag mit Spule verknüpft',
tagLinkFailed: 'Tag-Verknüpfung fehlgeschlagen',
- tagAlreadyLinked: 'Tag bereits mit anderer Spule verknüpft',
+ tagAlreadyLinked: 'Tag bereits mit Spule #{{id}} verknüpft',
unknownTag: 'Unbekannter RFID-Tag erkannt',
// Verbrauchshistorie
usageHistory: 'Verbrauchshistorie',
@@ -5401,7 +5409,7 @@ export default {
project: 'Projekt',
noProject: 'Kein Projekt',
itemsPrinted: 'Gedruckte Teile',
- itemsPrintedHelp: 'Anzahl der in diesem Druckauftrag produzierten Teile',
+ itemsPrintedHelp: 'Anzahl der in diesem Druckauftrag produzierten Teile. 0, wenn die ganze Platte unbrauchbar war.',
filamentUsed: 'Verbrauchtes Filament (g)',
filamentUsedPlaceholder: 'z. B. 46.16',
filamentUsedHelp: 'Von Hand eintragen, wenn ein Druck ohne 3MF archiviert wurde, damit er in die Filament-Summen eingeht. Beim erneuten Einlesen eines Archivs mit 3MF wird der Wert wieder aus der Datei übernommen.',
@@ -6794,6 +6802,7 @@ export default {
scaleWeight: 'Waage',
netWeight: 'Netto',
lastUsed: 'Zuletzt verwendet',
+ unknownColor: 'Unbekannte Farbe',
},
ams: {
noData: 'Kein AMS erkannt',
@@ -6971,7 +6980,7 @@ export default {
skip: 'Nicht geprüft — eine aktive MQTT-Verbindung ist erforderlich. Bei älteren Slicern, in denen diese Einstellung nur im Slicer existiert, meldet sie der Drucker nicht — diese Prüfung besteht auch dann, wenn die Option deaktiviert ist. Prüfen Sie Installationsschritt 4 in diesem Fall manuell.',
skip_unsupported_model: 'Dieses Modell hat einen SD-Slot, aber keine Möglichkeit, die Option zu aktivieren — die aktuelle P1-Firmware zeigt den Schalter in Bambu Studio nicht an und der Drucker hat kein Display. Hier gibt es nichts zu beheben; archivierten Drucken fehlen möglicherweise Vorschaubilder und Slicer-Metadaten, bis Bambu Lab dies per Firmware unterstützt.',
fail_no_media: 'Die Option ist aktiviert, aber der Drucker meldet weder Karte noch Stick im Steckplatz, daher können gesendete Dateien nirgends abgelegt werden. Legen Sie einen ein und drucken Sie erneut — bis dahin fehlen jedem archivierten Druck Vorschaubild und Slicer-Metadaten.',
- warn_internal_storage: 'Die Option ist aktiviert und ein Speicher ist vorhanden, aber der letzte Druck landete dennoch im internen Speicher des Druckers, den Bambuddy nicht lesen kann. Bei der H2-Serie und dem P2S sendet die Schaltfläche "Drucken" in Bambu Studio unabhängig von dieser Option immer dorthin. Drucke werden mit Namen und Zeiten archiviert, aber ohne Vorschaubild und Slicer-Metadaten. Für vollständige Archive den Druck aus Bambuddy starten oder in OrcaSlicer slicen — oder in Bambu Studio "Senden" mit "Extern" verwenden und den Druck danach starten.',
+ warn_internal_storage: 'Die Option ist aktiviert und ein Speicher ist vorhanden, aber der letzte Druck landete dennoch im internen Speicher des Druckers, den Bambuddy nicht lesen kann. Bei der H2-Serie, dem P2S und dem X2D sendet die Schaltfläche "Drucken" in Bambu Studio unabhängig von dieser Option immer dorthin. Drucke werden mit Namen und Zeiten archiviert, aber ohne Vorschaubild und Slicer-Metadaten. Für vollständige Archive den Druck aus Bambuddy starten oder in OrcaSlicer slicen — oder in Bambu Studio "Senden" mit "Extern" verwenden und den Druck danach starten.',
warn_internal_history: 'Die Option ist aktiv und Speicher vorhanden, aber der letzte Druck lief aus einer Datei, die bereits auf dem Drucker lag — ein erneuter Druck über sein Display, ein Start aus Handy oder eine früher gesendete Datei. Diese Bibliothek liegt im internen Speicher, den Bambuddy nicht lesen kann, und keine Einstellung ändert das, denn für diesen Druck wurde nichts gesendet. Er wird weiterhin mit Namen und Zeiten archiviert, nur ohne Vorschaubild und Slicer-Metadaten. Für vollständige Archive Drucke aus Bambuddy starten oder aus dem Slicer senden.',
},
port_rtsps: {
@@ -6979,11 +6988,20 @@ export default {
pass: 'Erreichbar — der Kamerastream funktioniert.',
warn: 'Port {{port}} ist nicht erreichbar. Die Live-Kameraansicht funktioniert nicht. Dies betrifft das Drucken nicht.',
},
+ macos_local_network: {
+ title: 'macOS-Berechtigung „Lokales Netzwerk“',
+ pass: 'macOS erlaubt Bambuddy den Zugriff auf das lokale Netzwerk.',
+ warn_unsigned: 'Das Python, das Bambuddy ausführt, hat keine Code-Signatur. Damit hat macOS nichts, woran es die Berechtigung „Lokales Netzwerk“ binden könnte, und verwirft jede Verbindung zum Drucker stillschweigend — ohne Fehler und ohne Nachfrage. Führe das Bambuddy-Update-Skript (install/update_macos.sh) aus, das die Signatur setzt, und starte Bambuddy anschließend neu. Interpreter: {{executable}}',
+ warn_permission: 'Wenn der Drucker eingeschaltet und unter dieser Adresse erreichbar ist, öffne Systemeinstellungen > Datenschutz & Sicherheit > Lokales Netzwerk und stelle sicher, dass das Python von Bambuddy aktiviert ist. macOS verwirft lokale Verbindungen sonst stillschweigend, und ein Python-Update kann die alte Berechtigung zurücklassen.',
+ },
network_mode: {
- title: 'Docker-Netzwerkmodus',
- pass: 'Läuft im Host-Netzwerkmodus.',
- warn: 'Bambuddy läuft im Docker-Bridge-Netzwerkmodus. Die Druckererkennung und der virtuelle Drucker benötigen den Host-Netzwerkmodus — erstellen Sie den Container mit "network_mode: host" neu.',
- skip: 'Läuft nicht in Docker — nicht zutreffend.',
+ title: 'Container-Netzwerkmodus',
+ genericRuntime: 'einem Container',
+ pass: 'Läuft in {{runtime}} mit Host-Netzwerk.',
+ warn: 'Bambuddy läuft in {{runtime}} mit Bridge-Netzwerk. Die Druckererkennung und der virtuelle Drucker benötigen den Host-Netzwerkmodus — erstellen Sie den Container mit Host-Netzwerk neu ("network_mode: host" in docker-compose, "--network=host" bei Podman).',
+ skip: 'Läuft nicht in einem Container — nicht zutreffend.',
+ skip_unknown: 'Bambuddy läuft in {{runtime}}, der Netzwerkmodus konnte jedoch nicht ermittelt werden. Falls die Druckererkennung oder der virtuelle Drucker nicht funktionieren, erstellen Sie den Container mit Host-Netzwerk neu.',
+ skip_system_container: 'Bambuddy läuft in einem {{runtime}}-Systemcontainer, der wie eine virtuelle Maschine direkt im LAN liegt — nicht zutreffend.',
},
subnet: {
title: 'Netzwerk-Subnetz',
diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts
index 9f8566f46..e80008d31 100644
--- a/frontend/src/i18n/locales/en.ts
+++ b/frontend/src/i18n/locales/en.ts
@@ -537,6 +537,8 @@ export default {
step: 'Step (mm)',
up: 'Move plate up',
down: 'Move plate down',
+ toolheadUp: 'Move toolhead up',
+ toolheadDown: 'Move toolhead down',
disabledWhilePrinting: 'Disabled while printing',
notHomedTitle: 'Printer is not homed',
notHomedMessage: 'The printer has not been homed since the last print. Run auto-home first for safe positioning (parks the toolhead, then homes X, Y, and Z), or move anyway — soft endstops will be bypassed.',
@@ -907,11 +909,15 @@ export default {
dismissLabel: 'Dismiss this notice',
docsLinkInternalStorage: 'Why this happens',
titleInternalStorage: 'Some recent prints stayed on the printer\'s internal storage',
- bodyInternalStorage: 'Bambu Studio put the sliced file on the printer\'s internal storage instead of the card, so there was nothing for Bambuddy to read over FTP. On H2-series and P2S its Print button always does that — only Send offers a choice, and that defaults to Cache too. Those prints are still archived with their name and timing, just without a thumbnail or slicer metadata. For complete archives, start the print from Bambuddy, or slice in OrcaSlicer — or in Bambu Studio use Send with External selected and start the print afterwards. All of them need a card or stick in the printer.',
+ bodyInternalStorage: 'Bambu Studio put the sliced file on the printer\'s internal storage instead of the card, so there was nothing for Bambuddy to read over FTP. On H2-series, P2S and X2D its Print button always does that — only Send offers a choice, and that defaults to Cache too. Those prints are still archived with their name and timing, just without a thumbnail or slicer metadata. For complete archives, start the print from Bambuddy, or slice in OrcaSlicer — or in Bambu Studio use Send with External selected and start the print afterwards. All of them need a card or stick in the printer.',
titleNoExternalStorage: 'Some recent prints couldn\'t be archived — no storage in the printer',
bodyNoExternalStorage: 'The printer reports no card or stick in its slot, so the sliced file had nowhere to land and Bambuddy had nothing to read. Insert one and the next print will archive in full.',
titleInternalHistory: 'Some recent prints were started from a file already on the printer',
bodyInternalHistory: 'Those prints ran from the printer\'s own library — a re-print from its screen, a start from Handy, or a file sent earlier and printed later. Bambuddy reads print files over FTP, which serves only the card or stick, while the printer keeps that library in an area FTP cannot reach, so there was no 3MF to read. No slicer setting changes this, because nothing was sent for these prints. They are still archived with their name and timing, and Edit Archive lets you fill in the filament used by hand. For a complete archive, start the print from Bambuddy or from your slicer instead.',
+ titleFtpsCooloff: 'Some recent prints couldn\'t be archived — the printer refused the file connection',
+ bodyFtpsCooloff: 'Bambuddy opened the printer\'s file-transfer port (FTPS 990) and the printer answered with something that is not TLS, so nothing could be read from it. Those prints are still archived with their name and timing, just without a thumbnail or slicer metadata. This is not a slicer setting and not something you changed — the same models and firmware run normally on other installs, and an affected printer usually works again later on its own. After such a failure Bambuddy pauses transfers to that printer for five minutes and comes back for the file once the pause clears, so a short episode fills itself in; a card still empty means the refusal outlasted the retry. What triggers it is not yet known. If it keeps happening, turn on debug logging on the System page and attach a support bundle to your report.',
+ titleFtpTransferFailed: 'Some recent prints couldn\'t be archived — the file transfer ran out of time',
+ bodyFtpTransferFailed: 'The sliced file was on the printer\'s card and the printer served it, but the transfer did not finish in the time allowed. At print start the printer is also handling its camera, its status messages and the job upload, and a large 3MF does not always get through against all that. Those prints are still archived with their name and timing, just without a thumbnail or slicer metadata. This is not a slicer setting and not something you changed. Bambuddy comes back for the file three times over the following ten minutes, so most of these fill themselves in; a card still empty means every attempt ran out of time too. If it keeps happening, raise Connection Timeout under Settings > Network > FTP Retry.',
},
searchPlaceholder: 'Search archives...',
filterByPrinter: 'Filter by printer',
@@ -1503,7 +1509,7 @@ export default {
// Time
time: {
etaIfStartedNow: 'Completion time if this job started now',
- asap: 'ASAP',
+ whenFree: 'When a printer is free',
overdue: 'Overdue',
now: 'Now',
lessThanMinute: 'In less than a minute',
@@ -1527,6 +1533,7 @@ export default {
staged: 'Staged (manual start)',
autoPowerOff: 'Auto power off after print',
requirePrevious: 'Require previous success',
+ gcodeInjection: 'Inject G-code',
printOptions: 'Print Options',
bedLevelling: 'Bed levelling',
flowCalibration: 'Flow calibration',
@@ -2350,7 +2357,7 @@ export default {
preheatEnabled: 'Enable preheat & soak',
preheatEnabledDesc: 'When off, queued prints dispatch immediately. Each queue item can override per print.',
preheatFilamentTargetsLabel: 'Per-filament chamber target (°C)',
- preheatFilamentTargetsHint: 'Bambuddy picks the highest target across the loaded AMS slots; PLA-only prints derive 0 and skip the chamber phase automatically.',
+ preheatFilamentTargetsHint: 'Bambuddy picks the highest target across the loaded AMS slots. A print whose slots all derive 0 — PLA, PETG, TPU, PVA — skips preheat entirely and starts immediately.',
preheatFilamentTargetsReset: 'Reset to defaults',
preheatFilamentTargetsDefaultRow: 'Other / unmapped',
preheatMaxWait: 'Max wait (seconds)',
@@ -2367,6 +2374,7 @@ export default {
calibrationMode_on: 'On',
calibrationMode_auto: 'Auto',
preheatTargetOverride: 'Chamber target override (°C, blank = filament default)',
+ preheatTargetOverrideHelp: '0 heats the bed and runs the soak without the chamber. Leave blank and a print with no chamber requirement skips preheat entirely.',
plateClear: 'Plate-Clear Confirmation',
requirePlateClear: 'Require plate-clear confirmation',
requirePlateClearDescription: 'When enabled, the scheduler waits for per-printer plate-clear confirmation before starting queued prints on printers with finished jobs. Disabling this also hides the plate status badge and the "Mark plate as cleared" button on printer cards.',
@@ -4960,7 +4968,7 @@ export default {
linkToSpool: 'Link to Spool',
tagLinked: 'Tag linked to spool',
tagLinkFailed: 'Failed to link tag',
- tagAlreadyLinked: 'Tag already linked to another spool',
+ tagAlreadyLinked: 'Tag already linked to spool #{{id}}',
unknownTag: 'Unknown RFID tag detected',
// Usage history
usageHistory: 'Usage History',
@@ -5452,7 +5460,7 @@ export default {
project: 'Project',
noProject: 'No project',
itemsPrinted: 'Items Printed',
- itemsPrintedHelp: 'Number of items produced in this print job',
+ itemsPrintedHelp: 'Number of items produced in this print job. Use 0 if the whole plate was ruined.',
filamentUsed: 'Filament used (g)',
filamentUsedPlaceholder: 'e.g. 46.16',
filamentUsedHelp: 'Set this by hand when a print archived without its 3MF, so it still counts towards the filament totals. Rescanning an archive that has its 3MF reads the figure back from the file.',
@@ -6845,6 +6853,7 @@ export default {
scaleWeight: 'Scale',
netWeight: 'Net',
lastUsed: 'Last used',
+ unknownColor: 'Unknown color',
},
ams: {
noData: 'No AMS detected',
@@ -7022,7 +7031,7 @@ export default {
skip: 'Not checked — needs a live MQTT connection. On older slicers where this setting lives only in the slicer the printer never reports it, so this check will pass even when the option is off — verify install step 4 manually.',
skip_unsupported_model: 'This model has an SD slot but no way to turn the option on — current P1-series firmware doesn\'t expose the toggle in Bambu Studio and the printer has no screen. Nothing to fix here; archived prints may lack thumbnails and slicer metadata until Bambu Lab adds firmware support.',
fail_no_media: 'The option is on, but the printer reports no card or stick in its slot, so there is nowhere for sent files to go. Insert one and print again — until then every archived print will be missing its thumbnail and slicer metadata.',
- warn_internal_storage: 'The option is on and storage is present, but the last print still went to the printer\'s internal storage, which Bambuddy cannot read. On H2-series and P2S, Bambu Studio\'s Print button always sends there whatever this option is set to. Prints archive with their name and timing, but without a thumbnail or slicer metadata. For complete archives, start prints from Bambuddy or slice in OrcaSlicer — or in Bambu Studio use Send with External selected, then start the print.',
+ warn_internal_storage: 'The option is on and storage is present, but the last print still went to the printer\'s internal storage, which Bambuddy cannot read. On H2-series, P2S and X2D, Bambu Studio\'s Print button always sends there whatever this option is set to. Prints archive with their name and timing, but without a thumbnail or slicer metadata. For complete archives, start prints from Bambuddy or slice in OrcaSlicer — or in Bambu Studio use Send with External selected, then start the print.',
warn_internal_history: 'The option is on and storage is present, but the last print ran from a file that was already on the printer — a re-print from its screen, a start from Handy, or a file sent earlier. That library sits on internal storage Bambuddy cannot read, and no setting changes it, because nothing was sent for that print. It still archives with its name and timing, but without a thumbnail or slicer metadata. For complete archives, start prints from Bambuddy or send them from the slicer.',
},
port_rtsps: {
@@ -7030,11 +7039,20 @@ export default {
pass: 'Reachable — the camera stream will work.',
warn: 'Port {{port}} is unreachable. The live camera view will not work. This does not affect printing.',
},
+ macos_local_network: {
+ title: 'macOS Local Network permission',
+ pass: 'macOS is allowing Bambuddy to reach the local network.',
+ warn_unsigned: 'The Python that runs Bambuddy has no code signature, so macOS has nothing to attach a Local Network permission to and silently drops every connection to the printer — no error and no prompt. Run the Bambuddy updater (install/update_macos.sh), which signs it, then restart Bambuddy. Interpreter: {{executable}}',
+ warn_permission: 'If the printer is powered on and at this address, open System Settings > Privacy & Security > Local Network and make sure the Python that runs Bambuddy is enabled. macOS drops local connections silently when it is not, and updating Python can leave the old permission behind.',
+ },
network_mode: {
- title: 'Docker network mode',
- pass: 'Running in host network mode.',
- warn: 'Bambuddy is running in Docker bridge networking. Printer discovery and the Virtual Printer need host network mode — recreate the container with "network_mode: host".',
- skip: 'Not running in Docker — not applicable.',
+ title: 'Container network mode',
+ genericRuntime: 'a container',
+ pass: 'Running in {{runtime}} with host networking.',
+ warn: 'Bambuddy is running in {{runtime}} with bridge networking. Printer discovery and the Virtual Printer need host networking — recreate the container with host network mode ("network_mode: host" in docker-compose, "--network=host" for Podman).',
+ skip: 'Not running in a container — not applicable.',
+ skip_unknown: 'Bambuddy is running in {{runtime}}, but its network mode could not be determined. If printer discovery or the Virtual Printer do not work, recreate the container with host networking.',
+ skip_system_container: 'Bambuddy is running in a {{runtime}} system container, which sits on the LAN like a virtual machine — not applicable.',
},
subnet: {
title: 'Network subnet',
diff --git a/frontend/src/i18n/locales/es.ts b/frontend/src/i18n/locales/es.ts
index 47a374e88..2d614e65b 100644
--- a/frontend/src/i18n/locales/es.ts
+++ b/frontend/src/i18n/locales/es.ts
@@ -534,6 +534,8 @@ export default {
step: 'Paso (mm)',
up: 'Subir la cama',
down: 'Bajar la cama',
+ toolheadUp: 'Subir el cabezal',
+ toolheadDown: 'Bajar el cabezal',
disabledWhilePrinting: 'Desactivado durante la impresión',
notHomedTitle: 'La impresora no está en posición de origen',
notHomedMessage: 'La impresora no se ha llevado a su posición de origen desde la última impresión. Ejecute el autoorigen primero para un posicionamiento seguro (estaciona el cabezal y luego lleva X, Y y Z al origen), o mueva de todos modos — los finales de carrera por software se omitirán.',
@@ -901,11 +903,15 @@ export default {
docsLink: 'Ver paso 4 de instalación',
docsLinkInternalStorage: 'Por qué ocurre',
titleInternalStorage: 'Algunas impresiones recientes se quedaron en el almacenamiento interno de la impresora',
- bodyInternalStorage: 'Bambu Studio guardó el archivo laminado en el almacenamiento interno de la impresora en lugar de la tarjeta, así que Bambuddy no tenía nada que leer por FTP. En la serie H2 y la P2S su botón «Imprimir» siempre lo hace: solo «Enviar» ofrece elección, y también viene con «Caché» por defecto. Esas impresiones se siguen archivando con su nombre y sus tiempos, solo que sin miniatura ni metadatos del laminador. Para archivos completos, inicia la impresión desde Bambuddy o lamina en OrcaSlicer, o bien en Bambu Studio usa «Enviar» con «Externo» y luego inicia la impresión. Todas requieren una tarjeta o memoria en la impresora.',
+ bodyInternalStorage: 'Bambu Studio guardó el archivo laminado en el almacenamiento interno de la impresora en lugar de la tarjeta, así que Bambuddy no tenía nada que leer por FTP. En la serie H2, la P2S y la X2D su botón «Imprimir» siempre lo hace: solo «Enviar» ofrece elección, y también viene con «Caché» por defecto. Esas impresiones se siguen archivando con su nombre y sus tiempos, solo que sin miniatura ni metadatos del laminador. Para archivos completos, inicia la impresión desde Bambuddy o lamina en OrcaSlicer, o bien en Bambu Studio usa «Enviar» con «Externo» y luego inicia la impresión. Todas requieren una tarjeta o memoria en la impresora.',
titleNoExternalStorage: 'Algunas impresiones recientes no se pudieron archivar — no hay almacenamiento en la impresora',
bodyNoExternalStorage: 'La impresora no detecta ninguna tarjeta ni memoria en su ranura, así que el archivo laminado no tenía dónde aterrizar y Bambuddy nada que leer. Inserte una y la próxima impresión se archivará por completo.',
titleInternalHistory: 'Algunas impresiones recientes se iniciaron desde un archivo que ya estaba en la impresora',
bodyInternalHistory: 'Esas impresiones salieron de la propia biblioteca de la impresora: una reimpresión desde su pantalla, un inicio desde Handy o un archivo enviado antes e impreso después. Bambuddy lee los archivos de impresión por FTP, que solo sirve la tarjeta o la memoria, mientras que la impresora guarda esa biblioteca en una zona que FTP no alcanza, así que no había ningún 3MF que leer. Ninguna opción del laminador cambia esto, porque para estas impresiones no se envió nada. Se siguen archivando con su nombre y sus tiempos, y «Editar archivo» permite anotar a mano el filamento usado. Para un archivo completo, inicia la impresión desde Bambuddy o desde tu laminador.',
+ titleFtpsCooloff: 'Algunas impresiones recientes no se pudieron archivar — la impresora rechazó la conexión de archivos',
+ bodyFtpsCooloff: 'Bambuddy abrió el puerto de transferencia de archivos de la impresora (FTPS 990) y la impresora respondió con algo que no es TLS, así que no se pudo leer nada de ella. Esas impresiones siguen archivadas con su nombre y sus tiempos, solo que sin miniatura ni metadatos del laminador. No es un ajuste del laminador ni algo que usted haya cambiado — los mismos modelos y firmware funcionan con normalidad en otras instalaciones, y una impresora afectada suele volver a funcionar sola más tarde. Tras un fallo así, Bambuddy pausa las transferencias a esa impresora durante cinco minutos y vuelve a por el archivo cuando termina la pausa, de modo que un episodio breve se resuelve por sí solo; una ficha que sigue vacía significa que el rechazo duró más que el reintento. Todavía no se sabe qué lo provoca. Si se repite, active el registro de depuración en la página Sistema y adjunte un paquete de soporte a su informe.',
+ titleFtpTransferFailed: 'Algunas impresiones recientes no se pudieron archivar: la transferencia del archivo agotó su tiempo',
+ bodyFtpTransferFailed: 'El archivo laminado estaba en la tarjeta de la impresora y la impresora lo sirvió, pero la transferencia no terminó en el tiempo permitido. Al iniciar la impresión la impresora también atiende su cámara, sus mensajes de estado y la subida del trabajo, y un 3MF grande no siempre consigue pasar con todo eso. Esas impresiones siguen archivadas con su nombre y sus tiempos, solo que sin miniatura ni metadatos del laminador. No es un ajuste del laminador ni algo que usted haya cambiado. Bambuddy vuelve a por el archivo tres veces durante los diez minutos siguientes, así que la mayoría se completan solas; una tarjeta que sigue vacía significa que todos esos intentos también agotaron su tiempo. Si se repite, aumente Tiempo de espera de conexión en Ajustes > Red > Reintento FTP.',
dismissLabel: 'Descartar este aviso',
},
searchPlaceholder: 'Buscar archivos...',
@@ -1487,7 +1493,7 @@ export default {
// Time
time: {
etaIfStartedNow: 'Hora de finalización si este trabajo comenzara ahora',
- asap: 'Lo antes posible',
+ whenFree: 'Cuando haya una impresora libre',
overdue: 'Atrasada',
now: 'Ahora',
lessThanMinute: 'En menos de un minuto',
@@ -1511,6 +1517,7 @@ export default {
staged: 'Preparado (inicio manual)',
autoPowerOff: 'Apagado automático tras la impresión',
requirePrevious: 'Requerir éxito previo',
+ gcodeInjection: 'Inyectar G-code',
printOptions: 'Opciones de impresión',
bedLevelling: 'Nivelación de la cama',
flowCalibration: 'Calibración del flujo',
@@ -2333,7 +2340,7 @@ export default {
preheatEnabled: 'Activar precalentamiento y soak',
preheatEnabledDesc: 'Si está desactivado, las impresiones en cola se inician de inmediato. Cada elemento puede anular por impresión.',
preheatFilamentTargetsLabel: 'Objetivo de cámara por filamento (°C)',
- preheatFilamentTargetsHint: 'Bambuddy elige el objetivo más alto entre los slots AMS cargados; las impresiones solo PLA derivan 0 y omiten la fase de cámara automáticamente.',
+ preheatFilamentTargetsHint: 'Bambuddy elige el objetivo más alto entre los slots AMS cargados. Una impresión cuyos slots derivan todos 0 (PLA, PETG, TPU, PVA) omite el precalentamiento por completo y empieza de inmediato.',
preheatFilamentTargetsReset: 'Restablecer valores predeterminados',
preheatFilamentTargetsDefaultRow: 'Otros / sin asignar',
preheatMaxWait: 'Espera máx. (segundos)',
@@ -2350,6 +2357,7 @@ export default {
calibrationMode_on: 'Activado',
calibrationMode_auto: 'Automático',
preheatTargetOverride: 'Sobrescribir objetivo de cámara (°C, vacío = por filamento)',
+ preheatTargetOverrideHelp: '0 calienta la cama y ejecuta el reposo sin cámara. Si se deja en blanco, una impresión sin necesidad de cámara omite el precalentamiento por completo.',
plateClear: 'Confirmación de cama despejada',
requirePlateClear: 'Requerir confirmación de cama despejada',
requirePlateClearDescription: 'Cuando está activado, el planificador espera la confirmación de cama despejada por impresora antes de iniciar impresiones en cola en impresoras con trabajos finalizados. Desactivar esto también oculta la insignia de estado de la cama y el botón "Marcar cama como despejada" en las tarjetas de impresora.',
@@ -4922,7 +4930,7 @@ export default {
linkToSpool: 'Vincular a bobina',
tagLinked: 'Etiqueta vinculada a la bobina',
tagLinkFailed: 'Error al vincular la etiqueta',
- tagAlreadyLinked: 'La etiqueta ya está vinculada a otra bobina',
+ tagAlreadyLinked: 'La etiqueta ya está vinculada a la bobina #{{id}}',
unknownTag: 'Se detectó una etiqueta RFID desconocida',
// Usage history
usageHistory: 'Historial de uso',
@@ -5409,7 +5417,7 @@ export default {
project: 'Proyecto',
noProject: 'Sin proyecto',
itemsPrinted: 'Elementos impresos',
- itemsPrintedHelp: 'Número de elementos producidos en este trabajo de impresión',
+ itemsPrintedHelp: 'Número de elementos producidos en este trabajo de impresión. Use 0 si toda la placa se estropeó.',
filamentUsed: 'Filamento usado (g)',
filamentUsedPlaceholder: 'p. ej. 46.16',
filamentUsedHelp: 'Introdúcelo a mano cuando una impresión se haya archivado sin su 3MF, para que siga contando en los totales de filamento. Al volver a escanear un archivo que sí tiene su 3MF, el valor se lee de nuevo del fichero.',
@@ -6802,6 +6810,7 @@ export default {
scaleWeight: 'Báscula',
netWeight: 'Neto',
lastUsed: 'Usada por última vez',
+ unknownColor: 'Color desconocido',
},
ams: {
noData: 'No se detectó ningún AMS',
@@ -6979,7 +6988,7 @@ export default {
skip: 'No comprobado — se necesita una conexión MQTT activa. En slicers más antiguos donde este ajuste solo existe en el slicer, la impresora no lo reporta, así que esta comprobación pasa aunque la opción esté desactivada — verifique el paso 4 de la instalación manualmente.',
skip_unsupported_model: 'Este modelo tiene ranura SD pero no hay forma de activar la opción — el firmware actual de la serie P1 no muestra el interruptor en Bambu Studio y la impresora no tiene pantalla. Aquí no hay nada que arreglar; a las impresiones archivadas pueden faltarles miniaturas y metadatos del slicer hasta que Bambu Lab lo admita por firmware.',
fail_no_media: 'La opción está activada, pero la impresora no detecta ninguna tarjeta ni memoria en su ranura, así que los archivos enviados no tienen dónde ir. Inserte una e imprima de nuevo — hasta entonces, cada impresión archivada carecerá de miniatura y de metadatos del laminador.',
- warn_internal_storage: 'La opción está activada y hay almacenamiento presente, pero la última impresión aun así fue al almacenamiento interno de la impresora, que Bambuddy no puede leer. En la serie H2 y la P2S, el botón «Imprimir» de Bambu Studio siempre envía ahí, sea cual sea este ajuste. Las impresiones se archivan con su nombre y sus tiempos, pero sin miniatura ni metadatos del laminador. Para archivos completos, inicia las impresiones desde Bambuddy o lamina en OrcaSlicer, o bien en Bambu Studio usa «Enviar» con «Externo» y luego inicia la impresión.',
+ warn_internal_storage: 'La opción está activada y hay almacenamiento presente, pero la última impresión aun así fue al almacenamiento interno de la impresora, que Bambuddy no puede leer. En la serie H2, la P2S y la X2D, el botón «Imprimir» de Bambu Studio siempre envía ahí, sea cual sea este ajuste. Las impresiones se archivan con su nombre y sus tiempos, pero sin miniatura ni metadatos del laminador. Para archivos completos, inicia las impresiones desde Bambuddy o lamina en OrcaSlicer, o bien en Bambu Studio usa «Enviar» con «Externo» y luego inicia la impresión.',
warn_internal_history: 'La opción está activada y hay almacenamiento, pero la última impresión salió de un archivo que ya estaba en la impresora: una reimpresión desde su pantalla, un inicio desde Handy o un archivo enviado antes. Esa biblioteca reside en el almacenamiento interno, que Bambuddy no puede leer, y ninguna opción lo cambia, porque para esa impresión no se envió nada. Se sigue archivando con su nombre y sus tiempos, pero sin miniatura ni metadatos del laminador. Para archivos completos, inicia las impresiones desde Bambuddy o envíalas desde el laminador.',
},
port_rtsps: {
@@ -6987,11 +6996,20 @@ export default {
pass: 'Accesible — la transmisión de la cámara funcionará.',
warn: 'El puerto {{port}} no es accesible. La vista de la cámara en directo no funcionará. Esto no afecta a la impresión.',
},
+ macos_local_network: {
+ title: 'Permiso de red local de macOS',
+ pass: 'macOS permite que Bambuddy acceda a la red local.',
+ warn_unsigned: 'El Python que ejecuta Bambuddy no tiene firma de código, así que macOS no tiene a qué asociar el permiso de red local y descarta en silencio todas las conexiones con la impresora: sin error y sin solicitud. Ejecuta el actualizador de Bambuddy (install/update_macos.sh), que lo firma, y reinicia Bambuddy. Intérprete: {{executable}}',
+ warn_permission: 'Si la impresora está encendida y en esta dirección, abre Ajustes del Sistema > Privacidad y seguridad > Red local y comprueba que el Python de Bambuddy esté activado. Si no lo está, macOS descarta las conexiones locales en silencio, y actualizar Python puede dejar atrás el permiso anterior.',
+ },
network_mode: {
- title: 'Modo de red de Docker',
- pass: 'Ejecutándose en modo de red de host.',
- warn: 'Bambuddy se está ejecutando en red de tipo bridge de Docker. La detección de impresoras y la impresora virtual necesitan el modo de red de host — vuelva a crear el contenedor con "network_mode: host".',
- skip: 'No se está ejecutando en Docker — no aplicable.',
+ title: 'Modo de red del contenedor',
+ genericRuntime: 'un contenedor',
+ pass: 'Ejecutándose en {{runtime}} con red de host.',
+ warn: 'Bambuddy se está ejecutando en {{runtime}} con red de tipo bridge. La detección de impresoras y la impresora virtual necesitan el modo de red de host — vuelva a crear el contenedor con red de host ("network_mode: host" en docker-compose, "--network=host" en Podman).',
+ skip: 'No se está ejecutando en un contenedor — no aplicable.',
+ skip_unknown: 'Bambuddy se está ejecutando en {{runtime}}, pero no se pudo determinar su modo de red. Si la detección de impresoras o la impresora virtual no funcionan, vuelva a crear el contenedor con red de host.',
+ skip_system_container: 'Bambuddy se está ejecutando en un contenedor de sistema {{runtime}}, que está en la LAN como una máquina virtual — no aplicable.',
},
subnet: {
title: 'Subred de la red',
diff --git a/frontend/src/i18n/locales/fr.ts b/frontend/src/i18n/locales/fr.ts
index 886144857..45442817a 100644
--- a/frontend/src/i18n/locales/fr.ts
+++ b/frontend/src/i18n/locales/fr.ts
@@ -534,6 +534,8 @@ export default {
step: 'Pas (mm)',
up: 'Monter le plateau',
down: 'Descendre le plateau',
+ toolheadUp: 'Monter la tête',
+ toolheadDown: 'Descendre la tête',
disabledWhilePrinting: 'Désactivé pendant l\'impression',
notHomedTitle: 'Imprimante non référencée',
notHomedMessage: 'L\'imprimante n\'a pas été référencée depuis la dernière impression. Lancez la référence automatique d\'abord pour un positionnement sûr (parque la tête d\'outil, puis référence X, Y et Z), ou déplacez quand même — les butées logicielles seront ignorées.',
@@ -901,11 +903,15 @@ export default {
docsLink: 'Voir l\'étape 4 de l\'installation',
docsLinkInternalStorage: 'Pourquoi cela arrive',
titleInternalStorage: 'Certaines impressions récentes sont restées dans le stockage interne de l\'imprimante',
- bodyInternalStorage: 'Bambu Studio a placé le fichier tranché dans le stockage interne de l\'imprimante au lieu de la carte, Bambuddy n\'avait donc rien à lire en FTP. Sur les séries H2 et P2S, son bouton « Imprimer » le fait toujours : seul « Envoyer » propose un choix, et il est lui aussi réglé sur « Cache » par défaut. Ces impressions restent archivées avec leur nom et leurs durées, simplement sans miniature ni métadonnées slicer. Pour des archives complètes, lancez l\'impression depuis Bambuddy ou tranchez dans OrcaSlicer, ou bien dans Bambu Studio utilisez « Envoyer » avec « Externe » puis lancez l\'impression. Toutes nécessitent une carte ou une clé dans l\'imprimante.',
+ bodyInternalStorage: 'Bambu Studio a placé le fichier tranché dans le stockage interne de l\'imprimante au lieu de la carte, Bambuddy n\'avait donc rien à lire en FTP. Sur les séries H2, P2S et X2D, son bouton « Imprimer » le fait toujours : seul « Envoyer » propose un choix, et il est lui aussi réglé sur « Cache » par défaut. Ces impressions restent archivées avec leur nom et leurs durées, simplement sans miniature ni métadonnées slicer. Pour des archives complètes, lancez l\'impression depuis Bambuddy ou tranchez dans OrcaSlicer, ou bien dans Bambu Studio utilisez « Envoyer » avec « Externe » puis lancez l\'impression. Toutes nécessitent une carte ou une clé dans l\'imprimante.',
titleNoExternalStorage: 'Certaines impressions récentes n\'ont pas pu être archivées — aucun stockage dans l\'imprimante',
bodyNoExternalStorage: 'L\'imprimante ne signale ni carte ni clé dans son emplacement, le fichier tranché n\'avait donc nulle part où atterrir et Bambuddy rien à lire. Insérez-en une et la prochaine impression sera archivée complètement.',
titleInternalHistory: 'Certaines impressions récentes ont été lancées depuis un fichier déjà présent sur l\'imprimante',
bodyInternalHistory: 'Ces impressions sont parties de la bibliothèque de l\'imprimante elle-même : une réimpression depuis son écran, un lancement depuis Handy, ou un fichier envoyé plus tôt et imprimé ensuite. Bambuddy lit les fichiers d\'impression en FTP, qui ne dessert que la carte ou la clé, tandis que l\'imprimante conserve cette bibliothèque dans une zone que le FTP n\'atteint pas : il n\'y avait donc aucun 3MF à lire. Aucun réglage du trancheur n\'y change quoi que ce soit, puisque rien n\'a été envoyé pour ces impressions. Elles restent archivées avec leur nom et leurs durées, et « Modifier l\'archive » permet de saisir à la main le filament utilisé. Pour une archive complète, lancez plutôt l\'impression depuis Bambuddy ou depuis votre trancheur.',
+ titleFtpsCooloff: 'Certaines impressions récentes n\'ont pas pu être archivées — l\'imprimante a refusé la connexion de fichiers',
+ bodyFtpsCooloff: 'Bambuddy a ouvert le port de transfert de fichiers de l\'imprimante (FTPS 990) et celle-ci a répondu par autre chose que du TLS, si bien que rien n\'a pu en être lu. Ces impressions restent archivées avec leur nom et leurs durées, simplement sans vignette ni métadonnées du trancheur. Ce n\'est pas un réglage du trancheur ni quelque chose que vous avez modifié — les mêmes modèles et les mêmes firmwares fonctionnent normalement sur d\'autres installations, et une imprimante touchée refonctionne généralement d\'elle-même plus tard. Après un tel échec, Bambuddy suspend les transferts vers cette imprimante pendant cinq minutes puis revient chercher le fichier une fois la pause terminée : un épisode bref se répare donc tout seul, tandis qu\'une fiche encore vide signifie que le refus a duré plus longtemps que la nouvelle tentative. On ignore encore ce qui le déclenche. Si cela se reproduit, activez la journalisation de débogage sur la page Système et joignez un paquet de support à votre signalement.',
+ titleFtpTransferFailed: 'Certaines impressions récentes n\'ont pas pu être archivées : le transfert du fichier a dépassé le temps imparti',
+ bodyFtpTransferFailed: 'Le fichier tranché se trouvait sur la carte de l\'imprimante et l\'imprimante l\'a bien servi, mais le transfert ne s\'est pas terminé dans le temps imparti. Au démarrage d\'une impression, l\'imprimante gère aussi sa caméra, ses messages d\'état et l\'envoi du travail, et un gros 3MF ne passe pas toujours face à tout cela. Ces impressions restent archivées avec leur nom et leurs durées, simplement sans miniature ni métadonnées du trancheur. Ce n\'est pas un réglage du trancheur ni quelque chose que vous avez modifié. Bambuddy revient chercher le fichier trois fois au cours des dix minutes suivantes, la plupart de ces cas se règlent donc d\'eux-mêmes ; une carte toujours vide signifie que chacune de ces tentatives a également dépassé le temps imparti. Si cela se reproduit, augmentez Délai de connexion dans Paramètres > Réseau > Nouvelle tentative FTP.',
dismissLabel: 'Ignorer ce message',
},
searchPlaceholder: 'Chercher dans les archives...',
@@ -1487,7 +1493,7 @@ export default {
// Time
time: {
etaIfStartedNow: 'Heure de fin si cette tâche démarrait maintenant',
- asap: 'Dès que possible',
+ whenFree: 'Dès qu\'une imprimante est libre',
overdue: 'En retard',
now: 'Maintenant',
lessThanMinute: 'Dans moins d\'une minute',
@@ -1511,6 +1517,7 @@ export default {
staged: 'Préparé (manuel)',
autoPowerOff: 'Extinction auto après',
requirePrevious: 'Requiert succès précédent',
+ gcodeInjection: 'Injecter le G-code',
printOptions: 'Options d\'impression',
bedLevelling: 'Nivellement plateau',
flowCalibration: 'Calibration débit',
@@ -2286,7 +2293,7 @@ export default {
preheatEnabled: 'Activer préchauffage & soak',
preheatEnabledDesc: 'Si désactivé, les impressions en file démarrent immédiatement. Chaque élément peut surcharger par impression.',
preheatFilamentTargetsLabel: 'Cible chambre par filament (°C)',
- preheatFilamentTargetsHint: 'Bambuddy choisit la cible la plus haute parmi les slots AMS chargés ; les impressions PLA uniquement dérivent 0 et sautent la phase chambre automatiquement.',
+ preheatFilamentTargetsHint: 'Bambuddy choisit la cible la plus haute parmi les slots AMS chargés. Une impression dont tous les slots donnent 0 — PLA, PETG, TPU, PVA — saute entièrement le préchauffage et démarre immédiatement.',
preheatFilamentTargetsReset: 'Restaurer les valeurs par défaut',
preheatFilamentTargetsDefaultRow: 'Autre / non mappé',
preheatMaxWait: 'Attente max (secondes)',
@@ -2303,6 +2310,7 @@ export default {
calibrationMode_on: 'Activé',
calibrationMode_auto: 'Auto',
preheatTargetOverride: 'Surcharger la cible chambre (°C, vide = par filament)',
+ preheatTargetOverrideHelp: '0 chauffe le plateau et exécute le maintien sans la chambre. Laissé vide, une impression sans besoin de chambre saute entièrement le préchauffage.',
plateClear: 'Confirmation de plateau libre',
requirePlateClear: 'Exiger la confirmation de plateau libre',
requirePlateClearDescription: 'Lorsque cette option est activée, le planificateur attend une confirmation de plateau libre par imprimante avant de lancer les impressions en file d\'attente sur les imprimantes ayant terminé. La désactiver masque également le badge d\'état du plateau et le bouton « Marquer le plateau comme dégagé » sur les cartes d\'imprimante.',
@@ -4904,7 +4912,7 @@ export default {
linkToSpool: 'Lier à une Bobine',
tagLinked: 'Tag lié à la bobine',
tagLinkFailed: 'Échec lien tag',
- tagAlreadyLinked: 'Tag déjà lié à une autre bobine',
+ tagAlreadyLinked: 'Tag déjà lié à la bobine #{{id}}',
unknownTag: 'Tag RFID inconnu détecté',
// Usage history
usageHistory: 'Historique de Consommation',
@@ -5391,7 +5399,7 @@ export default {
project: 'Projet',
noProject: 'Aucun projet',
itemsPrinted: 'Nombre de pièces',
- itemsPrintedHelp: 'Nombre d\'objets produits',
+ itemsPrintedHelp: 'Nombre d\'objets produits. Indiquez 0 si tout le plateau est raté.',
filamentUsed: 'Filament utilisé (g)',
filamentUsedPlaceholder: 'p. ex. 46.16',
filamentUsedHelp: 'À saisir à la main lorsqu\'une impression a été archivée sans son 3MF, afin qu\'elle compte quand même dans les totaux de filament. Une nouvelle analyse d\'une archive qui possède son 3MF relit la valeur depuis le fichier.',
@@ -6784,6 +6792,7 @@ export default {
scaleWeight: 'Balance',
netWeight: 'Réseau',
lastUsed: 'Dernière utilisation',
+ unknownColor: 'Couleur inconnue',
},
ams: {
noData: 'Aucun AMS détecté',
@@ -6961,7 +6970,7 @@ export default {
skip: 'Non vérifié — une connexion MQTT active est requise. Sur les slicers plus anciens où ce paramètre n\'existe que dans le slicer, l\'imprimante ne le signale pas, donc cette vérification passe même si l\'option est désactivée — vérifiez l\'étape 4 de l\'installation manuellement.',
skip_unsupported_model: 'Ce modèle a un emplacement SD mais aucun moyen d\'activer l\'option — le firmware actuel de la série P1 n\'affiche pas le bouton dans Bambu Studio et l\'imprimante n\'a pas d\'écran. Il n\'y a rien à corriger ici ; les impressions archivées peuvent manquer de miniatures et de métadonnées du slicer jusqu\'à ce que Bambu Lab l\'ajoute par firmware.',
fail_no_media: 'L\'option est activée, mais l\'imprimante ne signale ni carte ni clé dans son emplacement : les fichiers envoyés n\'ont nulle part où aller. Insérez-en une et relancez une impression — d\'ici là, chaque impression archivée sera dépourvue de miniature et de métadonnées slicer.',
- warn_internal_storage: 'L\'option est activée et un stockage est présent, mais la dernière impression est tout de même allée dans le stockage interne de l\'imprimante, que Bambuddy ne peut pas lire. Sur les séries H2 et P2S, le bouton « Imprimer » de Bambu Studio y envoie toujours, quel que soit ce réglage. Les impressions sont archivées avec leur nom et leurs durées, mais sans miniature ni métadonnées slicer. Pour des archives complètes, lancez les impressions depuis Bambuddy ou tranchez dans OrcaSlicer, ou bien dans Bambu Studio utilisez « Envoyer » avec « Externe » puis lancez l\'impression.',
+ warn_internal_storage: 'L\'option est activée et un stockage est présent, mais la dernière impression est tout de même allée dans le stockage interne de l\'imprimante, que Bambuddy ne peut pas lire. Sur les séries H2, P2S et X2D, le bouton « Imprimer » de Bambu Studio y envoie toujours, quel que soit ce réglage. Les impressions sont archivées avec leur nom et leurs durées, mais sans miniature ni métadonnées slicer. Pour des archives complètes, lancez les impressions depuis Bambuddy ou tranchez dans OrcaSlicer, ou bien dans Bambu Studio utilisez « Envoyer » avec « Externe » puis lancez l\'impression.',
warn_internal_history: 'L\'option est activée et un stockage est présent, mais la dernière impression est partie d\'un fichier déjà présent sur l\'imprimante : une réimpression depuis son écran, un lancement depuis Handy ou un fichier envoyé plus tôt. Cette bibliothèque réside dans le stockage interne, que Bambuddy ne peut pas lire, et aucun réglage n\'y change rien, puisque rien n\'a été envoyé pour cette impression. Elle reste archivée avec son nom et ses durées, mais sans miniature ni métadonnées slicer. Pour des archives complètes, lancez les impressions depuis Bambuddy ou envoyez-les depuis le trancheur.',
},
port_rtsps: {
@@ -6969,11 +6978,20 @@ export default {
pass: 'Accessible — le flux de la caméra fonctionnera.',
warn: 'Le port {{port}} est inaccessible. La vue caméra en direct ne fonctionnera pas. Cela n\'affecte pas l\'impression.',
},
+ macos_local_network: {
+ title: 'Autorisation « Réseau local » de macOS',
+ pass: 'macOS autorise Bambuddy à accéder au réseau local.',
+ warn_unsigned: 'Le Python qui exécute Bambuddy n’a pas de signature de code : macOS n’a donc rien à quoi rattacher l’autorisation « Réseau local » et rejette silencieusement toutes les connexions vers l’imprimante, sans erreur ni demande. Lancez le script de mise à jour de Bambuddy (install/update_macos.sh), qui le signe, puis redémarrez Bambuddy. Interpréteur : {{executable}}',
+ warn_permission: 'Si l’imprimante est allumée et joignable à cette adresse, ouvrez Réglages Système > Confidentialité et sécurité > Réseau local et vérifiez que le Python de Bambuddy est activé. Sinon macOS rejette les connexions locales en silence, et une mise à jour de Python peut laisser l’ancienne autorisation derrière elle.',
+ },
network_mode: {
- title: 'Mode réseau Docker',
- pass: 'Fonctionne en mode réseau host.',
- warn: 'Bambuddy fonctionne en réseau Docker bridge. La découverte d\'imprimantes et l\'imprimante virtuelle nécessitent le mode réseau host — recréez le conteneur avec "network_mode: host".',
- skip: 'Ne fonctionne pas dans Docker — non applicable.',
+ title: 'Mode réseau du conteneur',
+ genericRuntime: 'un conteneur',
+ pass: 'Fonctionne dans {{runtime}} en réseau host.',
+ warn: 'Bambuddy fonctionne dans {{runtime}} en réseau bridge. La découverte d\'imprimantes et l\'imprimante virtuelle nécessitent le mode réseau host — recréez le conteneur en réseau host ("network_mode: host" dans docker-compose, "--network=host" pour Podman).',
+ skip: 'Ne fonctionne pas dans un conteneur — non applicable.',
+ skip_unknown: 'Bambuddy fonctionne dans {{runtime}}, mais son mode réseau n\'a pas pu être déterminé. Si la découverte d\'imprimantes ou l\'imprimante virtuelle ne fonctionnent pas, recréez le conteneur en réseau host.',
+ skip_system_container: 'Bambuddy fonctionne dans un conteneur système {{runtime}}, présent sur le LAN comme une machine virtuelle — non applicable.',
},
subnet: {
title: 'Sous-réseau',
diff --git a/frontend/src/i18n/locales/it.ts b/frontend/src/i18n/locales/it.ts
index 5a53969f1..19c3e5190 100644
--- a/frontend/src/i18n/locales/it.ts
+++ b/frontend/src/i18n/locales/it.ts
@@ -534,6 +534,8 @@ export default {
step: 'Passo (mm)',
up: 'Sposta piano su',
down: 'Sposta piano giù',
+ toolheadUp: 'Sposta testina su',
+ toolheadDown: 'Sposta testina giù',
disabledWhilePrinting: 'Disabilitato durante la stampa',
notHomedTitle: 'Stampante non azzerata',
notHomedMessage: 'La stampante non è stata azzerata dall\'ultima stampa. Esegui prima l\'azzeramento automatico per un posizionamento sicuro (parcheggia la testa di stampa, poi azzera X, Y e Z), oppure muovi comunque — i finecorsa software verranno ignorati.',
@@ -901,11 +903,15 @@ export default {
docsLink: 'Vedi passo 4 dell\'installazione',
docsLinkInternalStorage: 'Perché succede',
titleInternalStorage: 'Alcune stampe recenti sono rimaste nella memoria interna della stampante',
- bodyInternalStorage: 'Bambu Studio ha messo il file elaborato nella memoria interna della stampante anziché sulla scheda, quindi Bambuddy non aveva nulla da leggere via FTP. Sulla serie H2 e sulla P2S il pulsante «Stampa» lo fa sempre: solo «Invia» offre una scelta, e anche quella è impostata su «Cache». Quelle stampe restano archiviate con nome e tempi, solo senza miniatura né metadati dello slicer. Per archivi completi, avvia la stampa da Bambuddy oppure elabora in OrcaSlicer, oppure in Bambu Studio usa «Invia» con «Esterna» e avvia la stampa dopo. Tutte richiedono una scheda o una chiavetta nella stampante.',
+ bodyInternalStorage: 'Bambu Studio ha messo il file elaborato nella memoria interna della stampante anziché sulla scheda, quindi Bambuddy non aveva nulla da leggere via FTP. Sulla serie H2, sulla P2S e sulla X2D il pulsante «Stampa» lo fa sempre: solo «Invia» offre una scelta, e anche quella è impostata su «Cache». Quelle stampe restano archiviate con nome e tempi, solo senza miniatura né metadati dello slicer. Per archivi completi, avvia la stampa da Bambuddy oppure elabora in OrcaSlicer, oppure in Bambu Studio usa «Invia» con «Esterna» e avvia la stampa dopo. Tutte richiedono una scheda o una chiavetta nella stampante.',
titleNoExternalStorage: 'Alcune stampe recenti non sono state archiviate — nessuna memoria nella stampante',
bodyNoExternalStorage: 'La stampante non rileva né scheda né chiavetta nel suo slot, quindi il file elaborato non aveva dove finire e Bambuddy nulla da leggere. Inseriscine una e la prossima stampa verrà archiviata per intero.',
titleInternalHistory: 'Alcune stampe recenti sono partite da un file già presente sulla stampante',
bodyInternalHistory: 'Quelle stampe sono uscite dalla libreria della stampante stessa: una ristampa dal suo schermo, un avvio da Handy o un file inviato prima e stampato dopo. Bambuddy legge i file di stampa via FTP, che serve solo la scheda o la chiavetta, mentre la stampante tiene quella libreria in un\'area che l\'FTP non raggiunge, quindi non c\'era alcun 3MF da leggere. Nessuna impostazione dello slicer cambia questo, perché per queste stampe non è stato inviato nulla. Restano archiviate con nome e tempi, e «Modifica archivio» consente di inserire a mano il filamento usato. Per un archivio completo, avvia la stampa da Bambuddy o dal tuo slicer.',
+ titleFtpsCooloff: 'Alcune stampe recenti non sono state archiviate — la stampante ha rifiutato la connessione dei file',
+ bodyFtpsCooloff: 'Bambuddy ha aperto la porta di trasferimento file della stampante (FTPS 990) e la stampante ha risposto con qualcosa che non è TLS, quindi non è stato possibile leggere nulla. Quelle stampe restano archiviate con nome e tempi, solo senza miniatura né metadati dello slicer. Non è un\'impostazione dello slicer né qualcosa che hai cambiato tu — gli stessi modelli e firmware funzionano normalmente su altre installazioni, e una stampante colpita di solito torna a funzionare da sola più tardi. Dopo un errore simile Bambuddy sospende i trasferimenti verso quella stampante per cinque minuti e ritorna a prendere il file al termine della pausa, così un episodio breve si risolve da sé; una scheda ancora vuota significa che il rifiuto è durato più del secondo tentativo. Non si sa ancora che cosa lo scateni. Se continua a capitare, attiva la registrazione di debug nella pagina Sistema e allega un pacchetto di supporto alla tua segnalazione.',
+ titleFtpTransferFailed: 'Alcune stampe recenti non sono state archiviate: il trasferimento del file ha esaurito il tempo',
+ bodyFtpTransferFailed: 'Il file elaborato era sulla scheda della stampante e la stampante lo ha effettivamente servito, ma il trasferimento non si è concluso nel tempo consentito. All\'avvio della stampa la stampante gestisce anche la telecamera, i messaggi di stato e il caricamento del lavoro, e un 3MF di grandi dimensioni non sempre riesce a passare. Quelle stampe restano archiviate con nome e tempi, solo senza miniatura né metadati dello slicer. Non è un\'impostazione dello slicer né qualcosa che hai cambiato. Bambuddy torna a prendere il file tre volte nei dieci minuti successivi, quindi la maggior parte dei casi si risolve da sola; una scheda ancora vuota significa che anche tutti quei tentativi hanno esaurito il tempo. Se continua a succedere, aumenta Timeout di connessione in Impostazioni > Rete > Nuovo tentativo FTP.',
dismissLabel: 'Chiudi questo avviso',
},
searchPlaceholder: 'Cerca archivi...',
@@ -1487,7 +1493,7 @@ export default {
// Time
time: {
etaIfStartedNow: 'Orario di completamento se questo lavoro iniziasse ora',
- asap: 'ASAP',
+ whenFree: 'Quando una stampante è libera',
overdue: 'Scaduto',
now: 'Ora',
lessThanMinute: 'Tra meno di un minuto',
@@ -1511,6 +1517,7 @@ export default {
staged: 'In staging (avvio manuale)',
autoPowerOff: 'Spegnimento automatico dopo stampa',
requirePrevious: 'Richiede successo precedente',
+ gcodeInjection: 'Inietta G-code',
printOptions: 'Opzioni stampa',
bedLevelling: 'Livellamento piatto',
flowCalibration: 'Calibrazione flusso',
@@ -2286,7 +2293,7 @@ export default {
preheatEnabled: 'Abilita preriscaldo & soak',
preheatEnabledDesc: 'Se disattivato, le stampe in coda partono subito. Ogni elemento può sovrascrivere per singola stampa.',
preheatFilamentTargetsLabel: 'Target camera per filamento (°C)',
- preheatFilamentTargetsHint: 'Bambuddy sceglie il target più alto tra gli slot AMS caricati; le stampe solo PLA derivano 0 e saltano la fase camera automaticamente.',
+ preheatFilamentTargetsHint: 'Bambuddy sceglie il target più alto tra gli slot AMS caricati. Una stampa i cui slot derivano tutti 0 — PLA, PETG, TPU, PVA — salta del tutto il preriscaldamento e parte subito.',
preheatFilamentTargetsReset: 'Ripristina valori predefiniti',
preheatFilamentTargetsDefaultRow: 'Altro / non mappato',
preheatMaxWait: 'Attesa max (secondi)',
@@ -2303,6 +2310,7 @@ export default {
calibrationMode_on: 'Attivo',
calibrationMode_auto: 'Auto',
preheatTargetOverride: 'Sovrascrivi target camera (°C, vuoto = per filamento)',
+ preheatTargetOverrideHelp: '0 riscalda il piano ed esegue il mantenimento senza camera. Se lasciato vuoto, una stampa che non richiede la camera salta del tutto il preriscaldamento.',
plateClear: 'Conferma piatto libero',
requirePlateClear: 'Richiedi conferma piatto libero',
requirePlateClearDescription: 'Quando questa opzione è abilitata, lo scheduler attende una conferma per stampante che il piatto sia libero prima di avviare le stampe in coda su stampanti con lavori completati. Disabilitandola vengono nascosti anche il badge di stato del piatto e il pulsante "Segna il piatto come liberato" sulle schede stampante.',
@@ -4903,7 +4911,7 @@ export default {
linkToSpool: 'Collega a bobina',
tagLinked: 'Tag collegato alla bobina',
tagLinkFailed: 'Impossibile collegare il tag',
- tagAlreadyLinked: 'Tag già collegato a un\'altra bobina',
+ tagAlreadyLinked: 'Tag già collegato alla bobina #{{id}}',
unknownTag: 'Tag RFID sconosciuto rilevato',
// Usage history
usageHistory: 'Cronologia utilizzo',
@@ -5390,7 +5398,7 @@ export default {
project: 'Progetto',
noProject: 'Nessun progetto',
itemsPrinted: 'Elementi stampati',
- itemsPrintedHelp: 'Numero di elementi prodotti in questo job di stampa',
+ itemsPrintedHelp: 'Numero di elementi prodotti in questo job di stampa. Usa 0 se l\'intero piatto è rovinato.',
filamentUsed: 'Filamento usato (g)',
filamentUsedPlaceholder: 'es. 46.16',
filamentUsedHelp: 'Inseriscilo a mano quando una stampa è stata archiviata senza il suo 3MF, così da farla rientrare comunque nei totali del filamento. La riscansione di un archivio che ha il suo 3MF rilegge il valore dal file.',
@@ -6783,6 +6791,7 @@ export default {
scaleWeight: 'Bilancia',
netWeight: 'Netto',
lastUsed: 'Ultimo utilizzo',
+ unknownColor: 'Colore sconosciuto',
},
ams: {
noData: 'Nessun AMS rilevato',
@@ -6960,7 +6969,7 @@ export default {
skip: 'Non verificato — è necessaria una connessione MQTT attiva. Negli slicer più vecchi dove questa impostazione esiste solo nello slicer, la stampante non la segnala, quindi questo controllo passa anche se l\'opzione è disattivata — verifica manualmente il passo 4 dell\'installazione.',
skip_unsupported_model: 'Questo modello ha uno slot SD ma nessun modo per attivare l\'opzione — il firmware attuale della serie P1 non mostra l\'interruttore in Bambu Studio e la stampante non ha uno schermo. Non c\'è nulla da correggere qui; alle stampe archiviate potrebbero mancare miniature e metadati dello slicer finché Bambu Lab non aggiungerà il supporto via firmware.',
fail_no_media: 'L\'opzione è attiva, ma la stampante non rileva né scheda né chiavetta nel suo slot, quindi i file inviati non hanno dove andare. Inseriscine una e stampa di nuovo — fino ad allora ogni stampa archiviata sarà priva di miniatura e metadati dello slicer.',
- warn_internal_storage: 'L\'opzione è attiva ed è presente una memoria, ma l\'ultima stampa è comunque finita nella memoria interna della stampante, che Bambuddy non può leggere. Sulla serie H2 e sulla P2S il pulsante «Stampa» di Bambu Studio invia sempre lì, a prescindere da questa opzione. Le stampe vengono archiviate con nome e tempi, ma senza miniatura né metadati dello slicer. Per archivi completi, avvia le stampe da Bambuddy oppure elabora in OrcaSlicer, oppure in Bambu Studio usa «Invia» con «Esterna» e avvia la stampa dopo.',
+ warn_internal_storage: 'L\'opzione è attiva ed è presente una memoria, ma l\'ultima stampa è comunque finita nella memoria interna della stampante, che Bambuddy non può leggere. Sulla serie H2, sulla P2S e sulla X2D il pulsante «Stampa» di Bambu Studio invia sempre lì, a prescindere da questa opzione. Le stampe vengono archiviate con nome e tempi, ma senza miniatura né metadati dello slicer. Per archivi completi, avvia le stampe da Bambuddy oppure elabora in OrcaSlicer, oppure in Bambu Studio usa «Invia» con «Esterna» e avvia la stampa dopo.',
warn_internal_history: 'L\'opzione è attiva e la memoria è presente, ma l\'ultima stampa è partita da un file già presente sulla stampante: una ristampa dal suo schermo, un avvio da Handy o un file inviato prima. Quella libreria si trova nella memoria interna, che Bambuddy non può leggere, e nessuna impostazione lo cambia, perché per quella stampa non è stato inviato nulla. Resta archiviata con nome e tempi, ma senza miniatura né metadati dello slicer. Per archivi completi, avvia le stampe da Bambuddy o inviale dallo slicer.',
},
port_rtsps: {
@@ -6968,11 +6977,20 @@ export default {
pass: 'Raggiungibile — lo streaming della fotocamera funzionerà.',
warn: 'La porta {{port}} non è raggiungibile. La visualizzazione live della fotocamera non funzionerà. Questo non influisce sulla stampa.',
},
+ macos_local_network: {
+ title: 'Autorizzazione « Rete locale » di macOS',
+ pass: 'macOS consente a Bambuddy di raggiungere la rete locale.',
+ warn_unsigned: 'Il Python che esegue Bambuddy non ha una firma del codice, quindi macOS non ha nulla a cui associare l’autorizzazione « Rete locale » e scarta in silenzio ogni connessione alla stampante, senza errori e senza richiesta. Esegui lo script di aggiornamento di Bambuddy (install/update_macos.sh), che lo firma, poi riavvia Bambuddy. Interprete: {{executable}}',
+ warn_permission: 'Se la stampante è accesa e raggiungibile a questo indirizzo, apri Impostazioni di Sistema > Privacy e sicurezza > Rete locale e verifica che il Python di Bambuddy sia abilitato. In caso contrario macOS scarta le connessioni locali in silenzio, e un aggiornamento di Python può lasciare indietro la vecchia autorizzazione.',
+ },
network_mode: {
- title: 'Modalità di rete Docker',
- pass: 'In esecuzione in modalità di rete host.',
- warn: 'Bambuddy è in esecuzione con la rete Docker bridge. Il rilevamento delle stampanti e la stampante virtuale richiedono la modalità di rete host — ricrea il container con "network_mode: host".',
- skip: 'Non in esecuzione in Docker — non applicabile.',
+ title: 'Modalità di rete del container',
+ genericRuntime: 'un container',
+ pass: 'In esecuzione in {{runtime}} con rete host.',
+ warn: 'Bambuddy è in esecuzione in {{runtime}} con rete bridge. Il rilevamento delle stampanti e la stampante virtuale richiedono la modalità di rete host — ricrea il container con rete host ("network_mode: host" in docker-compose, "--network=host" per Podman).',
+ skip: 'Non in esecuzione in un container — non applicabile.',
+ skip_unknown: 'Bambuddy è in esecuzione in {{runtime}}, ma non è stato possibile determinarne la modalità di rete. Se il rilevamento delle stampanti o la stampante virtuale non funzionano, ricrea il container con rete host.',
+ skip_system_container: 'Bambuddy è in esecuzione in un container di sistema {{runtime}}, presente sulla LAN come una macchina virtuale — non applicabile.',
},
subnet: {
title: 'Sottorete',
diff --git a/frontend/src/i18n/locales/ja.ts b/frontend/src/i18n/locales/ja.ts
index 1bfec7d34..2d65adaa0 100644
--- a/frontend/src/i18n/locales/ja.ts
+++ b/frontend/src/i18n/locales/ja.ts
@@ -533,6 +533,8 @@ export default {
step: 'ステップ (mm)',
up: 'プレートを上へ',
down: 'プレートを下へ',
+ toolheadUp: 'ツールヘッドを上へ',
+ toolheadDown: 'ツールヘッドを下へ',
disabledWhilePrinting: '印刷中は無効',
notHomedTitle: 'プリンターがホーミングされていません',
notHomedMessage: '前回の印刷以降、プリンターがホーミングされていません。安全な位置決めのためにまずオートホーミングを実行するか(ツールヘッドをパークしてからX・Y・Zをホーミングします)、このまま移動してください — ソフトエンドストップはバイパスされます。',
@@ -900,11 +902,15 @@ export default {
docsLink: 'インストール手順4を参照',
docsLinkInternalStorage: '原因について',
titleInternalStorage: '最近の一部の印刷はプリンターの内部ストレージに残りました',
- bodyInternalStorage: 'Bambu Studio がスライス済みファイルをカードではなくプリンターの内部ストレージに保存したため、Bambuddy が FTP で読み取れるものがありませんでした。H2 シリーズと P2S では「印刷」ボタンは常にそうなり、選択できるのは「送信」だけで、そちらも既定は「キャッシュ」です。これらの印刷は名前と時間付きでアーカイブされますが、サムネイルとスライサーのメタデータはありません。完全なアーカイブを残すには、Bambuddy から印刷を開始するか、OrcaSlicer でスライスしてください。Bambu Studio を使う場合は「送信」で「外部ストレージ」を選び、その後に印刷を開始します。いずれもプリンターにカードまたは USB メモリーが必要です。',
+ bodyInternalStorage: 'Bambu Studio がスライス済みファイルをカードではなくプリンターの内部ストレージに保存したため、Bambuddy が FTP で読み取れるものがありませんでした。H2 シリーズ、P2S、X2D では「印刷」ボタンは常にそうなり、選択できるのは「送信」だけで、そちらも既定は「キャッシュ」です。これらの印刷は名前と時間付きでアーカイブされますが、サムネイルとスライサーのメタデータはありません。完全なアーカイブを残すには、Bambuddy から印刷を開始するか、OrcaSlicer でスライスしてください。Bambu Studio を使う場合は「送信」で「外部ストレージ」を選び、その後に印刷を開始します。いずれもプリンターにカードまたは USB メモリーが必要です。',
titleNoExternalStorage: '最近の一部の印刷をアーカイブできませんでした — プリンターにストレージがありません',
bodyNoExternalStorage: 'プリンターのスロットにカードもUSBメモリも検出されないため、スライス済みファイルの保存先がなく、Bambuddyが読み取るものもありませんでした。挿入すれば次の印刷は完全にアーカイブされます。',
titleInternalHistory: '最近の一部の印刷は、すでにプリンター内にあるファイルから開始されました',
bodyInternalHistory: 'これらの印刷はプリンター自身のライブラリから実行されました — 画面からの再印刷、Handy からの開始、または以前に送信して後から印刷したファイルです。Bambuddy は印刷ファイルを FTP で読み取りますが、FTP が扱えるのはカードまたは USB メモリだけで、プリンターはそのライブラリを FTP の届かない領域に保存するため、読み取れる 3MF がありませんでした。これらの印刷では何も送信されていないので、スライサーの設定を変えても解決しません。名前と時間付きでのアーカイブは続き、「アーカイブを編集」で使用フィラメントを手入力できます。完全なアーカイブにするには、Bambuddy またはスライサーから印刷を開始してください。',
+ titleFtpsCooloff: '最近の一部の印刷を保存できませんでした — プリンターがファイル接続を拒否しました',
+ bodyFtpsCooloff: 'Bambuddy がプリンターのファイル転送ポート (FTPS 990) を開いたところ、プリンターは TLS ではないもので応答したため、何も読み取れませんでした。これらの印刷は名前と時間付きでアーカイブされていますが、サムネイルとスライサーのメタデータはありません。これはスライサーの設定でも、お客様が変更したことでもありません。同じ機種・同じファームウェアが他の環境では正常に動作しており、影響を受けたプリンターも通常はしばらくすると自然に復帰します。この失敗のあと Bambuddy はそのプリンターへの転送を 5 分間停止し、停止が明けてからファイルを取りに戻ります。短時間の事象であれば自動的に埋まり、カードが空のままであれば拒否が再試行より長く続いたことを意味します。原因はまだ分かっていません。繰り返す場合は、システムページでデバッグログを有効にし、サポートバンドルを報告に添付してください。',
+ titleFtpTransferFailed: '最近の一部の印刷を保存できませんでした — ファイル転送が時間切れになりました',
+ bodyFtpTransferFailed: 'スライス済みファイルはプリンターのカードにあり、プリンターも実際に送り出していましたが、許容時間内に転送が完了しませんでした。印刷開始時のプリンターはカメラ、ステータス通知、ジョブのアップロードも同時に処理しているため、大きな 3MF はその中を通り切れないことがあります。これらの印刷は名前と時間つきで保存されていますが、サムネイルとスライサー情報がありません。これはスライサーの設定ではなく、お客様が変更した結果でもありません。Bambuddy はその後 10 分のあいだにファイルを 3 回取りに戻るため、多くはひとりでに埋まります。カードが空のままの場合は、そのすべての試行も時間切れになったということです。繰り返す場合は、設定 > ネットワーク > FTP リトライ の 接続タイムアウト を大きくしてください。',
dismissLabel: 'この通知を閉じる',
},
searchPlaceholder: 'アーカイブを検索...',
@@ -1486,7 +1492,7 @@ export default {
// Time
time: {
etaIfStartedNow: 'このジョブを今開始した場合の完了予定時刻',
- asap: '即時',
+ whenFree: 'プリンターが空き次第',
overdue: '期限超過',
now: '今すぐ',
lessThanMinute: '1分以内',
@@ -1510,6 +1516,7 @@ export default {
staged: 'ステージ済み',
autoPowerOff: '印刷後に自動電源オフ',
requirePrevious: '前の成功を必要とする',
+ gcodeInjection: 'G-codeを挿入',
printOptions: '印刷オプション',
bedLevelling: 'ベッドレベリング',
flowCalibration: 'フローキャリブレーション',
@@ -2329,7 +2336,7 @@ export default {
preheatEnabled: 'プレヒート & ソークを有効化',
preheatEnabledDesc: 'オフにすると、キュー内の印刷は即時開始されます。各キュー項目で印刷ごとに上書き可能です。',
preheatFilamentTargetsLabel: 'フィラメント別チャンバー目標 (°C)',
- preheatFilamentTargetsHint: 'Bambuddyはロード済みのAMSスロット中で最も高い目標値を選びます。PLAのみの印刷は0となり、チャンバー段階は自動でスキップされます。',
+ preheatFilamentTargetsHint: 'Bambuddyはロード済みのAMSスロット中で最も高い目標値を選びます。すべてのスロットが0となる印刷(PLA、PETG、TPU、PVA)は予熱を完全にスキップし、すぐに開始します。',
preheatFilamentTargetsReset: 'デフォルトにリセット',
preheatFilamentTargetsDefaultRow: 'その他 / 未登録',
preheatMaxWait: '最大待機時間(秒)',
@@ -2346,6 +2353,7 @@ export default {
calibrationMode_on: 'オン',
calibrationMode_auto: '自動',
preheatTargetOverride: 'チャンバー目標を上書き (°C、空欄でフィラメント既定値)',
+ preheatTargetOverrideHelp: '0 はチャンバーなしでベッドを加熱し、ソークを実行します。空欄のままにすると、チャンバーが不要な印刷は予熱を完全にスキップします。',
plateClear: 'プレートクリア確認',
requirePlateClear: 'プレートクリア確認を必須にする',
requirePlateClearDescription: '有効にすると、スケジューラーは完了したプリンターでキューの印刷を開始する前に、プリンターごとのプレートクリア確認を待ちます。無効にすると、プリンターカード上のプレート状態バッジと「プレートをクリア済みにする」ボタンも非表示になります。',
@@ -4915,7 +4923,7 @@ export default {
linkToSpool: 'スプールにリンク',
tagLinked: 'タグがスプールにリンクされました',
tagLinkFailed: 'タグのリンクに失敗しました',
- tagAlreadyLinked: 'タグは既に別のスプールにリンクされています',
+ tagAlreadyLinked: 'タグは既にスプール #{{id}} にリンクされています',
unknownTag: '不明なRFIDタグが検出されました',
// Usage history
usageHistory: '使用履歴',
@@ -5402,7 +5410,7 @@ export default {
project: 'プロジェクト',
noProject: 'プロジェクトなし',
itemsPrinted: '印刷数',
- itemsPrintedHelp: 'この印刷ジョブで製造したアイテム数',
+ itemsPrintedHelp: 'この印刷ジョブで製造したアイテム数。プレート全体が失敗した場合は 0 を入力します。',
filamentUsed: '使用フィラメント (g)',
filamentUsedPlaceholder: '例: 46.16',
filamentUsedHelp: '3MF なしでアーカイブされた印刷は、手入力するとフィラメント合計に反映されます。3MF があるアーカイブを再スキャンすると、この値はファイルから読み直されます。',
@@ -6795,6 +6803,7 @@ export default {
scaleWeight: '計量',
netWeight: '正味',
lastUsed: '最終使用',
+ unknownColor: '不明な色',
},
ams: {
noData: 'AMSが検出されません',
@@ -6972,7 +6981,7 @@ export default {
skip: '未確認 — アクティブなMQTT接続が必要です。古いスライサーでこの設定がスライサー側のみに存在する場合、プリンターはそれを報告しないため、オプションが無効でもこのチェックは通過します — インストール手順4を手動で確認してください。',
skip_unsupported_model: 'このモデルにはSDスロットがありますが、オプションを有効にする方法がありません — 現在のP1シリーズのファームウェアはBambu Studioにトグルを表示せず、プリンターに画面もありません。ここで修正すべきことはありません。Bambu Labがファームウェアで対応するまで、アーカイブされた印刷にはサムネイルやスライサーのメタデータが欠ける場合があります。',
fail_no_media: 'オプションは有効ですが、プリンターのスロットにカードもUSBメモリも検出されないため、送信ファイルの保存先がありません。挿入して再度印刷してください。それまでアーカイブされる印刷にはサムネイルとスライサーメタデータがありません。',
- warn_internal_storage: 'オプションは有効でストレージも装着されていますが、直近の印刷は Bambuddy が読み取れないプリンターの内部ストレージに保存されました。H2 シリーズと P2S では、この設定に関係なく Bambu Studio の「印刷」ボタンは常にそちらへ送信します。印刷は名前と時間付きでアーカイブされますが、サムネイルとスライサーのメタデータはありません。完全なアーカイブを残すには、Bambuddy から印刷を開始するか、OrcaSlicer でスライスするか、Bambu Studio で「送信」から「外部ストレージ」を選んだあとに印刷を開始してください。',
+ warn_internal_storage: 'オプションは有効でストレージも装着されていますが、直近の印刷は Bambuddy が読み取れないプリンターの内部ストレージに保存されました。H2 シリーズ、P2S、X2D では、この設定に関係なく Bambu Studio の「印刷」ボタンは常にそちらへ送信します。印刷は名前と時間付きでアーカイブされますが、サムネイルとスライサーのメタデータはありません。完全なアーカイブを残すには、Bambuddy から印刷を開始するか、OrcaSlicer でスライスするか、Bambu Studio で「送信」から「外部ストレージ」を選んだあとに印刷を開始してください。',
warn_internal_history: 'オプションは有効でストレージもありますが、直近の印刷はすでにプリンター内にあったファイルから実行されました — 画面からの再印刷、Handy からの開始、または以前に送信したファイルです。そのライブラリは Bambuddy が読めない内部ストレージにあり、この印刷では何も送信されていないため、どの設定を変えても解決しません。名前と時間付きでアーカイブはされますが、サムネイルとスライサーのメタデータはありません。完全なアーカイブにするには、Bambuddy から印刷を開始するか、スライサーから送信してください。',
},
port_rtsps: {
@@ -6980,11 +6989,20 @@ export default {
pass: '到達可能 — カメラストリームは機能します。',
warn: 'ポート{{port}}に到達できません。ライブカメラ表示は機能しません。これは印刷には影響しません。',
},
+ macos_local_network: {
+ title: 'macOS のローカルネットワーク権限',
+ pass: 'macOS は Bambuddy のローカルネットワークへのアクセスを許可しています。',
+ warn_unsigned: 'Bambuddy を実行している Python にコード署名がないため、macOS はローカルネットワーク権限を結び付ける対象を持てず、プリンターへの接続をエラーも確認ダイアログもなく破棄します。署名を行う Bambuddy の更新スクリプト (install/update_macos.sh) を実行してから、Bambuddy を再起動してください。インタープリター: {{executable}}',
+ warn_permission: 'プリンターの電源が入っていてこのアドレスで到達できる場合は、システム設定 > プライバシーとセキュリティ > ローカルネットワーク を開き、Bambuddy の Python が有効になっているか確認してください。無効だと macOS はローカル接続を無言で破棄します。また Python を更新すると以前の許可が引き継がれないことがあります。',
+ },
network_mode: {
- title: 'Dockerネットワークモード',
- pass: 'ホストネットワークモードで実行中です。',
- warn: 'BambuddyはDockerブリッジネットワークで実行されています。プリンター検出と仮想プリンターにはホストネットワークモードが必要です — "network_mode: host" でコンテナを再作成してください。',
- skip: 'Dockerで実行されていません — 該当しません。',
+ title: 'コンテナのネットワークモード',
+ genericRuntime: 'コンテナ',
+ pass: '{{runtime}} でホストネットワークを使用して実行中です。',
+ warn: 'Bambuddy は {{runtime}} でブリッジネットワークを使用して実行されています。プリンター検出と仮想プリンターにはホストネットワークが必要です — ホストネットワークでコンテナを再作成してください(docker-compose では "network_mode: host"、Podman では "--network=host")。',
+ skip: 'コンテナで実行されていません — 該当しません。',
+ skip_unknown: 'Bambuddy は {{runtime}} で実行されていますが、ネットワークモードを判別できませんでした。プリンター検出や仮想プリンターが動作しない場合は、ホストネットワークでコンテナを再作成してください。',
+ skip_system_container: 'Bambuddy は {{runtime}} のシステムコンテナで実行されており、仮想マシンと同様に LAN 上にあります — 該当しません。',
},
subnet: {
title: 'ネットワークサブネット',
diff --git a/frontend/src/i18n/locales/ko.ts b/frontend/src/i18n/locales/ko.ts
index 99ce6c3c1..6e22c3e6f 100644
--- a/frontend/src/i18n/locales/ko.ts
+++ b/frontend/src/i18n/locales/ko.ts
@@ -503,6 +503,8 @@ export default {
step: '이동 거리 (mm)',
up: '플레이트 위로',
down: '플레이트 아래로',
+ toolheadUp: '툴헤드 위로',
+ toolheadDown: '툴헤드 아래로',
disabledWhilePrinting: '인쇄 중 비활성화됨',
notHomedTitle: '프린터 홈 설정 필요',
notHomedMessage: '마지막 인쇄 이후 홈 설정이 되지 않았습니다. 안전한 위치 지정을 위해 자동 홈 설정을 먼저 실행하거나, 그냥 이동하세요 — 소프트 엔드스톱이 무시됩니다.',
@@ -857,11 +859,15 @@ export default {
docsLink: '설치 단계 4 보기',
docsLinkInternalStorage: '왜 이런 일이 생기나요',
titleInternalStorage: '최근 일부 출력물이 프린터 내부 저장소에 남았습니다',
- bodyInternalStorage: '슬라이싱된 파일을 Bambu Studio가 카드가 아니라 프린터 내부 저장소에 저장해서 Bambuddy가 FTP로 읽을 것이 없었습니다. H2 시리즈와 P2S에서는 «인쇄» 버튼이 항상 그렇게 동작하며, 선택할 수 있는 것은 «보내기»뿐인데 그것도 기본값이 «캐시»입니다. 해당 출력물은 이름과 시간과 함께 계속 보관되지만 썸네일과 슬라이서 메타데이터는 없습니다. 완전한 기록을 남기려면 Bambuddy에서 출력을 시작하거나 OrcaSlicer로 슬라이싱하세요. Bambu Studio를 쓴다면 «보내기»에서 «외부 저장소»를 고른 뒤 출력을 시작하면 됩니다. 모두 프린터에 카드나 USB 메모리가 필요합니다.',
+ bodyInternalStorage: '슬라이싱된 파일을 Bambu Studio가 카드가 아니라 프린터 내부 저장소에 저장해서 Bambuddy가 FTP로 읽을 것이 없었습니다. H2 시리즈, P2S, X2D에서는 «인쇄» 버튼이 항상 그렇게 동작하며, 선택할 수 있는 것은 «보내기»뿐인데 그것도 기본값이 «캐시»입니다. 해당 출력물은 이름과 시간과 함께 계속 보관되지만 썸네일과 슬라이서 메타데이터는 없습니다. 완전한 기록을 남기려면 Bambuddy에서 출력을 시작하거나 OrcaSlicer로 슬라이싱하세요. Bambu Studio를 쓴다면 «보내기»에서 «외부 저장소»를 고른 뒤 출력을 시작하면 됩니다. 모두 프린터에 카드나 USB 메모리가 필요합니다.',
titleNoExternalStorage: '최근 일부 출력물을 보관하지 못했습니다 — 프린터에 저장소가 없습니다',
bodyNoExternalStorage: '프린터 슬롯에 카드도 USB도 감지되지 않아 슬라이싱된 파일이 저장될 곳이 없었고 Bambuddy가 읽을 것도 없었습니다. 하나 넣으면 다음 출력물은 온전히 보관됩니다.',
titleInternalHistory: '최근 일부 출력물은 이미 프린터에 있던 파일에서 시작되었습니다',
bodyInternalHistory: '해당 출력물은 프린터 자체 라이브러리에서 실행되었습니다 — 화면에서의 재출력, Handy에서의 시작, 또는 이전에 보내 두고 나중에 출력한 파일입니다. Bambuddy는 출력 파일을 FTP로 읽는데 FTP는 카드나 USB만 제공하고, 프린터는 그 라이브러리를 FTP가 닿지 않는 영역에 보관하므로 읽을 3MF가 없었습니다. 이 출력물들은 아무것도 전송되지 않았으므로 슬라이서 설정으로는 해결되지 않습니다. 이름과 시간과 함께 계속 보관되며, "아카이브 편집"에서 사용된 필라멘트를 직접 입력할 수 있습니다. 온전한 보관을 원하면 Bambuddy나 슬라이서에서 출력을 시작하세요.',
+ titleFtpsCooloff: '최근 일부 출력물을 보관하지 못했습니다 — 프린터가 파일 연결을 거부했습니다',
+ bodyFtpsCooloff: 'Bambuddy가 프린터의 파일 전송 포트(FTPS 990)를 열었으나 프린터가 TLS가 아닌 것으로 응답해 아무것도 읽을 수 없었습니다. 해당 출력물은 이름과 시간과 함께 보관되지만 미리보기와 슬라이서 메타데이터는 없습니다. 이는 슬라이서 설정 문제도, 사용자가 바꾼 것도 아닙니다. 같은 모델과 같은 펌웨어가 다른 설치 환경에서는 정상 동작하며, 문제가 생긴 프린터도 대개 나중에 저절로 다시 동작합니다. 이런 실패 후 Bambuddy는 해당 프린터로의 전송을 5분간 멈추고, 그 후 파일을 다시 가지러 갑니다. 짧은 문제라면 스스로 채워지고, 카드가 계속 비어 있다면 거부가 재시도보다 오래 지속되었다는 뜻입니다. 무엇이 원인인지는 아직 밝혀지지 않았습니다. 반복된다면 시스템 페이지에서 디버그 로깅을 켠 뒤 지원 번들을 보고에 첨부해 주세요.',
+ titleFtpTransferFailed: '최근 일부 출력을 보관하지 못했습니다 — 파일 전송이 시간 내에 끝나지 않았습니다',
+ bodyFtpTransferFailed: '슬라이싱된 파일은 프린터 카드에 있었고 프린터도 실제로 전송을 시작했지만, 허용된 시간 안에 전송이 끝나지 않았습니다. 출력 시작 시점의 프린터는 카메라와 상태 메시지, 작업 업로드까지 함께 처리하고 있어서 큰 3MF 파일은 그 사이를 통과하지 못할 때가 있습니다. 해당 출력은 이름과 시간과 함께 보관되어 있고, 썸네일과 슬라이서 정보만 없습니다. 슬라이서 설정 문제도 아니고 사용자가 바꾼 것 때문도 아닙니다. Bambuddy가 이후 10분 동안 파일을 세 번 더 가지러 가므로 대부분은 저절로 채워집니다. 카드가 계속 비어 있다면 그 시도들도 모두 시간 내에 끝나지 않았다는 뜻입니다. 계속 발생하면 설정 > 네트워크 > FTP 재시도 의 연결 제한 시간을 늘리세요.',
dismissLabel: '이 알림 닫기'
},
searchPlaceholder: '아카이브 검색...',
@@ -1416,7 +1422,7 @@ export default {
},
time: {
etaIfStartedNow: '이 작업을 지금 시작할 경우의 완료 예정 시각',
- asap: '즉시',
+ whenFree: '프린터가 사용 가능해지면',
overdue: '기한 초과',
now: '지금',
lessThanMinute: '1분 이내',
@@ -1438,6 +1444,7 @@ export default {
staged: '준비됨 (수동 시작)',
autoPowerOff: '인쇄 후 자동 전원 끄기',
requirePrevious: '이전 성공 필요',
+ gcodeInjection: 'G코드 삽입',
printOptions: '인쇄 옵션',
bedLevelling: '베드 레벨링',
flowCalibration: '유량 캘리브레이션',
@@ -2210,7 +2217,7 @@ export default {
preheatEnabled: '예열 & 소크 활성화',
preheatEnabledDesc: '꺼짐일 경우 대기열 인쇄가 즉시 시작됩니다. 각 항목은 인쇄별로 재정의할 수 있습니다.',
preheatFilamentTargetsLabel: '필라멘트별 챔버 목표 (°C)',
- preheatFilamentTargetsHint: 'Bambuddy는 로드된 AMS 슬롯 중 가장 높은 목표를 선택합니다. PLA만 사용하는 인쇄는 0으로 도출되어 챔버 단계를 자동으로 건너뜁니다.',
+ preheatFilamentTargetsHint: 'Bambuddy는 로드된 AMS 슬롯 중 가장 높은 목표를 선택합니다. 모든 슬롯이 0으로 도출되는 인쇄(PLA, PETG, TPU, PVA)는 예열을 완전히 건너뛰고 즉시 시작합니다.',
preheatFilamentTargetsReset: '기본값으로 재설정',
preheatFilamentTargetsDefaultRow: '기타 / 미매핑',
preheatMaxWait: '최대 대기 (초)',
@@ -2227,6 +2234,7 @@ export default {
calibrationMode_on: '켜기',
calibrationMode_auto: '자동',
preheatTargetOverride: '챔버 목표 재정의 (°C, 비우면 필라멘트 기본값)',
+ preheatTargetOverrideHelp: '0은 챔버 없이 베드를 가열하고 소크를 실행합니다. 비워 두면 챔버가 필요 없는 인쇄는 예열을 완전히 건너뜁니다.',
plateClear: '플레이트 비움 확인',
requirePlateClear: '플레이트 비움 확인 필요',
requirePlateClearDescription: '활성화하면 스케줄러가 완료된 작업이 있는 프린터에서 대기 중인 인쇄를 시작하기 전에 프린터별 플레이트 비움 확인을 기다립니다.',
@@ -4682,7 +4690,7 @@ export default {
linkToSpool: '스풀에 연결',
tagLinked: '태그가 스풀에 연결됨',
tagLinkFailed: '태그 연결 실패',
- tagAlreadyLinked: '태그가 이미 다른 스풀에 연결됨',
+ tagAlreadyLinked: '태그가 이미 스풀 #{{id}}에 연결됨',
unknownTag: '알 수 없는 RFID 태그 감지됨',
usageHistory: '사용 기록',
noUsageHistory: '사용 기록 없음',
@@ -5151,7 +5159,7 @@ export default {
project: '프로젝트',
noProject: '프로젝트 없음',
itemsPrinted: '인쇄된 항목',
- itemsPrintedHelp: '이 인쇄 작업에서 생산된 항목 수',
+ itemsPrintedHelp: '이 인쇄 작업에서 생산된 항목 수. 플레이트 전체를 버린 경우 0을 입력하세요.',
filamentUsed: '사용된 필라멘트 (g)',
filamentUsedPlaceholder: '예: 46.16',
filamentUsedHelp: '3MF 없이 보관된 인쇄는 직접 입력하면 필라멘트 합계에 반영됩니다. 3MF가 있는 보관 항목을 다시 스캔하면 이 값은 파일에서 다시 읽어옵니다.',
@@ -6440,7 +6448,8 @@ export default {
labelWeight: '라벨',
scaleWeight: '저울',
netWeight: '순 무게',
- lastUsed: '마지막 사용'
+ lastUsed: '마지막 사용',
+ unknownColor: '알 수 없는 색상'
},
ams: {
noData: 'AMS가 감지되지 않음',
@@ -7072,7 +7081,7 @@ export default {
skip: '확인되지 않음 — 활성 MQTT 연결이 필요합니다. 이 설정이 슬라이서에만 존재하는 이전 슬라이서에서는 프린터가 보고하지 않으므로, 옵션이 꺼져 있어도 이 검사는 통과합니다 — 설치 단계 4를 수동으로 확인하세요.',
skip_unsupported_model: '이 모델에는 SD 슬롯이 있지만 옵션을 켤 방법이 없습니다 — 현재 P1 시리즈 펌웨어는 Bambu Studio에 토글을 표시하지 않으며 프린터에 화면도 없습니다. 여기서 고칠 것은 없습니다. Bambu Lab이 펌웨어로 지원할 때까지 보관된 출력물에는 썸네일과 슬라이서 메타데이터가 없을 수 있습니다.',
fail_no_media: '옵션은 켜져 있지만 프린터 슬롯에 카드도 USB도 감지되지 않아 보낸 파일이 갈 곳이 없습니다. 하나 넣고 다시 출력하세요. 그전까지는 보관되는 모든 출력물에 썸네일과 슬라이서 메타데이터가 없습니다.',
- warn_internal_storage: '옵션이 켜져 있고 저장소도 있지만 마지막 출력물은 Bambuddy가 읽을 수 없는 프린터 내부 저장소에 저장되었습니다. H2 시리즈와 P2S에서는 이 설정과 관계없이 Bambu Studio의 «인쇄» 버튼이 항상 그쪽으로 보냅니다. 출력물은 이름과 시간과 함께 보관되지만 썸네일과 슬라이서 메타데이터는 없습니다. 완전한 기록을 남기려면 Bambuddy에서 출력을 시작하거나, OrcaSlicer로 슬라이싱하거나, Bambu Studio에서 «보내기»로 «외부 저장소»를 고른 뒤 출력을 시작하세요.',
+ warn_internal_storage: '옵션이 켜져 있고 저장소도 있지만 마지막 출력물은 Bambuddy가 읽을 수 없는 프린터 내부 저장소에 저장되었습니다. H2 시리즈, P2S, X2D에서는 이 설정과 관계없이 Bambu Studio의 «인쇄» 버튼이 항상 그쪽으로 보냅니다. 출력물은 이름과 시간과 함께 보관되지만 썸네일과 슬라이서 메타데이터는 없습니다. 완전한 기록을 남기려면 Bambuddy에서 출력을 시작하거나, OrcaSlicer로 슬라이싱하거나, Bambu Studio에서 «보내기»로 «외부 저장소»를 고른 뒤 출력을 시작하세요.',
warn_internal_history: '옵션은 켜져 있고 저장소도 있지만, 마지막 출력은 이미 프린터에 있던 파일에서 실행되었습니다 — 화면에서의 재출력, Handy에서의 시작, 또는 이전에 보낸 파일입니다. 그 라이브러리는 Bambuddy가 읽을 수 없는 내부 저장소에 있으며, 그 출력에서는 아무것도 전송되지 않았으므로 어떤 설정으로도 바뀌지 않습니다. 이름과 시간과 함께 보관되기는 하지만 썸네일과 슬라이서 메타데이터는 없습니다. 온전한 보관을 원하면 Bambuddy에서 출력을 시작하거나 슬라이서에서 전송하세요.',
},
port_rtsps: {
@@ -7080,11 +7089,20 @@ export default {
pass: '연결 가능 — 카메라 스트림이 작동합니다.',
warn: '포트 {{port}}에 연결할 수 없습니다. 라이브 카메라 보기가 작동하지 않습니다. 인쇄에는 영향을 주지 않습니다.'
},
+ macos_local_network: {
+ title: 'macOS 로컬 네트워크 권한',
+ pass: 'macOS가 Bambuddy의 로컬 네트워크 접근을 허용하고 있습니다.',
+ warn_unsigned: 'Bambuddy를 실행하는 Python에 코드 서명이 없어 macOS가 로컬 네트워크 권한을 연결할 대상을 찾지 못하고, 프린터로 향하는 모든 연결을 오류도 확인 창도 없이 조용히 차단합니다. 서명을 수행하는 Bambuddy 업데이트 스크립트(install/update_macos.sh)를 실행한 다음 Bambuddy를 재시작하십시오. 인터프리터: {{executable}}',
+ warn_permission: '프린터가 켜져 있고 이 주소로 연결할 수 있다면 시스템 설정 > 개인 정보 보호 및 보안 > 로컬 네트워크를 열어 Bambuddy의 Python이 활성화되어 있는지 확인하십시오. 활성화되어 있지 않으면 macOS는 로컬 연결을 조용히 차단하며, Python을 업데이트하면 이전 권한이 남지 않을 수 있습니다.',
+ },
network_mode: {
- title: 'Docker 네트워크 모드',
- pass: '호스트 네트워크 모드로 실행 중입니다.',
- warn: 'Bambuddy가 Docker 브리지 네트워킹으로 실행 중입니다. 프린터 검색과 가상 프린터에는 호스트 네트워크 모드가 필요합니다 — "network_mode: host"로 컨테이너를 재생성하세요.',
- skip: 'Docker에서 실행 중이 아닙니다 — 해당 없음.'
+ title: '컨테이너 네트워크 모드',
+ genericRuntime: '컨테이너',
+ pass: '{{runtime}}에서 호스트 네트워크로 실행 중입니다.',
+ warn: 'Bambuddy가 {{runtime}}에서 브리지 네트워크로 실행 중입니다. 프린터 검색과 가상 프린터에는 호스트 네트워크가 필요합니다 — 호스트 네트워크로 컨테이너를 재생성하세요(docker-compose에서는 "network_mode: host", Podman에서는 "--network=host").',
+ skip: '컨테이너에서 실행 중이 아닙니다 — 해당 없음.',
+ skip_unknown: 'Bambuddy가 {{runtime}}에서 실행 중이지만 네트워크 모드를 확인할 수 없습니다. 프린터 검색이나 가상 프린터가 작동하지 않으면 호스트 네트워크로 컨테이너를 재생성하세요.',
+ skip_system_container: 'Bambuddy가 {{runtime}} 시스템 컨테이너에서 실행 중이며, 가상 머신처럼 LAN에 연결되어 있습니다 — 해당 없음.'
},
subnet: {
title: '네트워크 서브넷',
diff --git a/frontend/src/i18n/locales/nl.ts b/frontend/src/i18n/locales/nl.ts
index f5cfd5c62..51e12e00d 100644
--- a/frontend/src/i18n/locales/nl.ts
+++ b/frontend/src/i18n/locales/nl.ts
@@ -537,6 +537,8 @@ export default {
step: 'Stap (mm)',
up: 'Plaat omhoog bewegen',
down: 'Plaat omlaag bewegen',
+ toolheadUp: 'Printkop omhoog bewegen',
+ toolheadDown: 'Printkop omlaag bewegen',
disabledWhilePrinting: 'Uitgeschakeld tijdens afdrukken',
notHomedTitle: 'Printer is niet gehomed',
notHomedMessage: 'De printer is sinds de laatste afdruk niet gehomed. Voer eerst auto-home uit voor een veilige positionering (parkeert de toolhead en homet daarna X, Y en Z), of verplaats toch — soft endstops worden omzeild.',
@@ -907,11 +909,15 @@ export default {
dismissLabel: 'Deze melding sluiten',
docsLinkInternalStorage: 'Waarom dit gebeurt',
titleInternalStorage: 'Sommige recente afdrukken bleven op de interne opslag van de printer staan',
- bodyInternalStorage: 'Bambu Studio plaatste het geslicede bestand op de interne opslag van de printer in plaats van op de kaart, waardoor Bambuddy via FTP niets kon uitlezen. Op H2-serie en P2S doet de knop Afdrukken dit altijd — alleen Verzenden biedt een keuze, en ook daar is Cache standaard. Die afdrukken worden nog steeds gearchiveerd met naam en timing, maar zonder miniatuur of slicermetadata. Voor volledige archieven start je de afdruk vanuit Bambuddy, slice je in OrcaSlicer, of gebruik je in Bambu Studio Verzenden met Extern geselecteerd en start je daarna de afdruk. Voor al deze opties is een kaart of USB-stick in de printer nodig.',
+ bodyInternalStorage: 'Bambu Studio plaatste het geslicede bestand op de interne opslag van de printer in plaats van op de kaart, waardoor Bambuddy via FTP niets kon uitlezen. Op H2-serie, P2S en X2D doet de knop Afdrukken dit altijd — alleen Verzenden biedt een keuze, en ook daar is Cache standaard. Die afdrukken worden nog steeds gearchiveerd met naam en timing, maar zonder miniatuur of slicermetadata. Voor volledige archieven start je de afdruk vanuit Bambuddy, slice je in OrcaSlicer, of gebruik je in Bambu Studio Verzenden met Extern geselecteerd en start je daarna de afdruk. Voor al deze opties is een kaart of USB-stick in de printer nodig.',
titleNoExternalStorage: 'Sommige recente afdrukken konden niet worden gearchiveerd — geen opslag in de printer',
bodyNoExternalStorage: 'De printer meldt dat er geen kaart of USB-stick in de sleuf zit, waardoor het geslicede bestand nergens kon worden opgeslagen en Bambuddy niets kon uitlezen. Plaats er een en de volgende afdruk wordt volledig gearchiveerd.',
titleInternalHistory: 'Sommige recente prints zijn gestart vanaf een bestand dat al op de printer stond',
bodyInternalHistory: 'Die prints liepen vanuit de eigen bibliotheek van de printer — opnieuw afdrukken via het scherm, starten vanuit Handy, of een bestand dat eerder is verstuurd en later is afgedrukt. Bambuddy leest printbestanden via FTP, dat alleen de kaart of stick aanbiedt, terwijl de printer die bibliotheek bewaart op een plek die FTP niet kan bereiken. Er was dus geen 3MF om te lezen. Geen enkele slicer-instelling verandert dit, omdat er voor deze prints niets is verstuurd. Ze worden nog steeds gearchiveerd met naam en tijden, en via Archief bewerken kun je het gebruikte filament handmatig invullen. Start de print vanuit Bambuddy of vanuit je slicer voor een volledig archief.',
+ titleFtpsCooloff: 'Sommige recente prints konden niet worden gearchiveerd — de printer weigerde de bestandsverbinding',
+ bodyFtpsCooloff: 'Bambuddy opende de bestandsoverdrachtspoort van de printer (FTPS 990) en de printer antwoordde met iets dat geen TLS is, waardoor er niets van te lezen viel. Die prints staan nog wel in het archief met hun naam en tijden, alleen zonder miniatuur of slicer-metadata. Dit is geen slicer-instelling en niets wat u hebt gewijzigd — dezelfde modellen en firmware draaien normaal op andere installaties, en een getroffen printer werkt later meestal vanzelf weer. Na zo\'n fout pauzeert Bambuddy de overdrachten naar die printer vijf minuten en haalt het bestand daarna alsnog op, dus een korte episode herstelt zichzelf; een kaart die leeg blijft betekent dat de weigering langer duurde dan de herhaalpoging. Wat het veroorzaakt is nog niet bekend. Blijft het gebeuren, zet dan debug-logging aan op de pagina Systeem en voeg een supportpakket bij uw melding.',
+ titleFtpTransferFailed: 'Sommige recente prints konden niet worden gearchiveerd — de bestandsoverdracht duurde te lang',
+ bodyFtpTransferFailed: 'Het gesliced bestand stond op de kaart van de printer en de printer leverde het ook aan, maar de overdracht was niet binnen de toegestane tijd klaar. Bij de start van een print bedient de printer ook zijn camera, zijn statusberichten en de upload van de opdracht, en een grote 3MF komt daar niet altijd doorheen. Die prints zijn nog steeds gearchiveerd met naam en tijden, alleen zonder miniatuur of slicergegevens. Dit is geen slicerinstelling en niets dat u hebt gewijzigd. Bambuddy haalt het bestand in de tien minuten daarna nog drie keer op, dus de meeste gevallen lossen zichzelf op; een kaart die leeg blijft betekent dat ook al die pogingen te lang duurden. Als het vaker gebeurt, verhoog dan Verbindingstime-out onder Instellingen > Netwerk > FTP opnieuw proberen.',
},
searchPlaceholder: 'Archieven zoeken...',
filterByPrinter: 'Filteren op printer',
@@ -1503,7 +1509,7 @@ export default {
// Time
time: {
etaIfStartedNow: 'Voltooiingstijd als deze taak nu zou starten',
- asap: 'Zo snel mogelijk',
+ whenFree: 'Zodra een printer vrij is',
overdue: 'Te laat',
now: 'Nu',
lessThanMinute: 'Binnen een minuut',
@@ -1527,6 +1533,7 @@ export default {
staged: 'Gereed voor handmatige start',
autoPowerOff: 'Automatisch uitschakelen na afdruk',
requirePrevious: 'Vereis dat vorige taak slaagt',
+ gcodeInjection: 'G-code injecteren',
printOptions: 'Afdrukopties',
bedLevelling: 'Bednivellering',
flowCalibration: 'Flowkalibratie',
@@ -2350,7 +2357,7 @@ export default {
preheatEnabled: 'Voorverwarmen en stabiliseren inschakelen',
preheatEnabledDesc: 'Wanneer uitgeschakeld worden afdrukken uit de wachtrij direct verzonden. Elk wachtrij-item kan dit per afdruk overschrijven.',
preheatFilamentTargetsLabel: 'Kamerdoel per filament (°C)',
- preheatFilamentTargetsHint: 'Bambuddy kiest het hoogste doel van de geladen AMS-sleuven; afdrukken met alleen PLA krijgen 0 en slaan de kamerfase automatisch over.',
+ preheatFilamentTargetsHint: 'Bambuddy kiest het hoogste doel van de geladen AMS-sleuven. Een afdruk waarvan alle sleuven 0 opleveren — PLA, PETG, TPU, PVA — slaat het voorverwarmen volledig over en start meteen.',
preheatFilamentTargetsReset: 'Standaardwaarden herstellen',
preheatFilamentTargetsDefaultRow: 'Overig / niet gekoppeld',
preheatMaxWait: 'Max. wachttijd (seconden)',
@@ -2367,6 +2374,7 @@ export default {
calibrationMode_on: 'Aan',
calibrationMode_auto: 'Auto',
preheatTargetOverride: 'Kamerdoel overschrijven (°C, leeg = filamentstandaard)',
+ preheatTargetOverrideHelp: '0 verwarmt het bed en voert het doorwarmen uit zonder kamer. Laat het leeg en een afdruk zonder kamerbehoefte slaat het voorverwarmen volledig over.',
plateClear: 'Bevestiging plaat vrij',
requirePlateClear: 'Bevestiging vereisen dat plaat vrij is',
requirePlateClearDescription: 'Wanneer ingeschakeld wacht de scheduler op een bevestiging per printer dat de plaat vrij is voordat afdrukken uit de wachtrij starten op printers met voltooide taken. Als je dit uitschakelt, worden ook de plaatstatusbadge en de knop "Plaat als leeg markeren" op printerkaarten verborgen.',
@@ -4960,7 +4968,7 @@ export default {
linkToSpool: 'Koppelen aan spoel',
tagLinked: 'Tag aan spoel gekoppeld',
tagLinkFailed: 'Tag koppelen mislukt',
- tagAlreadyLinked: 'Tag is al aan een andere spoel gekoppeld',
+ tagAlreadyLinked: 'Tag is al aan spoel #{{id}} gekoppeld',
unknownTag: 'Onbekende RFID-tag gedetecteerd',
// Usage history
usageHistory: 'Verbruiksgeschiedenis',
@@ -5452,7 +5460,7 @@ export default {
project: 'Project',
noProject: 'Geen project',
itemsPrinted: 'Afgedrukte items',
- itemsPrintedHelp: 'Aantal items dat met deze afdruktaak is geproduceerd',
+ itemsPrintedHelp: 'Aantal items dat met deze afdruktaak is geproduceerd. Gebruik 0 als de hele plaat mislukt is.',
filamentUsed: 'Gebruikt filament (g)',
filamentUsedPlaceholder: 'bijv. 46.16',
filamentUsedHelp: 'Stel dit handmatig in wanneer een afdruk zonder 3MF is gearchiveerd, zodat deze toch meetelt in de filamenttotalen. Bij het opnieuw scannen van een archief met een 3MF wordt de waarde weer uit het bestand gelezen.',
@@ -6845,6 +6853,7 @@ export default {
scaleWeight: 'Weegschaal',
netWeight: 'Netto',
lastUsed: 'Laatst gebruikt',
+ unknownColor: 'Onbekende kleur',
},
ams: {
noData: 'Geen AMS gedetecteerd',
@@ -7022,7 +7031,7 @@ export default {
skip: 'Niet gecontroleerd — hiervoor is een actieve MQTT-verbinding nodig. Bij oudere slicers, waar deze instelling alleen in de slicer staat, rapporteert de printer hem nooit. Daardoor kan deze controle slagen terwijl de optie uitstaat — controleer installatiestap 4 handmatig.',
skip_unsupported_model: 'Dit model heeft een SD-slot maar geen manier om de optie in te schakelen — de huidige P1-seriefirmware toont de schakelaar niet in Bambu Studio en de printer heeft geen scherm. Hier is niets te herstellen; gearchiveerde afdrukken kunnen miniaturen en slicermetadata missen totdat Bambu Lab firmwareondersteuning toevoegt.',
fail_no_media: 'De optie staat aan, maar de printer meldt dat er geen kaart of stick in het slot zit, waardoor verzonden bestanden nergens kunnen worden opgeslagen. Plaats er een en druk opnieuw af — tot die tijd mist elke gearchiveerde afdruk zijn miniatuur en slicermetadata.',
- warn_internal_storage: 'De optie staat aan en opslag is aanwezig, maar de laatste afdruk ging toch naar de interne opslag van de printer, die Bambuddy niet kan lezen. Bij H2-serie en P2S stuurt de knop Afdrukken van Bambu Studio altijd daarheen, ongeacht deze instelling. Afdrukken worden gearchiveerd met naam en timing, maar zonder miniatuur of slicermetadata. Voor volledige archieven start je afdrukken vanuit Bambuddy of slice je in OrcaSlicer — of gebruik in Bambu Studio Verzenden met Extern geselecteerd en start daarna de afdruk.',
+ warn_internal_storage: 'De optie staat aan en opslag is aanwezig, maar de laatste afdruk ging toch naar de interne opslag van de printer, die Bambuddy niet kan lezen. Bij H2-serie, P2S en X2D stuurt de knop Afdrukken van Bambu Studio altijd daarheen, ongeacht deze instelling. Afdrukken worden gearchiveerd met naam en timing, maar zonder miniatuur of slicermetadata. Voor volledige archieven start je afdrukken vanuit Bambuddy of slice je in OrcaSlicer — of gebruik in Bambu Studio Verzenden met Extern geselecteerd en start daarna de afdruk.',
warn_internal_history: 'De optie staat aan en er is opslag aanwezig, maar de laatste print liep vanaf een bestand dat al op de printer stond — opnieuw afdrukken via het scherm, starten vanuit Handy, of een eerder verstuurd bestand. Die bibliotheek staat op interne opslag die Bambuddy niet kan lezen, en geen enkele instelling verandert dat, omdat er voor die print niets is verstuurd. Hij wordt nog steeds gearchiveerd met naam en tijden, maar zonder thumbnail of slicer-metadata. Start prints vanuit Bambuddy of verstuur ze vanuit de slicer voor volledige archieven.',
},
port_rtsps: {
@@ -7030,11 +7039,20 @@ export default {
pass: 'Bereikbaar — de camerastream werkt.',
warn: 'Poort {{port}} is niet bereikbaar. De livecamera werkt niet. Dit heeft geen invloed op afdrukken.',
},
+ macos_local_network: {
+ title: 'macOS-toegang tot lokaal netwerk',
+ pass: 'macOS staat Bambuddy toe het lokale netwerk te bereiken.',
+ warn_unsigned: 'De Python waarmee Bambuddy draait heeft geen codehandtekening, dus macOS heeft niets om de toegang tot het lokale netwerk aan te koppelen en laat elke verbinding met de printer stilletjes vallen — zonder fout en zonder vraag. Voer het updatescript van Bambuddy (install/update_macos.sh) uit, dat de handtekening plaatst, en start Bambuddy daarna opnieuw. Interpreter: {{executable}}',
+ warn_permission: 'Staat de printer aan en is hij op dit adres bereikbaar, open dan Systeeminstellingen > Privacy en beveiliging > Lokaal netwerk en controleer of de Python van Bambuddy is ingeschakeld. Zo niet, dan laat macOS lokale verbindingen stilletjes vallen, en een Python-update kan de oude toestemming achterlaten.',
+ },
network_mode: {
- title: 'Docker-netwerkmodus',
- pass: 'Draait in host-netwerkmodus.',
- warn: 'Bambuddy draait met Docker bridge-netwerken. Printerdetectie en de Virtuele printer vereisen host-netwerkmodus — maak de container opnieuw aan met "network_mode: host".',
- skip: 'Niet actief in Docker — niet van toepassing.',
+ title: 'Netwerkmodus van de container',
+ genericRuntime: 'een container',
+ pass: 'Draait in {{runtime}} met host-netwerk.',
+ warn: 'Bambuddy draait in {{runtime}} met bridge-netwerken. Printerdetectie en de Virtuele printer vereisen host-netwerk — maak de container opnieuw aan met host-netwerk ("network_mode: host" in docker-compose, "--network=host" bij Podman).',
+ skip: 'Draait niet in een container — niet van toepassing.',
+ skip_unknown: 'Bambuddy draait in {{runtime}}, maar de netwerkmodus kon niet worden bepaald. Als printerdetectie of de Virtuele printer niet werken, maak de container dan opnieuw aan met host-netwerk.',
+ skip_system_container: 'Bambuddy draait in een {{runtime}}-systeemcontainer, die net als een virtuele machine op het LAN zit — niet van toepassing.',
},
subnet: {
title: 'Netwerksubnet',
diff --git a/frontend/src/i18n/locales/pt-BR.ts b/frontend/src/i18n/locales/pt-BR.ts
index 323b711a1..829f354f7 100644
--- a/frontend/src/i18n/locales/pt-BR.ts
+++ b/frontend/src/i18n/locales/pt-BR.ts
@@ -534,6 +534,8 @@ export default {
step: 'Passo (mm)',
up: 'Mover mesa para cima',
down: 'Mover mesa para baixo',
+ toolheadUp: 'Mover cabeçote para cima',
+ toolheadDown: 'Mover cabeçote para baixo',
disabledWhilePrinting: 'Desativado durante a impressão',
notHomedTitle: 'Impressora não referenciada',
notHomedMessage: 'A impressora não foi referenciada desde a última impressão. Execute a referência automática primeiro para um posicionamento seguro (estaciona o cabeçote, depois referencia X, Y e Z), ou mova assim mesmo — os fins de curso de software serão ignorados.',
@@ -901,11 +903,15 @@ export default {
docsLink: 'Ver passo 4 da instalação',
docsLinkInternalStorage: 'Por que isso acontece',
titleInternalStorage: 'Algumas impressões recentes ficaram no armazenamento interno da impressora',
- bodyInternalStorage: 'O Bambu Studio colocou o arquivo fatiado no armazenamento interno da impressora em vez do cartão, então o Bambuddy não tinha nada para ler via FTP. Na série H2 e na P2S o botão «Imprimir» sempre faz isso: só «Enviar» oferece escolha, e ela também vem com «Cache» por padrão. Essas impressões continuam arquivadas com nome e tempos, apenas sem miniatura nem metadados do fatiador. Para arquivos completos, inicie a impressão pelo Bambuddy ou fatie no OrcaSlicer, ou então no Bambu Studio use «Enviar» com «Externo» e inicie a impressão depois. Todos precisam de um cartão ou pendrive na impressora.',
+ bodyInternalStorage: 'O Bambu Studio colocou o arquivo fatiado no armazenamento interno da impressora em vez do cartão, então o Bambuddy não tinha nada para ler via FTP. Na série H2, na P2S e na X2D o botão «Imprimir» sempre faz isso: só «Enviar» oferece escolha, e ela também vem com «Cache» por padrão. Essas impressões continuam arquivadas com nome e tempos, apenas sem miniatura nem metadados do fatiador. Para arquivos completos, inicie a impressão pelo Bambuddy ou fatie no OrcaSlicer, ou então no Bambu Studio use «Enviar» com «Externo» e inicie a impressão depois. Todos precisam de um cartão ou pendrive na impressora.',
titleNoExternalStorage: 'Algumas impressões recentes não puderam ser arquivadas — sem armazenamento na impressora',
bodyNoExternalStorage: 'A impressora não detecta cartão nem pendrive no slot, então o arquivo fatiado não tinha onde ficar e o Bambuddy nada para ler. Insira um e a próxima impressão será arquivada por completo.',
titleInternalHistory: 'Algumas impressões recentes começaram a partir de um arquivo que já estava na impressora',
bodyInternalHistory: 'Essas impressões saíram da própria biblioteca da impressora: uma reimpressão pela tela dela, um início pelo Handy ou um arquivo enviado antes e impresso depois. O Bambuddy lê os arquivos de impressão por FTP, que só serve o cartão ou o pendrive, enquanto a impressora guarda essa biblioteca em uma área que o FTP não alcança, então não havia nenhum 3MF para ler. Nenhuma opção do fatiador muda isso, porque nada foi enviado para essas impressões. Elas continuam arquivadas com nome e tempos, e «Editar Arquivo» permite preencher à mão o filamento usado. Para um arquivo completo, inicie a impressão pelo Bambuddy ou pelo seu fatiador.',
+ titleFtpsCooloff: 'Algumas impressões recentes não puderam ser arquivadas — a impressora recusou a conexão de arquivos',
+ bodyFtpsCooloff: 'O Bambuddy abriu a porta de transferência de arquivos da impressora (FTPS 990) e a impressora respondeu com algo que não é TLS, então nada pôde ser lido dela. Essas impressões continuam arquivadas com nome e tempos, apenas sem miniatura nem metadados do fatiador. Não é uma configuração do fatiador nem algo que você mudou — os mesmos modelos e firmwares funcionam normalmente em outras instalações, e uma impressora afetada costuma voltar a funcionar sozinha depois. Após uma falha dessas, o Bambuddy pausa as transferências para essa impressora por cinco minutos e volta a buscar o arquivo quando a pausa termina, de modo que um episódio curto se resolve sozinho; um cartão ainda vazio significa que a recusa durou mais que a nova tentativa. Ainda não se sabe o que provoca isso. Se continuar acontecendo, ative o registro de depuração na página Sistema e anexe um pacote de suporte ao seu relato.',
+ titleFtpTransferFailed: 'Algumas impressões recentes não puderam ser arquivadas — a transferência do arquivo esgotou o tempo',
+ bodyFtpTransferFailed: 'O arquivo fatiado estava no cartão da impressora e a impressora chegou a servi-lo, mas a transferência não terminou no tempo permitido. No início da impressão a impressora também cuida da câmera, das mensagens de status e do envio do trabalho, e um 3MF grande nem sempre consegue passar no meio disso. Essas impressões continuam arquivadas com nome e tempos, apenas sem miniatura nem metadados do fatiador. Não é uma configuração do fatiador nem algo que você mudou. O Bambuddy volta a buscar o arquivo três vezes ao longo dos dez minutos seguintes, então a maioria se resolve sozinha; um cartão ainda vazio significa que todas essas tentativas também esgotaram o tempo. Se continuar acontecendo, aumente Tempo limite de conexão em Configurações > Rede > Nova tentativa de FTP.',
dismissLabel: 'Dispensar este aviso',
},
searchPlaceholder: 'Pesquisar arquivos...',
@@ -1487,7 +1493,7 @@ export default {
// Time
time: {
etaIfStartedNow: 'Horário de conclusão se este trabalho começasse agora',
- asap: 'ASAP',
+ whenFree: 'Quando uma impressora estiver livre',
overdue: 'Atrasado',
now: 'Agora',
lessThanMinute: 'Em menos de um minuto',
@@ -1511,6 +1517,7 @@ export default {
staged: 'Preparado (início manual)',
autoPowerOff: 'Desligamento automático após impressão',
requirePrevious: 'Requer sucesso anterior',
+ gcodeInjection: 'Injetar G-code',
printOptions: 'Opções de Impressão',
bedLevelling: 'Nivelamento da Mesa',
flowCalibration: 'Calibração de Fluxo',
@@ -2286,7 +2293,7 @@ export default {
preheatEnabled: 'Ativar pré-aquecimento & soak',
preheatEnabledDesc: 'Quando desligado, as impressões na fila iniciam imediatamente. Cada item pode sobrescrever por impressão.',
preheatFilamentTargetsLabel: 'Alvo da câmara por filamento (°C)',
- preheatFilamentTargetsHint: 'Bambuddy escolhe o alvo mais alto entre os slots AMS carregados; impressões apenas com PLA derivam 0 e pulam a fase da câmara automaticamente.',
+ preheatFilamentTargetsHint: 'Bambuddy escolhe o alvo mais alto entre os slots AMS carregados. Uma impressão cujos slots derivam todos 0 — PLA, PETG, TPU, PVA — pula o preaquecimento por completo e começa imediatamente.',
preheatFilamentTargetsReset: 'Redefinir para padrões',
preheatFilamentTargetsDefaultRow: 'Outro / sem mapeamento',
preheatMaxWait: 'Espera máx. (segundos)',
@@ -2303,6 +2310,7 @@ export default {
calibrationMode_on: 'Ligado',
calibrationMode_auto: 'Auto',
preheatTargetOverride: 'Sobrescrever alvo da câmara (°C, vazio = por filamento)',
+ preheatTargetOverrideHelp: '0 aquece a mesa e executa a estabilização sem a câmara. Em branco, uma impressão que não precisa de câmara pula o preaquecimento por completo.',
plateClear: 'Confirmação de placa livre',
requirePlateClear: 'Exigir confirmação de placa livre',
requirePlateClearDescription: 'Quando ativado, o agendador aguarda uma confirmação de placa livre por impressora antes de iniciar impressões na fila em impressoras com trabalhos concluídos. Desativar isso também oculta o indicador de status da placa e o botão "Marcar placa como liberada" nos cartões das impressoras.',
@@ -4903,7 +4911,7 @@ export default {
linkToSpool: 'Vincular ao Carretel',
tagLinked: 'Tag vinculada ao carretel',
tagLinkFailed: 'Falha ao vincular tag',
- tagAlreadyLinked: 'Tag já vinculada a outro carretel',
+ tagAlreadyLinked: 'Tag já vinculada ao carretel #{{id}}',
unknownTag: 'Tag RFID desconhecida detectada',
// Usage history
usageHistory: 'Histórico de Uso',
@@ -5390,7 +5398,7 @@ export default {
project: 'Projeto',
noProject: 'Nenhum projeto',
itemsPrinted: 'Itens Impressos',
- itemsPrintedHelp: 'Número de itens produzidos neste trabalho de impressão',
+ itemsPrintedHelp: 'Número de itens produzidos neste trabalho de impressão. Use 0 se a placa inteira foi perdida.',
filamentUsed: 'Filamento usado (g)',
filamentUsedPlaceholder: 'ex.: 46.16',
filamentUsedHelp: 'Informe manualmente quando uma impressão for arquivada sem o 3MF, para que ela ainda conte nos totais de filamento. Reescanear um arquivo que tem o 3MF lê o valor novamente do arquivo.',
@@ -6783,6 +6791,7 @@ export default {
scaleWeight: 'Balança',
netWeight: 'Líquido',
lastUsed: 'Último uso',
+ unknownColor: 'Cor desconhecida',
},
ams: {
noData: 'Nenhum AMS detectado',
@@ -6960,7 +6969,7 @@ export default {
skip: 'Não verificado — é necessária uma conexão MQTT ativa. Em fatiadores mais antigos onde essa configuração existe apenas no fatiador, a impressora não a reporta, então esta verificação passa mesmo com a opção desligada — verifique o passo 4 da instalação manualmente.',
skip_unsupported_model: 'Este modelo tem slot SD mas nenhuma forma de ativar a opção — o firmware atual da série P1 não mostra o botão no Bambu Studio e a impressora não tem tela. Não há nada a corrigir aqui; as impressões arquivadas podem ficar sem miniaturas e metadados do fatiador até que a Bambu Lab adicione suporte por firmware.',
fail_no_media: 'A opção está ligada, mas a impressora não detecta cartão nem pendrive no slot, então os arquivos enviados não têm para onde ir. Insira um e imprima novamente — até lá, toda impressão arquivada ficará sem miniatura e sem metadados do fatiador.',
- warn_internal_storage: 'A opção está ligada e há armazenamento presente, mas a última impressão ainda assim foi para o armazenamento interno da impressora, que o Bambuddy não consegue ler. Na série H2 e na P2S o botão «Imprimir» do Bambu Studio sempre envia para lá, independentemente desta opção. As impressões são arquivadas com nome e tempos, mas sem miniatura nem metadados do fatiador. Para arquivos completos, inicie as impressões pelo Bambuddy ou fatie no OrcaSlicer, ou então no Bambu Studio use «Enviar» com «Externo» e inicie a impressão depois.',
+ warn_internal_storage: 'A opção está ligada e há armazenamento presente, mas a última impressão ainda assim foi para o armazenamento interno da impressora, que o Bambuddy não consegue ler. Na série H2, na P2S e na X2D o botão «Imprimir» do Bambu Studio sempre envia para lá, independentemente desta opção. As impressões são arquivadas com nome e tempos, mas sem miniatura nem metadados do fatiador. Para arquivos completos, inicie as impressões pelo Bambuddy ou fatie no OrcaSlicer, ou então no Bambu Studio use «Enviar» com «Externo» e inicie a impressão depois.',
warn_internal_history: 'A opção está ligada e há armazenamento, mas a última impressão saiu de um arquivo que já estava na impressora: uma reimpressão pela tela dela, um início pelo Handy ou um arquivo enviado antes. Essa biblioteca fica no armazenamento interno, que o Bambuddy não consegue ler, e nenhuma opção muda isso, porque nada foi enviado para essa impressão. Ela continua arquivada com nome e tempos, mas sem miniatura nem metadados do fatiador. Para arquivos completos, inicie as impressões pelo Bambuddy ou envie-as pelo fatiador.',
},
port_rtsps: {
@@ -6968,11 +6977,20 @@ export default {
pass: 'Acessível — o streaming da câmera funcionará.',
warn: 'A porta {{port}} está inacessível. A visualização ao vivo da câmera não funcionará. Isso não afeta a impressão.',
},
+ macos_local_network: {
+ title: 'Permissão de rede local do macOS',
+ pass: 'O macOS está permitindo que o Bambuddy alcance a rede local.',
+ warn_unsigned: 'O Python que executa o Bambuddy não tem assinatura de código, então o macOS não tem a que vincular a permissão de rede local e descarta silenciosamente todas as conexões com a impressora — sem erro e sem solicitação. Execute o atualizador do Bambuddy (install/update_macos.sh), que faz a assinatura, e reinicie o Bambuddy. Interpretador: {{executable}}',
+ warn_permission: 'Se a impressora estiver ligada e acessível neste endereço, abra Ajustes do Sistema > Privacidade e Segurança > Rede Local e verifique se o Python do Bambuddy está ativado. Caso contrário, o macOS descarta conexões locais em silêncio, e atualizar o Python pode deixar a permissão antiga para trás.',
+ },
network_mode: {
- title: 'Modo de rede Docker',
- pass: 'Executando no modo de rede host.',
- warn: 'O Bambuddy está sendo executado em rede Docker bridge. A descoberta de impressoras e a impressora virtual precisam do modo de rede host — recrie o contêiner com "network_mode: host".',
- skip: 'Não está sendo executado no Docker — não aplicável.',
+ title: 'Modo de rede do contêiner',
+ genericRuntime: 'um contêiner',
+ pass: 'Executando em {{runtime}} com rede host.',
+ warn: 'O Bambuddy está sendo executado em {{runtime}} com rede bridge. A descoberta de impressoras e a impressora virtual precisam da rede host — recrie o contêiner com rede host ("network_mode: host" no docker-compose, "--network=host" no Podman).',
+ skip: 'Não está sendo executado em um contêiner — não aplicável.',
+ skip_unknown: 'O Bambuddy está sendo executado em {{runtime}}, mas não foi possível determinar o modo de rede. Se a descoberta de impressoras ou a impressora virtual não funcionarem, recrie o contêiner com rede host.',
+ skip_system_container: 'O Bambuddy está sendo executado em um contêiner de sistema {{runtime}}, que fica na LAN como uma máquina virtual — não aplicável.',
},
subnet: {
title: 'Sub-rede',
diff --git a/frontend/src/i18n/locales/ru.ts b/frontend/src/i18n/locales/ru.ts
index e6d610de3..e6fe22b62 100644
--- a/frontend/src/i18n/locales/ru.ts
+++ b/frontend/src/i18n/locales/ru.ts
@@ -508,6 +508,8 @@ export default {
step: "Шаг (мм)",
up: "Поднять стол",
down: "Опустить стол",
+ toolheadUp: "Поднять печатающую голову",
+ toolheadDown: "Опустить печатающую голову",
disabledWhilePrinting: "Недоступно во время печати",
notHomedTitle: "Принтер не выполнил парковку по осям",
notHomedMessage: "После последней печати принтер не выполнял Home. Сначала запустите автоматическую парковку для безопасного позиционирования (парковка печатающей головы и поиск нуля по X, Y и Z) либо продолжите перемещение — программные концевики будут отключены.",
@@ -856,11 +858,15 @@ export default {
docsLink: "См. шаг 4 установки",
docsLinkInternalStorage: 'Почему так происходит',
titleInternalStorage: 'Некоторые недавние печати остались во внутренней памяти принтера',
- bodyInternalStorage: 'Bambu Studio сохранил нарезанный файл во внутренней памяти принтера, а не на карте, поэтому Bambuddy было нечего читать по FTP. На серии H2 и P2S кнопка «Печать» всегда делает так: выбор есть только в «Отправить», и там по умолчанию тоже «Кэш». Эти печати по-прежнему архивируются с именем и временем, только без миниатюры и метаданных слайсера. Чтобы архивы были полными, запускайте печать из Bambuddy или нарезайте в OrcaSlicer, либо в Bambu Studio используйте «Отправить» с «Внешним накопителем», а печать запускайте после этого. Во всех случаях в принтере нужна карта или флешка.',
+ bodyInternalStorage: 'Bambu Studio сохранил нарезанный файл во внутренней памяти принтера, а не на карте, поэтому Bambuddy было нечего читать по FTP. На серии H2, P2S и X2D кнопка «Печать» всегда делает так: выбор есть только в «Отправить», и там по умолчанию тоже «Кэш». Эти печати по-прежнему архивируются с именем и временем, только без миниатюры и метаданных слайсера. Чтобы архивы были полными, запускайте печать из Bambuddy или нарезайте в OrcaSlicer, либо в Bambu Studio используйте «Отправить» с «Внешним накопителем», а печать запускайте после этого. Во всех случаях в принтере нужна карта или флешка.',
titleNoExternalStorage: 'Некоторые недавние печати не удалось архивировать — в принтере нет накопителя',
bodyNoExternalStorage: 'Принтер не видит ни карты, ни флешки в слоте, поэтому нарезанному файлу некуда было попасть, а Bambuddy нечего читать. Вставьте накопитель, и следующая печать будет заархивирована полностью.',
titleInternalHistory: 'Некоторые недавние печати запущены из файла, который уже был на принтере',
bodyInternalHistory: 'Эти печати шли из собственной библиотеки принтера — повторная печать с его экрана, запуск из Handy или файл, отправленный раньше и напечатанный позже. Bambuddy читает файлы печати по FTP, а он отдаёт только карту или флешку, тогда как принтер держит эту библиотеку в области, куда FTP не достаёт, — читать 3MF было негде. Настройки слайсера тут ничего не меняют: для этих печатей ничего не отправлялось. Они по-прежнему архивируются с именем и временем, а в «Редактировании архива» израсходованный филамент можно указать вручную. Чтобы архив был полным, запускайте печать из Bambuddy или из своего слайсера.',
+ titleFtpsCooloff: 'Некоторые недавние печати не удалось заархивировать — принтер отклонил файловое соединение',
+ bodyFtpsCooloff: 'Bambuddy открыл порт передачи файлов принтера (FTPS 990), и принтер ответил чем-то, что не является TLS, поэтому прочитать с него ничего не удалось. Эти печати всё равно сохранены в архиве с названием и временем, только без миниатюры и метаданных слайсера. Это не настройка слайсера и не то, что вы меняли: те же модели и прошивки нормально работают на других установках, а затронутый принтер обычно позже начинает работать сам. После такой ошибки Bambuddy приостанавливает передачи к этому принтеру на пять минут и возвращается за файлом, когда пауза заканчивается, поэтому короткий эпизод исправляется сам; если карточка так и осталась пустой, значит отказ продлился дольше повторной попытки. Что именно это вызывает, пока неизвестно. Если повторяется, включите отладочное журналирование на странице Система и приложите пакет поддержки к своему сообщению.',
+ titleFtpTransferFailed: 'Некоторые недавние печати не удалось заархивировать — передача файла не уложилась во время',
+ bodyFtpTransferFailed: 'Нарезанный файл лежал на карте принтера, и принтер его действительно отдавал, но передача не завершилась за отведённое время. В момент старта печати принтер одновременно обслуживает камеру, сообщения о состоянии и загрузку задания, и крупный 3MF не всегда успевает пройти на этом фоне. Эти печати по-прежнему заархивированы с названием и временем, только без миниатюры и данных слайсера. Это не настройка слайсера и не следствие ваших изменений. Bambuddy возвращается за файлом ещё три раза в течение следующих десяти минут, поэтому большинство таких случаев закрываются сами; если карточка так и осталась пустой, значит и все эти попытки не уложились во время. Если это повторяется, увеличьте Таймаут подключения в разделе Настройки > Сеть > Повтор FTP.',
dismissLabel: "Закрыть это уведомление",
},
searchPlaceholder: "Поиск в архиве...",
@@ -1426,7 +1432,7 @@ export default {
},
time: {
etaIfStartedNow: "Время завершения, если запустить это задание сейчас",
- asap: "Как можно скорее",
+ whenFree: "Когда принтер освободится",
overdue: "Просрочено",
now: "Сейчас",
lessThanMinute: "Меньше чем через минуту",
@@ -1448,6 +1454,7 @@ export default {
staged: "Подготовлено (ручной запуск)",
autoPowerOff: "Отключить питание после печати",
requirePrevious: "Требовать успешного завершения предыдущего задания",
+ gcodeInjection: "Вставлять G-код",
printOptions: "Параметры печати",
bedLevelling: "Калибровка стола",
flowCalibration: "Калибровка потока",
@@ -2210,7 +2217,7 @@ export default {
preheatEnabled: "Включить преднагрев и выдержку",
preheatEnabledDesc: "Если выключено, задания очереди отправляются сразу. Для каждого задания можно задать отдельное значение.",
preheatFilamentTargetsLabel: "Целевая температура камеры по типу филамента (°C)",
- preheatFilamentTargetsHint: "Bambuddy выбирает наибольшее значение среди задействованных слотов AMS; для печати только PLA автоматически используется 0 и этап прогрева камеры пропускается.",
+ preheatFilamentTargetsHint: "Bambuddy выбирает наибольшее значение среди задействованных слотов AMS. Печать, для всех слотов которой получается 0 — PLA, PETG, TPU, PVA, — полностью пропускает прогрев и начинается сразу.",
preheatFilamentTargetsReset: "Восстановить значения по умолчанию",
preheatFilamentTargetsDefaultRow: "Прочее / без сопоставления",
preheatMaxWait: "Максимальное ожидание (с)",
@@ -2227,6 +2234,7 @@ export default {
calibrationMode_on: "Включено",
calibrationMode_auto: "Авто",
preheatTargetOverride: "Переопределение температуры камеры (°C; пусто — значение для филамента)",
+ preheatTargetOverrideHelp: "0 нагревает стол и выполняет выдержку без камеры. Если поле пустое, печать, которой камера не нужна, полностью пропускает прогрев.",
plateClear: "Подтверждение очистки пластины",
requirePlateClear: "Требовать подтверждение очистки пластины",
requirePlateClearDescription: "Если включено, планировщик не запускает следующее задание на принтере с завершённой печатью, пока не подтверждена очистка пластины. Отключение также скрывает индикатор состояния пластины и кнопку «Пластина очищена» на карточках принтеров.",
@@ -4682,7 +4690,7 @@ export default {
linkToSpool: "Связать с катушкой",
tagLinked: "Метка связана с катушкой",
tagLinkFailed: "Не удалось связать метку",
- tagAlreadyLinked: "Метка уже связана с другой катушкой",
+ tagAlreadyLinked: 'Метка уже связана с катушкой #{{id}}',
unknownTag: "Обнаружена неизвестная RFID-метка",
usageHistory: "История расхода",
noUsageHistory: "Расход пока не зарегистрирован",
@@ -5139,7 +5147,7 @@ export default {
project: "Проект",
noProject: "Без проекта",
itemsPrinted: "Напечатано изделий",
- itemsPrintedHelp: "Количество изделий, полученных в этом задании печати",
+ itemsPrintedHelp: "Количество изделий, полученных в этом задании печати. Укажите 0, если испорчена вся пластина.",
filamentUsed: "Израсходованный филамент (г)",
filamentUsedPlaceholder: "напр. 46.16",
filamentUsedHelp: "Укажите вручную, если печать заархивирована без 3MF — тогда она попадёт в итоги по филаменту. При повторном сканировании архива с 3MF значение снова считывается из файла.",
@@ -6429,6 +6437,7 @@ export default {
scaleWeight: "По весам",
netWeight: "Нетто",
lastUsed: "Последнее использование",
+ unknownColor: "Неизвестный цвет",
},
ams: {
noData: "AMS не обнаружена",
@@ -6600,7 +6609,7 @@ export default {
skip: "Не проверено — требуется активное подключение MQTT. В старых версиях слайсеров этот параметр существует только в слайсере и принтер его не сообщает, поэтому проверка может пройти даже при выключенном параметре. Проверьте шаг 4 установки вручную.",
skip_unsupported_model: "В этой модели есть слот SD-карты, но включить соответствующую функцию невозможно: текущая прошивка серии P1 не предоставляет переключатель в Bambu Studio, а у принтера нет экрана. Исправлять здесь нечего. Пока Bambu Lab не добавит поддержку в прошивку, архивные задания могут сохраняться без миниатюр и метаданных слайсера.",
fail_no_media: 'Параметр включён, но принтер не видит ни карты, ни флешки в слоте, поэтому отправленным файлам некуда деваться. Вставьте накопитель и напечатайте снова — до тех пор у каждой архивной печати не будет ни миниатюры, ни метаданных слайсера.',
- warn_internal_storage: 'Параметр включён и накопитель на месте, но последняя печать всё равно ушла во внутреннюю память принтера, которую Bambuddy не может прочитать. На серии H2 и P2S кнопка «Печать» в Bambu Studio всегда отправляет туда, независимо от этого параметра. Печати архивируются с именем и временем, но без миниатюры и метаданных слайсера. Чтобы архивы были полными, запускайте печать из Bambuddy, нарезайте в OrcaSlicer или в Bambu Studio используйте «Отправить» с «Внешним накопителем», а затем запускайте печать.',
+ warn_internal_storage: 'Параметр включён и накопитель на месте, но последняя печать всё равно ушла во внутреннюю память принтера, которую Bambuddy не может прочитать. На серии H2, P2S и X2D кнопка «Печать» в Bambu Studio всегда отправляет туда, независимо от этого параметра. Печати архивируются с именем и временем, но без миниатюры и метаданных слайсера. Чтобы архивы были полными, запускайте печать из Bambuddy, нарезайте в OrcaSlicer или в Bambu Studio используйте «Отправить» с «Внешним накопителем», а затем запускайте печать.',
warn_internal_history: 'Опция включена и накопитель на месте, но последняя печать шла из файла, который уже был на принтере, — повторная печать с его экрана, запуск из Handy или файл, отправленный раньше. Эта библиотека лежит во внутренней памяти, которую Bambuddy не читает, и никакие настройки этого не меняют: для той печати ничего не отправлялось. Она по-прежнему архивируется с именем и временем, но без миниатюры и метаданных слайсера. Чтобы архивы были полными, запускайте печать из Bambuddy или отправляйте её из слайсера.',
},
port_rtsps: {
@@ -6608,11 +6617,20 @@ export default {
pass: "Доступен — видеопоток камеры будет работать.",
warn: "Порт {{port}} недоступен. Просмотр камеры в реальном времени работать не будет. На печать это не влияет.",
},
+ macos_local_network: {
+ title: 'Разрешение macOS на доступ к локальной сети',
+ pass: 'macOS разрешает Bambuddy доступ к локальной сети.',
+ warn_unsigned: 'У Python, под которым работает Bambuddy, нет подписи кода, поэтому macOS не к чему привязать разрешение на доступ к локальной сети и молча отбрасывает все подключения к принтеру — без ошибки и без запроса. Запустите сценарий обновления Bambuddy (install/update_macos.sh), который подписывает его, и перезапустите Bambuddy. Интерпретатор: {{executable}}',
+ warn_permission: 'Если принтер включён и доступен по этому адресу, откройте Настройки системы > Конфиденциальность и безопасность > Локальная сеть и убедитесь, что Python, под которым работает Bambuddy, включён. Иначе macOS молча отбрасывает локальные подключения, а обновление Python может оставить старое разрешение позади.',
+ },
network_mode: {
- title: "Сетевой режим Docker",
- pass: "Используется сетевой режим host.",
- warn: "Bambuddy запущен в Docker с сетью bridge. Для обнаружения принтеров и виртуального принтера требуется режим host — пересоздайте контейнер с параметром «network_mode: host».",
- skip: "Bambuddy запущен не в Docker — проверка неприменима.",
+ title: "Сетевой режим контейнера",
+ genericRuntime: "контейнере",
+ pass: "Работает в {{runtime}} с сетью host.",
+ warn: "Bambuddy работает в {{runtime}} с сетью bridge. Для обнаружения принтеров и виртуального принтера требуется сеть host — пересоздайте контейнер с сетью host («network_mode: host» в docker-compose, «--network=host» для Podman).",
+ skip: "Bambuddy запущен не в контейнере — проверка неприменима.",
+ skip_unknown: "Bambuddy работает в {{runtime}}, но определить сетевой режим не удалось. Если обнаружение принтеров или виртуальный принтер не работают, пересоздайте контейнер с сетью host.",
+ skip_system_container: "Bambuddy работает в системном контейнере {{runtime}}, который находится в локальной сети как виртуальная машина — проверка неприменима.",
},
subnet: {
title: "Подсеть",
diff --git a/frontend/src/i18n/locales/sv.ts b/frontend/src/i18n/locales/sv.ts
new file mode 100644
index 000000000..47b5da2b9
--- /dev/null
+++ b/frontend/src/i18n/locales/sv.ts
@@ -0,0 +1,7671 @@
+export default {
+ // Navigation
+ nav: {
+ printers: 'Skrivare',
+ archives: 'Arkiv',
+ queue: 'Utskriftskö',
+ stats: 'Statistik',
+ profiles: 'Profiler',
+ maintenance: 'Underhåll',
+ projects: 'Projekt',
+ finance: 'Finanser',
+ inventory: 'Filament',
+ files: 'Filhanterare',
+ makerworld: 'MakerWorld',
+ notifications: 'Notiser',
+ settings: 'Inställningar',
+ system: 'System',
+ collapseSidebar: 'Minimera sidofältet',
+ expandSidebar: 'Maximera sidofältet',
+ update: 'Uppdatera',
+ updateAvailable: 'Uppdatering tillgänglig: v{{version}}',
+ updateAvailableBanner: 'Version {{version}} är tillgänglig!',
+ viewUpdate: 'Visa uppdatering',
+ viewOnGithub: 'Visa på GitHub',
+ keyboardShortcuts: 'Tangentbordsgenvägar (?)',
+ switchToLight: 'Växla till ljust läge',
+ switchToDark: 'Växla till mörkt läge',
+ switchToSystem: 'Växla till systemläge',
+ smartSwitches: 'Smarta strömbrytare',
+ logout: 'Logga ut',
+ installApp: 'Installera app',
+ installAppSuccess: 'Bambuddy installerades',
+ },
+
+ // Common
+ common: {
+ plusNMore: '+{{count}} till',
+ save: 'Spara',
+ saving: 'Sparar...',
+ cancel: 'Avbryt',
+ delete: 'Ta bort',
+ edit: 'Redigera',
+ add: 'Lägg till',
+ close: 'Stäng',
+ confirm: 'Bekräfta',
+ loading: 'Laddar...',
+ error: 'Fel',
+ errorLoading: 'Fel vid inläsning av data',
+ retry: 'Försök igen',
+ success: 'Lyckades',
+ warning: 'Varning',
+ enabled: 'Aktiverad',
+ disabled: 'Inaktiverad',
+ yes: 'Ja',
+ no: 'Nej',
+ on: 'På',
+ off: 'Av',
+ all: 'Alla',
+ none: 'Inga',
+ search: 'Sök',
+ filter: 'Filtrera',
+ sort: 'Sortera',
+ refresh: 'Uppdatera',
+ download: 'Ladda ner',
+ upload: 'Ladda upp',
+ uploading: 'Laddar upp...',
+ uploadFailed: 'Uppladdning misslyckades',
+ actions: 'Åtgärder',
+ status: 'Status',
+ name: 'Namn',
+ user: 'Användare',
+ description: 'Beskrivning',
+ date: 'Datum',
+ time: 'Tid',
+ hours: 'timmar',
+ minutes: 'minuter',
+ seconds: 'sekunder',
+ days: 'dagar',
+ enable: 'Aktivera',
+ disable: 'Inaktivera',
+ permissions: 'Behörigheter',
+ noPrinters: 'Inga skrivare konfigurerade',
+ noData: 'Ingen data tillgänglig',
+ linkNotFound: 'Länk hittades ej',
+ required: 'Obligatorisk',
+ optional: 'Valfri',
+ dismiss: 'Avfärda',
+ apply: 'Tillämpa',
+ reset: 'Återställ',
+ export: 'Exportera',
+ import: 'Importera',
+ clear: 'Rensa',
+ selectAll: 'Markera alla',
+ deselectAll: 'Avmarkera alla',
+ noChange: '— Ingen ändring —',
+ unchanged: 'Oförändrad',
+ unassigned: 'Otilldelad',
+ unknown: 'Okänd',
+ unknownError: 'Okänt fel',
+ today: 'Idag',
+ tomorrow: 'Imorgon',
+ asap: 'Så snart som möjligt',
+ overdue: 'Försenad',
+ now: 'Nu',
+ collapse: 'Minimera',
+ expand: 'Maximera',
+ previous: 'Föregående',
+ next: 'Nästa',
+ viewArchive: 'Visa arkiv',
+ viewInFileManager: 'Visa i filhanterare',
+ addedBy: 'Tillagd av {{username}}',
+ prints: 'utskrifter',
+ more: '+{{count}} till',
+ ascending: 'Stigande',
+ descending: 'Sjunkande',
+ back: 'Tillbaka',
+ copy: 'Kopiera',
+ copied: 'Kopierad!',
+ printer: 'Skrivare',
+ remove: 'Ta bort',
+ type: 'Typ',
+ print: 'Skriv ut',
+ rename: 'Döp om',
+ move: 'Flytta',
+ create: 'Skapa',
+ duplicate: 'Duplicera',
+ left: 'Vänster',
+ right: 'Höger',
+ run: 'Kör',
+ running: 'Kör...',
+ },
+
+ finance: {
+ allTypes: 'Alla typer',
+ allCostCenters: 'Alla kostnadsställen',
+ title: 'Finanser',
+ subtitle: 'Plånbok, personliga transaktioner och kostnadsställen',
+ noAccess: 'Du har inte behörighet att visa finansiella data.',
+ personalView: 'Personlig vy',
+ adminView: 'Adminvy',
+ createCostCenter: 'Skapa kostnadsställe',
+ adjustWallet: 'Justera plånbok',
+ addManualPrint: 'Lägg till manuell utskrift',
+ manageMembers: 'Hantera medlemmar i kostnadsställe',
+ currentBalance: 'Aktuellt saldo',
+ transactions: 'Transaktioner',
+ personalTransactions: 'Personliga transaktioner',
+ costCenters: 'Kostnadsställen',
+ availableForPrinting: 'Tillgängligt för utskrifter',
+ costCenterName: 'Namn',
+ budgetType: 'Budgettyp',
+ monthlyBudget: 'Månadsbudget',
+ totalBudget: 'Total budget',
+ noBudget: "Ingen budget",
+ create: 'Skapa',
+ selectUser: 'Välj användare',
+ transactionType: 'Typ',
+ amount: 'Belopp',
+ noCostCenter: 'Inget kostnadsställe',
+ descriptionOptional: 'Beskrivning (valfritt)',
+ applyAdjustment: 'Tillämpa justering',
+ memberCanPrint: 'Medlem kan skriva ut',
+ addMember: 'Lägg till medlem',
+ canPrint: 'Kan skriva ut',
+ noMembers: 'Inga medlemmar tilldelade.',
+ editCostCenter: 'Redigera kostnadsställe',
+ myCostCenters: 'Mina kostnadsställen',
+ costCentersHint: 'Granska budgetgränser och håll kostnaderna under kontroll',
+ noCostCenters: 'Inga kostnadsställen hittade.',
+ owner: 'Ägare',
+ balance: 'Kontosaldo',
+ unlimited: 'Obegränsad',
+ budgetPolicyHint: 'Kontosaldon håller reda på kostnader. Utskrift är begränsat endast av det valda kostnadsställets budget; utan budget är utskriften obgränsad.',
+ budget: 'Budget',
+ shared: 'Delad',
+ cannotEditPrivateCostCenter: 'Privat kostnadsställe kan inte redigeras här',
+ recentTransactions: 'Senaste transaktioner',
+ transactionsHint: 'Filtrera efter typ och kostnadsställe',
+ first: 'Första',
+ prev: 'Föregående',
+ next: 'Nästa',
+ last: 'Sista',
+ pageNumberOf: 'Sida {{page}} av {{total}}',
+ noTransactions: 'Inga transaktioner tillgängliga.',
+ noTransactionsForFilter: 'Inga transaktioner matchar de valda filtren.',
+ costCenter: 'Kostnadsställe',
+ balanceAfter: 'Saldo efter',
+ userWithId: 'Användare #{{id}}',
+ partial: 'Delvis',
+ editTransaction: 'Redigera transaktion',
+ selectCostCenter: 'Välj kostnadsställe...',
+ costCenterRequired: 'Vänligen välj ett kostnadsställe',
+ amountExample: 't.ex., 4,00',
+ manualAdjustmentExample: 't.ex., manuell justering',
+ userRequired: 'Välj en användare',
+ amountInvalid: 'Beloppet måste vara ett giltigt nummer',
+ createdCostCenter: 'Kostnadsställe skapat',
+ createCostCenterFailed: 'Kunde inte skapa kostnadsställe',
+ transactionDeleted: 'Transaktion borttagen',
+ deleteTransactionFailed: 'Kunde inte ta bort transaktion',
+ transactionEdited: 'Transaktion uppdaterad och bokföring omberäknad',
+ editTransactionFailed: 'Kunde inte redigera transaktion',
+ manualPrintCreated: 'Manuell utskriftsavgift tillagd och bokföring omberäknad',
+ manualPrintFailed: 'Kunde inte skapa manuell utskrift',
+ memberSaved: 'Medlem sparad',
+ memberSaveFailed: 'Kunde inte spara medlem',
+ memberRemoved: 'Medlem borttagen',
+ memberRemoveFailed: 'Kunde inte ta bort medlem',
+ costCenterNameRequired: 'Namn på kostnadsställe krävs',
+ costCenterUpdated: 'Kostnadsställe uppdaterat',
+ costCenterUpdateFailed: 'Kunde inte uppdatera kostnadsställe',
+ confirmDeleteCostCenter: 'Ta bort kostnadsställe "{{name}}"?',
+ costCenterDeleted: 'Kostnadsställe borttaget',
+ costCenterDeleteFailed: 'Kunde inte ta bort kostnadsställe',
+ deleteTransactionConfirm: 'Ta bort denna transaktion? Saldon kommer att omberäknas automatiskt.',
+ deposit: 'Insättning',
+ withdraw: 'Uttag',
+ printCharge: 'Utskriftsavgift',
+ deleteTransaction: 'Ta bort transaktion',
+ },
+
+ // Printers page
+ printers: {
+ title: 'Skrivare',
+ addPrinter: 'Lägg till skrivare',
+ addPreflight: {
+ checking: 'Kontrollerar anslutning...',
+ warning: 'Vissa anslutningskontroller misslyckades. Denna skrivare kan visas som offline. Granska kontrollerna nedan, åtgärda vad du kan, eller spara ändå.',
+ back: 'Tillbaka',
+ saveAnyway: 'Spara ändå',
+ },
+ editPrinter: 'Redigera skrivare',
+ deletePrinter: 'Ta bort skrivare',
+ printerName: 'Skrivarnamn',
+ serialNumber: 'Serienummer',
+ ipAddress: 'IP-adress / värdnamn',
+ accessCode: 'Åtkomstkod',
+ model: 'Modell',
+ nozzleCount: 'Antal munstycken',
+ autoArchive: 'Autoarkivera',
+ status: {
+ available: 'Tillgänglig',
+ idle: 'Inaktiv',
+ printing: 'Skriver ut',
+ paused: 'Pausad',
+ offline: 'Offline',
+ problem: 'Problem',
+ error: 'Fel',
+ finished: 'Färdig',
+ unknown: 'Okänd',
+ },
+ temperatures: {
+ nozzle: 'Munstycke',
+ bed: 'Byggplatta',
+ chamber: 'Kammare',
+ },
+ heaterHistory: {
+ title: 'Värmehistorik',
+ openLabel: 'Visa värmehistorik',
+ nozzle: 'Munstycke',
+ nozzle2: 'Munstycke 2',
+ bed: 'Byggplatta',
+ chamber: 'Kammare',
+ error: 'Kunde inte ladda historik',
+ empty: 'Ingen data inspelad än',
+ },
+ progress: '{{percent}}% klar',
+ timeRemaining: '{{time}} kvar',
+ deleteConfirm: 'Är du säker på att du vill ta bort "{{name}}"?',
+ maintenanceOk: 'Underhåll OK',
+ maintenanceWarning: '{{count}} varning',
+ maintenanceWarning_plural: '{{count}} varningar',
+ maintenanceDue: '{{count}} utebliven',
+ maintenanceDue_plural: '{{count}} uteblivna',
+ // Sort options
+ sort: {
+ name: 'Namn',
+ status: 'Status',
+ model: 'Modell',
+ location: 'Plats',
+ eta: 'Beräknad tid',
+ ascending: 'Sortera stigande',
+ descending: 'Sortera fallande',
+ },
+ // Card size
+ cardSize: {
+ small: 'Små kort',
+ medium: 'Mellanstora kort',
+ large: 'Stora kort',
+ extraLarge: 'Extra stora kort',
+ },
+ pageView: {
+ openCamWallPage: 'Öppna kameravägg som sida',
+ cards: 'Kort',
+ camWall: 'Kameravägg',
+ },
+ camWall: {
+ page: {
+ tokenRejected:
+ 'Denna länk till kameraväggen är inte längre giltig. Token kan ha löpt ut eller återkallats.',
+ loadFailed: 'Kunde inte ladda skrivarna.',
+ },
+ noPrinters: 'Inga skrivare att visa',
+ noSignal: 'Ingen signal',
+ live: 'Live',
+ snap: 'Ögonblicksbild',
+ off: 'Av',
+ summary: '{{live}} live, {{snap}} ögonblicksbilder, {{total}} totalt',
+ layer: 'Skikt {{cur}}/{{total}}',
+ timeLeft: '{{time}} kvar',
+ statusMode: {
+ off: 'Av',
+ compact: 'Kompakt',
+ full: 'Fullständig',
+ },
+ settings: {
+ title: 'Inställningar för kameravägg',
+ maxLive: 'Max antal live-sändningar',
+ maxLiveHint: 'Hur många rutor som sänds live samtidigt. Övriga uppdateras som ögonblicksbilder.',
+ snapshotInterval: 'Intervall för ögonblicksbilder (sekunder)',
+ snapshotIntervalHint: 'Hur ofta icke-live-rutor hämtar en ny ögonblicksbild.',
+ statusOverlay: 'Statusöverlägg',
+ statusOverlayHint: 'Kompakt: Endast statusmärke. Fullständig: + framsteg, skikt, tid kvar.',
+ },
+ },
+ // Controls
+ hideOffline: 'Dölj offline',
+ nextAvailable: 'Nästa tillgängliga',
+ powerOn: 'Sätt på',
+ offlinePrintersWithPlugs: 'Offline-skrivare med smarta kontakter',
+ noPrintersConfigured: 'Inga skrivare konfigurerade än',
+ search: 'Sök skrivare...',
+ noSearchResults: 'Inga skrivare matchar din sökning eller filter',
+ filter: {
+ allStatuses: 'Alla statusar',
+ allLocations: 'Alla platser',
+ },
+ toolbar: {
+ filters: 'Filter',
+ view: 'Vy',
+ actions: 'Åtgärder',
+ },
+ // Printer card
+ readyToPrint: 'Redo att skriva ut',
+ external: 'Extern',
+ extL: 'Ext-V',
+ extR: 'Ext-H',
+ deleteArchives: 'Ta bort utskriftsarkiv',
+ noLabel: 'Ingen etikett',
+ printPreview: 'Förhandsgranskning av utskrift',
+ width: 'Bredd',
+ height: 'Höjd',
+ noObjectsFound: 'Inga objekt hittade',
+ objectsLoadedOnPrintStart: 'Objekt laddas när en utskrift startar',
+ willBeSkipped: 'Kommer att hoppas över',
+ name: 'Namn',
+ serialCannotBeChanged: 'Serienummer kan inte ändras',
+ locationHelp: 'Används för att gruppera skrivare och filtrera köuppgifter',
+ // WiFi signal strength
+ wifiSignal: {
+ veryWeak: 'Mycket svag',
+ weak: 'Svag',
+ fair: 'Medel',
+ good: 'Bra',
+ excellent: 'Utmärkt',
+ },
+ // Maintenance
+ maintenanceUpToDate: 'Allt underhåll är uppdaterat - Klicka för att se',
+ // Maintenance Mode (#1476) — operator-flipped "out of service" state.
+ // Distinct from the scheduled-maintenance dashboard above; this one
+ // wraps the backend is_active flag and stops MQTT + queue dispatch.
+ maintenance: {
+ title: 'I underhållsläge',
+ subtitle: 'Denna skrivare är pausad — ej ansluten, ej berättigad för kö, skickar inga notiser.',
+ pillLabel: 'Underhåll',
+ exitButton: 'Avsluta underhåll',
+ menuEnter: 'Aktivera underhållsläge',
+ menuExit: 'Avsluta underhållsläge',
+ toastEntered: '{{name}} är nu i underhållsläge',
+ toastExited: '{{name}} är tillbaka online',
+ confirmMidPrintTitle: 'Aktivera underhållsläge under utskrift?',
+ confirmMidPrintMessage: '{{name}} skriver för närvarande ut. Om du aktiverar underhållsläge kommer MQTT att kopplas från och framstegsspårning samt färdighetsnotiser för detta jobb kommer att stoppas. Fortsätta?',
+ editFieldLabel: 'Underhållsläge',
+ editFieldHelp: 'När det är på, är denna skrivare pausad från MQTT, köutskick och notiser — användbart för reparation, parallella Bambuddy-installationer eller tillfällig paus.',
+ },
+ // Chamber light
+ chamberLightOn: 'Sätt på kammarljus',
+ chamberLightOff: 'Stäng av kammarljus',
+ // Files
+ files: 'Filer',
+ browseFiles: 'Bläddra bland skrivarens filer',
+ // Smart plug
+ autoOffAfterPrint: 'Automatisk avstängning efter utskrift',
+ autoOffExecuted: 'Auto-avstängning utfördes – sätt på skrivaren för att återställa',
+ // HMS errors
+ hmsErrors: 'HMS-fel',
+ viewHmsErrors: 'Visa {{count}} HMS-fel',
+ // Actions
+ resume: 'Fortsätt',
+ pause: 'Pausa',
+ stop: 'Stoppa',
+ camera: 'Kamera',
+ skipObject: 'Hoppa över objekt',
+ reconnect: 'Återanslut',
+ forceRefresh: 'Tvinga uppdatering',
+ forceRefreshSuccess: 'Uppdatering begärd',
+ mqttDebug: 'MQTT-felsökning',
+ printerInformation: 'Skrivarinformation',
+ copyToClipboard: 'Kopiera',
+ copied: 'Kopierad!',
+ state: 'Status',
+ wifiSignalLabel: 'WiFi-signal',
+ developerMode: 'Utvecklarläge',
+ enabled: 'Aktiverad',
+ disabled: 'Inaktiverad',
+ addedOn: 'Tillagd',
+ sdCard: 'SD-kort',
+ inserted: 'Insatt',
+ notInserted: 'Ej insatt',
+ totalPrintHours: 'Utskriftstimmar',
+ activeNozzle: 'Aktiv: {{nozzle}} munstycke',
+ amsSwitchInletTooltip: 'Filamentspårväxlare IN-{{inlet}} ({{side}}) — matar båda munstyckena',
+ nozzleRack: 'Munstyckesställ',
+ nozzleDocked: 'Parkerad',
+ nozzleMounted: 'Monterad',
+ nozzleActive: 'Aktiv',
+ nozzleIdle: 'Inaktiv',
+ nozzleDiameter: 'Diameter',
+ nozzleType: 'Typ',
+ nozzleStatus: 'Status',
+ nozzleFilament: 'Filament',
+ nozzleWear: 'Slitage',
+ nozzleMaxTemp: 'Max temp',
+ nozzleSerial: 'Serienummer',
+ nozzleHardenedSteel: 'Härdat stål',
+ nozzleStainlessSteel: 'Rostfritt stål',
+ nozzleTungstenCarbide: 'Volframkarbid',
+ nozzleFlow: 'Flöde',
+ nozzleHighFlow: 'Högt flöde',
+ nozzleStandardFlow: 'Standard',
+ // Firmware
+ firmwareUpdate: 'Firmwareuppdatering',
+ firmwareInstructions: 'På skrivarens pekskärm, gå till',
+ firmwareNav: 'Navigera till',
+ settings: 'Inställningar',
+ firmware: 'Firmware',
+ // Discovery
+ discoverPrinters: 'Hitta skrivare',
+ searching: 'Söker...',
+ manualEntry: 'Manuell inmatning',
+ addFromCloud: 'Lägg till från molnet',
+ // Toast messages
+ toast: {
+ printerDeleted: 'Skrivare borttagen',
+ missingSpoolAssignment: 'Utskrift startade på {{printer}}. Saknas spool-tilldelning för: {{slots}}',
+ killSwitchTriggered: 'Fjärrlås för fakturering stoppade en obehörig utskrift på {{printer}}: {{filename}}',
+ billingChargeFailed: 'Fakturering misslyckades för {{filename}} på {{printer}}. Budgetreservationen bibehölls; kontrollera serverloggarna.',
+ assignmentVerified: 'Filament laddat i fack {{slot}} ({{printer}})',
+ assignmentVerifiedNoKprofile: 'Fack {{slot}} på {{printer}} laddat, men flödeskalibrering (K-profil) tillämpades inte',
+ assignmentNotConfirmed: 'Kunde inte bekräfta tilldelningen för fack {{slot}} på {{printer}} — kontrollera AMS-facket',
+ printerAdded: 'Skrivare tillagd',
+ printerUpdated: 'Skrivare uppdaterad',
+ failedToDelete: 'Kunde inte ta bort skrivare',
+ failedToAdd: 'Kunde inte lägga till skrivare',
+ connectionFailedNotAdded: 'Kunde inte ansluta till skrivaren. Verifiera IP, serienummer och åtkomstkod, och bekräfta att LAN-läge är aktiverat. Skrivaren lades inte till.',
+ failedToUpdate: 'Kunde inte uppdatera skrivare',
+ commandSent: 'Kommando skickat',
+ failedToSendCommand: 'Kunde inte skicka kommando',
+ turnedOn: '{{name}} slogs på',
+ failedToPowerOn: 'Kunde inte sätta på {{name}}',
+ scriptTriggered: 'Skript utlöst',
+ printStopped: 'Utskrift stoppad',
+ printPaused: 'Utskrift pausad',
+ printResumed: 'Utskrift återupptagen',
+ referenceDeleted: 'Referens borttagen',
+ detectionAreaSaved: 'Detekteringsområde sparat',
+ failedToRunScript: 'Kunde inte köra skript',
+ failedToStopPrint: 'Kunde inte stoppa utskrift',
+ failedToPausePrint: 'Kunde inte pausa utskrift',
+ failedToResumePrint: 'Kunde inte återuppta utskrift',
+ failedToControlChamberLight: 'Kunde inte kontrollera kammarljus',
+ failedToSetSpeed: 'Kunde inte ställa in utskriftshastighet',
+ failedToUpdateSetting: 'Kunde inte uppdatera inställning',
+ failedToSkipObjects: 'Kunde inte hoppa över objekt',
+ failedToRereadRfid: 'Kunde inte läsa om RFID',
+ failedToCheckPlate: 'Kunde inte kontrollera platta',
+ failedToUpdateLabel: 'Kunde inte uppdatera etikett',
+ failedToDeleteReference: 'Kunde inte ta bort referens',
+ failedToSaveDetectionArea: 'Kunde inte spara detekteringsområde',
+ plateCheckEnabled: 'Plattkontroll aktiverad',
+ plateCheckDisabled: 'Plattkontroll inaktiverad',
+ calibrationSaved: 'Kalibrering sparad!',
+ calibrationFailed: 'Kalibrering misslyckades',
+ rfidRereadInitiated: 'Omstart av RFID-läsning påbörjad',
+ loadInitiated: 'Laddar filament…',
+ unloadInitiated: 'Laddar ur filament…',
+ failedToLoad: 'Kunde inte ladda filament',
+ failedToUnload: 'Kunde inte ladda ur filament',
+ },
+ // Connection status
+ connection: {
+ connected: 'Ansluten',
+ offline: 'Offline',
+ },
+ plateStatus: {
+ markCleared: 'Markera platta som tömd',
+ cleared: 'Platta tömd',
+ notCleared: 'Platta ej tömd',
+ inUse: 'Platta i användning',
+ },
+ // Queue info
+ queue: {
+ inQueue: '{{count}} utskrift i kö',
+ inQueue_plural: '{{count}} utskrifter i kö',
+ },
+ // Controls section
+ controls: 'Kontroller',
+ // RFID
+ rfid: {
+ reread: 'Läs om RFID',
+ },
+ // AMS load/unload (#891)
+ ams: {
+ load: 'Ladda',
+ unload: 'Ladda ur',
+ feedTitle: 'Ladda {{slot}} till vilket munstycke?',
+ feedPrompt: 'Filamentspårväxlaren kan dirigera detta fack till endera värmeblock. Välj vilken du vill mata.',
+ feedLeft: 'Vänster munstycke',
+ feedRight: 'Höger munstycke',
+ feedAlreadyLoaded: 'Redan laddad',
+ switchNotReady: 'Filamentspårväxlaren är inte konfigurerad ännu. Tilldela varje AMS till ett inlopp på skrivaren och försök sedan igen.',
+ },
+ bedJog: {
+ limitWarning: 'Rörlighetsgränser tillämpas inte under manuella förflyttningar — en Bambu-firmware-bugg ignorerar mjukvarustoppar för fjärrkommandon. Flytta försiktigt för att undvika kollision.',
+ title: 'Manuella kontroller',
+ bed: 'Byggplatta',
+ step: 'Steg (mm)',
+ up: 'Flytta upp platta',
+ down: 'Flytta ner platta',
+ toolheadUp: 'Flytta upp extruderhuvud',
+ toolheadDown: 'Flytta ner extruderhuvud',
+ disabledWhilePrinting: 'Inaktiverad under utskrift',
+ notHomedTitle: 'Skrivaren är inte riktad',
+ notHomedMessage: 'Skrivaren har inte riktats sedan senaste utskriften. Kör auto-riktning för säker positionering (parkerar verktygshuvudet, riktar sedan X, Y och Z), eller flytta ändå — mjukvarustoppar kommer att ignoreras.',
+ homeZ: 'Auto-riktning',
+ moveAnyway: 'Flytta ändå',
+ homingStarted: 'Auto-riktning pågår…',
+ },
+ // Permissions
+ permission: {
+ noAdd: 'Du har inte behörighet att lägga till skrivare',
+ noEdit: 'Du har inte behörighet att redigera skrivare',
+ noDelete: 'Du har inte behörighet att ta bort skrivare',
+ noControl: 'Du har inte behörighet att kontrollera skrivare',
+ noFiles: 'Du har inte behörighet att komma åt skrivarens filer',
+ noAmsRfid: 'Du har inte behörighet att läsa om AMS-RFID',
+ noSmartPlugControl: 'Du har inte behörighet att kontrollera smarta kontakter',
+ noCamera: 'Du har inte behörighet att visa kameror',
+ },
+ // Add/Edit modal
+ modal: {
+ addTitle: 'Lägg till skrivare',
+ editTitle: 'Redigera skrivare',
+ myPrinter: 'Min skrivare',
+ selectModel: 'Välj modell...',
+ locationGroup: 'Plats / grupp (valfritt)',
+ locationPlaceholder: 't.ex. Verkstad, Kontor, Källare',
+ autoArchiveLabel: 'Auto-arkivera färdiga utskrifter',
+ fromPrinterSettings: 'Från skrivarens inställningar',
+ modelOptional: 'Modell (valfritt)',
+ saveChanges: 'Spara ändringar',
+ },
+ // Skip objects
+ skipObjects: {
+ tooltip: 'Hoppa över objekt',
+ onlyWhilePrinting: 'Hoppa över objekt (endast under utskrift)',
+ requiresMultiple: 'Hoppa över objekt (kräver 2+ objekt)',
+ title: 'Hoppa över objekt',
+ matchIdsInfo: 'Matcha ID:n med skrivarens display',
+ printerShowsIds: 'Skrivarens skärm visar objekt-ID:n på byggplattan',
+ skipSelected: 'Hoppa över valda',
+ skipping: 'Hoppar över...',
+ noObjectsSelected: 'Inga objekt valda',
+ selectObjectsToSkip: 'Välj objekt du vill hoppa över från den aktuella utskriften',
+ skipped: 'hoppades över',
+ objectsSkipped: 'Objekt hoppades över',
+ activeCount: '{{count}} aktiva',
+ waitForLayer: 'Vänta på skikt 2+ för att hoppa över objekt (nuvarande skikt {{layer}})',
+ skip: 'Hoppa över',
+ confirmTitle: 'Hoppa över objekt?',
+ confirmMessage: 'Är du säker på att du vill hoppa över "{{name}}"? Detta kan inte ångras.',
+ confirmAllMessage: 'Alla återstående objekt är valda. Detta kommer att stoppa utskriftsjobbet. Fortsätta?',
+ confirmMultipleMessage: 'Hoppa över {{count}} valda objekt? Detta kan inte ångras.',
+ },
+ // Confirm modals
+ confirm: {
+ deleteTitle: 'Ta bort skrivare',
+ deleteMessage: 'Är du säker på att du vill ta bort "{{name}}"? Detta kommer att ta bort alla anslutningsinställningar.',
+ deleteArchivesNote: 'All utskriftshistorik för denna skrivare kommer att tas bort permanent.',
+ keepArchivesNote: 'Utskriftshistorik kommer att behållas men inte längre vara kopplad till denna skrivare.',
+ stopTitle: 'Stoppa utskrift',
+ stopMessage: 'Är du säker på att du vill stoppa den aktuella utskriften på "{{name}}"? Detta kommer att avbryta utskriftsjobbet.',
+ stopButton: 'Stoppa utskrift',
+ pauseTitle: 'Pausa utskrift',
+ pauseMessage: 'Är du säker på att du vill pausa den aktuella utskriften på "{{name}}"?',
+ pauseButton: 'Pausa utskrift',
+ resumeTitle: 'Fortsätt utskrift',
+ resumeMessage: 'Är du säker på att du vill fortsätta utskriften på "{{name}}"?',
+ resumeButton: 'Fortsätt utskrift',
+ powerOnTitle: 'Sätt på skrivare',
+ powerOnMessage: 'Är du säker på att du vill sätta på strömmen för "{{name}}"?',
+ powerOnButton: 'Sätt på',
+ powerOffTitle: 'Stäng av skrivare',
+ powerOffMessage: 'Är du säker på att du vill stänga av strömmen för "{{name}}"?',
+ powerOffWarning: 'VARNING: "{{name}}" skriver för närvarande ut! Är du säker på att du vill stänga av strömmen? Detta kommer att avbryta utskriften och kan skada skrivaren.',
+ powerOffButton: 'Stäng av',
+ haToggleTitle: 'Växla "{{name}}"',
+ haToggleMessage: 'Växla Home Assistant-enheten {{entity}}? Detta kan stänga av strömmen om den för närvarande är på.',
+ haToggleWarning: 'VARNING: "{{name}}" skriver för närvarande ut! Att växla {{entity}} kan bryta strömmen och avbryta utskriften. Fortsätta?',
+ haToggleButton: 'Växla',
+ },
+ // Bulk actions
+ bulk: {
+ select: 'Välj',
+ selectAll: 'Välj alla',
+ selectByLocation: 'Välj efter plats',
+ selected: '{{count}} valda',
+ actions: {
+ stop: 'Stoppa',
+ pause: 'Pausa',
+ resume: 'Fortsätt',
+ clearPlate: 'Töm byggplatta',
+ clearHMS: 'Rensa notiser',
+ },
+ confirm: {
+ stopTitle: 'Stoppa {{count}} utskrifter',
+ stopMessage: 'Detta kommer att avbryta aktiva utskrifter på {{count}} skrivare. Denna åtgärd kan inte ångras.',
+ stopButton: 'Stoppa alla',
+ pauseTitle: 'Pausa {{count}} utskrifter',
+ pauseMessage: 'Detta kommer att pausa aktiva utskrifter på {{count}} skrivare.',
+ pauseButton: 'Pausa alla',
+ clearPlateTitle: 'Töm {{count}} byggplattor',
+ clearPlateMessage: 'Detta kommer att tömma byggplattan på {{count}} skrivare och kan utlösa köade jobb.',
+ clearPlateButton: 'Töm alla',
+ },
+ success: '{{action}} slutförd på {{count}} skrivare',
+ partial: '{{succeeded}} lyckades, {{failed}} misslyckades',
+ noneApplicable: 'Inga valda skrivare är i rätt tillstånd för denna åtgärd',
+ selectByState: 'Välj efter status',
+ },
+ // Discovery
+ discovery: {
+ title: 'Hitta skrivare',
+ searching: 'Söker...',
+ scanning: 'Skannar...',
+ scanProgress: 'Skannar... {{scanned}}/{{total}}',
+ foundPrinters: 'Hittade {{count}} skrivare',
+ noPrintersFound: 'Inga skrivare hittade',
+ noPrintersFoundSubnet: 'Inga skrivare hittade i det angivna subnätet.',
+ noPrintersFoundNetwork: 'Inga skrivare hittade i nätverket.',
+ allConfigured: 'Alla hittade skrivare är redan konfigurerade.',
+ alreadyAdded: 'Redan tillagd',
+ select: 'Välj',
+ manualEntry: 'Manuell inmatning',
+ addFromCloud: 'Lägg till från molnet',
+ subnetToScan: 'Subnät att söka',
+ dockerNote: 'Docker upptäckt. Ange din skrivares subnät i CIDR-notation. Kräver network_mode: host i docker-compose.yml.',
+ scanSubnet: 'Skanna subnät efter skrivare',
+ discoverNetwork: 'Hitta skrivare i nätverket',
+ scanningSubnet: 'Söker subnät efter Bambu-skrivare...',
+ scanningNetwork: 'Söker nätverk...',
+ serialRequired: 'Serienummer krävs',
+ unknown: 'Okänd',
+ failedToStart: 'Kunde inte starta skanning',
+ customSubnetOption: 'Anpassat subnät...',
+ customSubnetLabel: 'Anpassat subnät (CIDR)',
+ customSubnetNote: 'Använd ett anpassat subnät om din skrivare är på ett annat nätverk än denna server. FTP (990) och MQTT (8883) portarna måste vara nåbara över routningsgränsen.',
+ },
+ // AMS Drying
+ drying: {
+ start: 'Starta torkning',
+ stop: 'Stoppa torkning',
+ temperature: 'Temperatur',
+ duration: 'Varaktighet',
+ hours: 'timmar',
+ timeRemaining: '{{time}} kvar',
+ active: 'Torkar',
+ targetSummary: '{{filament}} @ {{temp}}°C',
+ notSupported: 'Torkning stöds inte',
+ powerRequired: 'Anslut AMS-strömadapter för att aktivera torkning',
+ cannotDryNow: 'AMS kan inte börja torka just nu',
+ startingDrying: 'Startar torkning...',
+ toastCommandSent: 'Torkningskommando skickat',
+ toastStopped: 'Torkning stoppad',
+ toastNotStarted: 'Skrivaren accepterade kommandot men AMS startade aldrig torkning. Kontrollera att AMS-strömadaptern är ansluten och att skrivaren är inaktiv.',
+ screenOnly: 'AMS-torkning på denna skrivare kan endast kontrolleras från skrivarens egen skärm (Bambu-begränsning)',
+ stoppingDrying: 'Stoppar torkning...',
+ rotateTray: 'Roterar rulle under torkning',
+ rotateUnavailableReason: 'Ej tillgänglig — en slot i denna AMS är laddad till verktygshuvudet. Rullen är låst av matarröret och kan inte rotera. Dra tillbaka filamentet först.',
+ startMode: 'Starttid',
+ modeNow: 'Nu',
+ modeDelay: 'Efter fördröjning',
+ modeAtTime: 'Vid tidpunkt',
+ // Verb (button label), not the noun.
+ schedule: 'Schemalägg',
+ scheduledFor: 'Torkning schemalagd för {{time}}',
+ scheduledAsap: 'Torkning schemalagd (väntar på skrivare)',
+ cancelScheduled: 'Avbryt schemalagd torkning',
+ scheduleFailed: 'Kunde inte schemalägga torkning',
+ retractFilament: 'Dra tillbaka filamentet vid AMS-utloppet för att starta torkning',
+ waitingAmsNotFound: 'Väntar på att AMS ska upptäckas',
+ waitingOffline: 'Väntar på att skrivaren ska bli online',
+ waitingPrinterBusy: 'Väntar på att skrivaren ska bli ledig',
+ waitingAlreadyDrying: 'Väntar på att den aktuella torkningscykeln ska slutföras',
+ waitingInterrupted: 'Avbruten, kommer att starta om när skrivaren är ledig',
+ scheduleFailedReason: 'Schemalagd torkning misslyckades: {{reason}}',
+ scheduleFailedUnknown: 'Okänt fel',
+ dismissFailed: 'Avfärda',
+ },
+ // AMS Filament Backup status badge (printer-wide auto-switch to another spool)
+ amsBackup: {
+ titleOn: 'AMS filamentbackup är PÅ. Klicka för att inaktivera.',
+ titleOff: 'AMS filamentbackup är AV. Klicka för att aktivera.',
+ titleUnknown: 'AMS filamentbackupstatus ej tillgänglig på denna skrivare.',
+ toastEnabled: 'AMS filamentbackup aktiverad',
+ toastDisabled: 'AMS filamentbackup inaktiverad',
+ modalTitle: 'AMS filamentbackup',
+ modalHelp: 'När det aktiva facket tar slut går skrivaren genom alla matchande fack med samma inställningar och färg i denna ordning.',
+ modalNoSlots: 'Inga filament laddade.',
+ modalNoPairs: 'Inga backup-par — inga fack delar samma filamentprofil och färg.',
+ extruderRightShort: 'H',
+ extruderLeftShort: 'V',
+ stateOn: 'Aktiverad',
+ stateOff: 'Inaktiverad',
+ stateUnknown: 'Stöds inte på denna skrivare',
+ },
+ activeJobSlot: {
+ title: 'Detta fack är filament {{n}} i den aktiva utskriften',
+ ariaLabel: 'Aktivt fack {{n}}',
+ },
+ expectedSlot: {
+ title: 'Skrivaren väntar på filament i detta fack',
+ ariaLabel: 'Förväntat filamentfack {{n}}',
+ label: '{{ams}} · Fack {{slot}}',
+ external: 'Extern rulle',
+ },
+ // Filaments section
+ filaments: 'Filament',
+ externalSpool: {
+ hide: 'Dölj extern rulle',
+ show: 'Visa extern rulle',
+ },
+ // Camera
+ openCameraOverlay: 'Öppna kameran i överlägg',
+ openCameraWindow: 'Öppna kameran i nytt fönster',
+ // Firmware
+ firmwareUpdateAvailable: 'Firmwareuppdatering tillgänglig: {{current}} → {{latest}}',
+ firmwareUpToDate: 'Firmware {{version}} — Uppdaterad',
+ firmwareUpdateButton: 'Uppdatera',
+ // Plate detection
+ plateDetection: {
+ noPermission: 'Du har inte behörighet att uppdatera skrivare',
+ enabledClick: 'Plattkontroll aktiverad – Klicka för att inaktivera',
+ disabledClick: 'Plattkontroll inaktiverad – Klicka för att aktivera',
+ manageCalibration: 'Hantera plattdetekteringskalibrering',
+ calibrationRequired: 'Kalibrering krävs',
+ calibrationInstructions: 'Se till att byggplattan är helt tom , klicka sedan på Kalibrera.',
+ calibrationDescription: 'Kalibrering tar en referensbild av den tomma plattan. Framtida kontroller kommer att jämföras med denna referens för att upptäcka objekt.',
+ calibrationTip: 'Tips: Du kan spara upp till 5 kalibreringar för olika plattor. Systemet använder automatiskt den bästa matchningen vid kontroll.',
+ plateEmpty: 'Plattan verkar tom',
+ objectsDetected: 'Objekt upptäckta på plattan',
+ confidence: 'Säkerhet',
+ difference: 'Skillnad',
+ analysisPreview: 'Analysförhandsvisning:',
+ analysisLegend: 'Grön ruta = detekteringsområde, Rött överlägg = skillnad från kalibrering',
+ savedReferences: 'Sparade referenser ({{count}}/{{max}})',
+ deleteReference: 'Ta bort referens',
+ labelPlaceholder: 'Etikett...',
+ clickToEdit: '{{label}} – Klicka för att redigera',
+ clickToAddLabel: 'Klicka för att lägga till etikett',
+ },
+ // Speed
+ speed: {
+ title: 'Utskriftshastighet',
+ silent: 'Tyst (50%)',
+ standard: 'Standard (100%)',
+ sport: 'Sport (124%)',
+ ludicrous: 'Löjlig (166%)',
+ },
+ airduct: {
+ title: 'Luftkanalsläge',
+ cooling: 'Kylning',
+ heating: 'Uppvärmning',
+ },
+ noSdCard: 'Inget SD-kort',
+ door: {
+ open: 'Öppen',
+ closed: 'Stängd',
+ },
+ // Fans
+ fans: {
+ partCooling: 'Delkylningsfläkt',
+ auxiliary: 'Hjälpfläkt',
+ leftAuxiliary: 'Vänster hjälpfläkt',
+ exhaust: 'Frånluft',
+ chamber: 'Kammarfläkt',
+ },
+ // HMS errors
+ clickToViewHmsErrors: 'Klicka för att visa HMS-fel',
+ aiDetection: {
+ safe: 'Säker',
+ warning: 'Varning',
+ failure: 'Fel',
+ idle: 'Inaktiv',
+ error: 'Kontrollerar ej',
+ unknown: 'Startar',
+ tooltipError: 'AI-feldetektering kontrollerar inte denna utskrift: {{reason}} - klicka för detaljer',
+ tooltipUnknown: 'AI-feldetektering: väntar på det första resultatet - klicka för detaljer',
+ errorHint: 'Denna utskrift kontrolleras ej. Detekteringen återupptas automatiskt när problemet nedan är åtgärdat.',
+ tooltip: 'AI-feldetektering: {{status}} (score {{score}}) - klicka för detaljer',
+ tooltipIdle: 'AI-feldetektering aktiverad – övervakning startar med nästa utskrift – klicka för detaljer',
+ modalTitle: 'AI-feldetektering – {{name}}',
+ currentStatus: 'Status',
+ score: 'Poäng',
+ framesAnalyzed: 'Analyserade bilder',
+ idleHint: 'Ingen utskrift övervakas för närvarande. Övervakning startar automatiskt med nästa utskrift.',
+ lastError: 'Senaste fel',
+ openSettings: 'Öppna inställningar',
+ },
+ estimatedCompletion: 'Beräknad färdigställningstid',
+ plateNumber: 'Platta {{number}}',
+ slotOptions: 'Fackalternativ',
+ // AMS hover popup
+ amsPopup: {
+ friendlyName: 'AMS-namn',
+ friendlyNamePlaceholder: 't.ex. AMS Vänligt Namn',
+ serialNumber: 'Serienummer',
+ firmwareVersion: 'Firmware',
+ save: 'Spara',
+ clear: 'Rensa',
+ noEditPermission: 'Du har inte behörighet att döpa om AMS-enheter',
+ },
+ // Firmware modal
+ firmwareModal: {
+ title: 'Firmwareuppdatering',
+ titleUpToDate: 'Firmwareinformation',
+ currentVersion: 'Aktuell:',
+ latestVersion: 'Senaste:',
+ releaseNotes: 'Versionsinformation',
+ checkingPrereqs: 'Kontrollerar förutsättningar...',
+ sdCardReady: 'SD-kort redo. Klicka nedan för att ladda upp firmware.',
+ uploadedSuccess: 'Firmware uppladdat till SD-kort!',
+ applyInstructions: 'För att tillämpa uppdateringen på din skrivare:',
+ step1: 'På skrivarens pekskärm, gå till Inställningar ',
+ step2: 'Navigera till Firmware ',
+ step3: 'Välj Uppdatera från SD-kort ',
+ step4: 'Uppdateringen tar 10-20 minuter',
+ done: 'Klar',
+ starting: 'Startar...',
+ uploadFirmware: 'Ladda upp firmware',
+ uploadFailed: 'Kunde inte starta uppladdning: {{error}}',
+ uploadedToast: 'Firmware uppladdat! Trigga uppdatering från skrivarens skärm.',
+ availableVersions: 'Tillgängliga versioner',
+ usable: 'Användbar',
+ unavailable: 'Ej tillgänglig',
+ installed: 'Installerad',
+ newerBadge: 'nyare',
+ olderBadge: 'äldre',
+ currentBadge: 'aktuell',
+ },
+ accessCodePlaceholder: 'Lämna tomt för att behålla nuvarande',
+ // ROI editor
+ roi: {
+ title: 'Detekteringsområde (ROI)',
+ xStart: 'X start',
+ yStart: 'Y start',
+ width: 'Bredd',
+ height: 'Höjd',
+ instruction: 'Justera detekteringsområdet för att fokusera på byggplattan. Den gröna rutan i förhandsvisningen visar det aktuella området.',
+ },
+ developerModeWarning: 'Utvecklar-LAN-läge är inte aktiverat på: {{names}}. Vissa funktioner kommer kanske inte att fungera.',
+ howToEnable: 'Hur man aktiverar',
+ incompatibleFile: 'Denna fil var slicad för {{slicedFor}}, men denna skrivare är en {{printerModel}}',
+ dropNotPrintable: 'Endast .gcode- och .gcode.3mf-filer kan skrivas ut',
+ dropToPrint: 'Släpp för att skriva ut',
+ dropToQueue: 'Skrivare upptagen',
+ },
+
+ // Archives page
+ archives: {
+ media: {
+ title: 'Skrivarvideor',
+ download: 'Ladda ner skrivarvideor',
+ searching: 'Kollar skrivaren efter videor',
+ searchFailed: 'Kunde inte kolla efter videor.',
+ none: 'Ingen timelapse- eller IP-kamerabitar hittades för denna utskrift.',
+ attachedTimelapse: 'Bifogad timelapse',
+ printerFiles: 'Filer kvar på skrivaren',
+ downloadSelected: 'Nerladdning vald',
+ printerMissing: 'Skrivaren är inte längre konfigurerad',
+ timelapseUnavailable: 'Timelapse-mappen kunde inte läsas',
+ ipcamUnavailable: 'IP-kameramappen kunde inte läsas',
+ },
+ title: 'Utskriftsarkiv',
+ no3mfBanner: {
+ title: 'Vissa av de senaste utskrifterna kunde inte arkiveras med minibilder',
+ body: 'Slicer-programmet lämnade inte .gcode.3mf på skrivarens SD-kort så Bambuddy kunde inte hämta minibilden eller slicer-programmets metadata. Detta beror vanligtvis på att "Lagra skickade filer på extern lagring" är avstängt i slicer-programmet (Bambu Studio / OrcaSlicer).',
+ docsLink: 'Se installationssteg 4',
+ dismissLabel: 'Avfärda denna notis',
+ docsLinkInternalStorage: 'Varför detta händer',
+ titleInternalStorage: 'Vissa av de senaste utskrifterna blev kvar i skrivarens interna lagring',
+ bodyInternalStorage: 'Bambu Studio placerade den slicade filen på skrivarens interna lagring istället för på kortet, så det fanns ingenting för Bambuddy att läsa via FTP. På H2-serien och P2S gör dess Skriv ut-knapp alltid det — endast Skicka erbjuder ett val, och det förvalet är Cache också. Dessa utskrifter arkiveras ändå med sitt namn och tid, bara utan en minibild eller slicer-inställningar. För kompletta arkiv, starta utskriften från Bambuddy, eller slica i OrcaSlicer — eller i Bambu Studio, använd Skicka med Extern valt och starta utskriften därefter. Alla kräver ett kort eller en USB-minne i skrivaren.',
+ titleNoExternalStorage: 'Vissa av de senaste utskrifterna kunde inte arkiveras — ingen lagring i skrivaren',
+ bodyNoExternalStorage: 'Skrivaren rapporterar inget kort eller USB-minne i sitt fack, så den slicade filen hade ingenstans att hamna och Bambuddy hade ingenting att läsa. Sätt in ett och nästa utskrift kommer att arkiveras fullständigt.',
+ titleInternalHistory: 'Vissa av de senaste utskrifterna startades från en fil som redan fanns på skrivaren',
+ bodyInternalHistory: 'Dessa utskrifter kördes från skrivarens eget bibliotek — en omutskrift från dess skärm, en start från Handy, eller en fil som skickades tidigare och skrivits ut senare. Bambuddy läser utskriftsfiler via FTP, som endast betjänar kortet eller USB-minnet, medan skrivaren håller det biblioteket i ett område som FTP inte kan nå, så det fanns ingen 3MF att läsa. Ingen slicer-inställning ändrar detta, eftersom ingenting skickades för dessa utskrifter. De arkiveras ändå med sitt namn och tid, och Redigera arkiv låter dig fylla i det använda filamentet manuellt. För ett komplett arkiv, starta utskriften från Bambuddy eller från din slicer istället.',
+ titleFtpsCooloff: 'Vissa av de senaste utskrifterna kunde inte arkiveras – skrivaren vägrade filanslutningen',
+ bodyFtpsCooloff: 'Bambuddy öppnade skrivarens filöverföringsport (FTPS 990) och skrivaren svarade med något som inte är TLS, så inget kunde läsas från det. Dessa utskrifter arkiveras fortfarande med deras namn och tidpunkt, bara utan en miniatyrbild eller metadata för slicer. Det här är inte en slicer-inställning och inte något du har ändrat - samma modeller och firmware körs normalt på andra installationer, och en påverkad skrivare fungerar vanligtvis igen senare på egen hand. Efter ett sådant misslyckande pausar Bambuddy överföringarna till den skrivaren i fem minuter och kommer tillbaka för filen när pausen försvinner, så ett kort avsnitt fylls av sig själv; ett kort som fortfarande är tomt betyder att vägran var längre än ett nytt försök. Vad som utlöser det är ännu inte känt. Om det fortsätter att hända, aktivera felsökningsloggning på systemsidan och bifoga ett supportpaket till din rapport.',
+ titleFtpTransferFailed: 'Vissa av de senaste utskrifterna kunde inte arkiveras – filöverföringen fick slut på tid',
+ bodyFtpTransferFailed: 'Den slicade filen fanns på skrivarens minneskort och skrivaren serverade den, men överföringen slutfördes inte inom den tillåtna tiden. Vid utskriftsstart hanterar skrivaren också sin kamera, dess statusmeddelanden och uppladdningen av jobb, och en stor 3MF kommer inte alltid igenom allt detta. Dessa utskrifter arkiveras fortfarande med deras namn och timing, bara utan en miniatyrbild eller slicer-metadata. Det här är inte en slicer-inställning och inte något du har ändrat. Bambuddy kommer tillbaka för filen tre gånger under de följande tio minuterna, så de flesta av dessa fyller i sig själva; ett minneskort som fortfarande är tomt betyder att varje försök också fick slut på tid. Om det fortsätter att hända, höj anslutningstidsgränsen under Inställningar > Nätverk > FTP-försök igen.',
+ },
+ searchPlaceholder: 'Sök i arkiv...',
+ filterByPrinter: 'Filtrera efter skrivare',
+ filterByStatus: 'Filtrera efter status',
+ sortBy: 'Sortera efter',
+ sortNewest: 'Nyaste först',
+ sortOldest: 'Äldsta först',
+ sortName: 'Namn',
+ sortDuration: 'Varaktighet',
+ sortLargest: 'Största först',
+ sortSmallest: 'Minsta först',
+ sortSize: 'Storlek',
+ noArchives: 'Inga arkiv hittade',
+ noArchivesSearch: 'Inga arkiv matchar din sökning',
+ originalPrintNotVisible: 'Originalutskriften syns inte – prova att nollställa filter',
+ noArchivesYet: 'Inga arkiv än',
+ prints: 'utskrifter',
+ pagination: {
+ showing: 'Visar',
+ to: 'till',
+ of: 'av',
+ show: 'Visa',
+ page: 'Sida',
+ all: 'Alla',
+ },
+ loadingArchives: 'Laddar arkiv...',
+ releaseToUpload: 'Släpp för att ladda upp',
+ showAll: 'Visa alla',
+ showFavoritesOnly: 'Visa endast favoriter',
+ gridView: 'Rutnätsvy',
+ listView: 'Listvy',
+ calendarView: 'Kalendervy',
+ logView: 'Utskriftslogg',
+ manageTags: 'Hantera taggar',
+ showFailedPrints: 'Visa misslyckade utskrifter',
+ hideFailedPrints: 'Dölj misslyckade utskrifter',
+ hideDuplicates: 'Dölj dubbletter',
+ viewOriginalPrint: 'Klicka för att visa originalutskrift (#{{id}})',
+ printTime: 'Utskriftstid',
+ filamentUsed: 'Filament använt',
+ cost: 'Kostnad',
+ preview: 'Förhandsgranskning',
+ deleteArchive: 'Ta bort arkiv',
+ deleteConfirm: 'Är du säker på att du vill ta bort detta arkiv?',
+ favorite: 'Favorit',
+ unfavorite: 'Ta bort från favoriter',
+ viewDetails: 'Visa detaljer',
+ status: {
+ completed: 'Slutförd',
+ failed: 'Misslyckad',
+ stopped: 'Stoppad',
+ },
+ toast: {
+ source3mfAttached: '3MF-källfil bifogad: {{filename}}',
+ failedUploadSource3mf: 'Kunde inte ladda upp 3MF-källfil',
+ source3mfRemoved: '3MF-källfil borttagen',
+ failedRemoveSource3mf: 'Kunde inte ta bort 3MF-källfil',
+ f3dAttached: 'F3D bifogad: {{filename}}',
+ failedUploadF3d: 'Kunde inte ladda upp F3D',
+ f3dRemoved: 'F3D borttagen',
+ failedRemoveF3d: 'Kunde inte ta bort F3D',
+ timelapseAttached: 'Timelapse bifogad: {{filename}}',
+ timelapseAlreadyAttached: 'Timelapse redan bifogad',
+ noMatchingTimelapse: 'Ingen matchande timelapse hittad',
+ failedScanTimelapse: 'Kunde inte söka efter timelapse',
+ failedAttachTimelapse: 'Kunde inte bifoga timelapse',
+ timelapseRemoved: 'Timelapse borttagen',
+ failedRemoveTimelapse: 'Kunde inte ta bort timelapse',
+ timelapseUploaded: 'Timelapse uppladdad: {{filename}}',
+ failedUploadTimelapse: 'Kunde inte ladda upp timelapse',
+ archiveDeleted: 'Arkiv borttaget',
+ failedDeleteArchive: 'Kunde inte ta bort arkiv',
+ addedToFavorites: 'Tillagd i favoriter',
+ removedFromFavorites: 'Borttagen från favoriter',
+ projectUpdated: 'Projekt uppdaterat',
+ failedUpdateProject: 'Kunde inte uppdatera projekt',
+ linkCopied: 'Länk kopierad till urklipp',
+ failedCopyLink: 'Kunde inte kopiera länk',
+ photoDeleted: 'Foto borttaget',
+ failedDeletePhoto: 'Kunde inte ta bort foto',
+ failedDeleteArchives: 'Kunde inte ta bort arkiv',
+ failedUpdateFavorites: 'Kunde inte uppdatera favoriter',
+ exportDownloaded: 'Nedladdning exporterad',
+ exportFailed: 'Export misslyckades',
+ },
+ menu: {
+ print: 'Skriv ut',
+ openInBambuStudio: 'Öppna i slicer-program',
+ slice: 'Slica',
+ externalLink: 'Extern länk',
+ viewOnMakerWorld: 'Visa på MakerWorld',
+ preview3d: '3D-förhandsgranskning',
+ viewTimelapse: 'Visa timelapse',
+ scanForTimelapse: 'Sök efter timelapse',
+ uploadTimelapse: 'Ladda upp timelapse',
+ removeTimelapse: 'Ta bort timelapse',
+ downloadSource3mf: 'Ladda ner 3MF-källfil',
+ uploadSource3mf: 'Ladda upp 3MF-källfil',
+ replaceSource3mf: 'Ersätt 3MF-källfil',
+ removeSource3mf: 'Ta bort 3MF-källfil',
+ uploadF3d: 'Ladda upp F3D',
+ replaceF3d: 'Ersätt F3D',
+ downloadF3d: 'Ladda ner F3D',
+ removeF3d: 'Ta bort F3D',
+ download: 'Ladda ner',
+ copyDownloadLink: 'Kopiera nedladdningslänk',
+ qrCode: 'QR-kod',
+ viewPhotos: 'Visa bilder',
+ viewPhotosCount: 'Visa bilder ({{count}})',
+ projectPage: 'Projektsida',
+ addToFavorites: 'Lägg till i favoriter',
+ removeFromFavorites: 'Ta bort från favoriter',
+ edit: 'Redigera',
+ printLog: 'Utskriftslogg',
+ goToProject: 'Gå till projekt: {{name}}',
+ addToProject: 'Lägg till i projekt',
+ removeFromProject: 'Ta bort från projekt',
+ loading: 'Laddar...',
+ noProjectsAvailable: 'Inga projekt tillgängliga',
+ searchProjects: 'Sök projekt...',
+ select: 'Välj',
+ deselect: 'Avmarkera',
+ delete: 'Ta bort',
+ },
+ permission: {
+ noReprint: 'Du har inte behörighet att skriva ut detta arkiv igen',
+ noAddToQueue: 'Du har inte behörighet att lägga till i kö',
+ noUpdateArchives: 'Du har inte behörighet att uppdatera arkiv',
+ noUploadFiles: 'Du har inte behörighet att ladda upp filer',
+ noDownload: 'Du har inte behörighet att ladda ner arkiv',
+ noCopyLink: 'Du har inte behörighet att kopiera nedladdningslänkar',
+ noDelete: 'Du har inte behörighet att ta bort detta arkiv',
+ noEdit: 'Du har inte behörighet att redigera denna post',
+ noCreate: 'Du har inte behörighet att skapa arkiv',
+ },
+ platePicker: {
+ title: 'Välj platta för förhandsgranskning',
+ hint: 'Detta arkiv har flera plattor. Välj en för att öppna i GCode-visaren.',
+ plateLabel: 'Platta {{index}}',
+ objectCount: '{{count}} objekt',
+ objectCount_plural: '{{count}} objekt',
+ noGcode: 'Detta arkiv har ingen slicad G-kod för förhandsgranskning. Öppna det i Bambu Studio för att slica först.',
+ },
+ card: {
+ previousPlate: 'Föregående platta',
+ nextPlate: 'Nästa platta',
+ plateNumber: 'Platta {{index}}',
+ moreOptions: 'Högerklicka för fler alternativ',
+ addToFavorites: 'Lägg till i favoriter',
+ removeFromFavorites: 'Ta bort från favoriter',
+ cancelled: 'avbruten',
+ failed: 'misslyckad',
+ duplicate: 'kopia',
+ duplicateTitle: 'Denna modell har skrivits ut tidigare',
+ openSource3mf: 'Öppna 3MF-källfil i Bambu Studio (högerklicka för fler alternativ)',
+ downloadF3d: 'Ladda ner Fusion 360-designfil',
+ viewTimelapse: 'Visa timelapse',
+ viewPhoto: 'Visa 1 foto',
+ viewPhotos: 'Visa {{count}} foton',
+ openFolder: 'Öppna mapp: {{name}}',
+ slicedFile: 'Slicad fil – redo att skriva ut',
+ sourceFile: 'Endast källfil – ingen AMS-mappning tillgänglig',
+ gcode: 'GCODE',
+ source: 'KÄLLA',
+ project: 'Projekt: {{name}}',
+ runsBadge: '{{count}} utskrifter',
+ runsBadgeTitle: '{{count}} utskrifter totalt — {{successful}} lyckade, {{failed}} misslyckade. Klicka för att se hela utskriftsloggen.',
+ estimated: 'Uppskattad: {{time}}',
+ actual: 'Faktisk: {{time}}',
+ accuracy: 'Noggrannhet: {{percent}}%',
+ filament: '{{weight}}g',
+ layer: '{{count}} skikt',
+ layers: '{{count}} skikt',
+ object: '{{count}} objekt',
+ objects: '{{count}} objekt',
+ slicedFor: 'Slicad för {{model}}',
+ uploadedBy: 'Uppladdad av',
+ noPermissionReprint: 'Du har inte behörighet att skriva ut igen',
+ noFileForReprint: 'Ingen 3MF-fil tillgänglig — filen kunde inte laddas ner från skrivaren när utskriften registrerades',
+ slicerAmsMapping: 'AMS-mappning sparad ({{printer}})',
+ slicerAmsMappingTooltip: 'Slicer-programmets eget val av AMS-fack sparades för {{printer}}. Bricknummer betyder endast något på den skrivaren, så en omutskrift återanvänder dem endast när den riktas till {{printer}} igen.',
+ noPermissionEdit: 'Du har inte behörighet att redigera arkiv',
+ noPermissionDelete: 'Du har inte behörighet att ta bort arkiv',
+ openInBambuStudio: 'Öppna i slicer-program',
+ openInBambuStudioToSlice: 'Öppna i slicer-program för att slica',
+ slice: 'Slica',
+ externalLink: 'Extern länk',
+ makerWorld: 'MakerWorld: {{designer}}',
+ viewProject: 'Visa projekt',
+ noExternalLink: 'Ingen extern länk',
+ preview3d: '3D-förhandsgranskning',
+ download: 'Ladda ner',
+ edit: 'Redigera',
+ delete: 'Ta bort',
+ },
+ runLog: {
+ title: 'Utskriftslogg',
+ modalTitle: 'Utskriftslogg — {{name}}',
+ modalTitleFallback: 'detta arkiv',
+ empty: 'Inga utskriftshändelser registrerade för detta arkiv än.',
+ col: {
+ date: 'Datum',
+ status: 'Status',
+ duration: 'Varaktighet',
+ filament: 'Filament',
+ cost: 'Kostnad',
+ },
+ status: {
+ completed: 'Slutförd',
+ failed: 'Misslyckad',
+ cancelled: 'Avbruten',
+ stopped: 'Stoppad',
+ skipped: 'Hoppad över',
+ printing: 'Skriver ut',
+ },
+ },
+ modal: {
+ deleteArchive: 'Ta bort arkiv',
+ deleteConfirm: 'Är du säker på att du vill ta bort "{{name}}"? Denna åtgärd kan inte ångras.',
+ deleteButton: 'Ta bort',
+ deletePurgeStats: 'Ta också bort denna utskrift från Snabbstatistik (filament, tid, kostnad, energi)',
+ deleteQueueItemsWarning: '{{count}} köade artiklar kopplade till detta arkiv kommer också att tas bort.',
+ deleteBlockedByPrinting: 'Kan inte ta bort — {{count}} köade artiklar skrivs för närvarande ut. Stoppa utskriften först och försök sedan igen.',
+ removeSource3mf: 'Ta bort 3MF-källfil',
+ removeSource3mfConfirm: 'Är du säker på att du vill ta bort 3MF-källfilen från "{{name}}"? Detta kommer att ta bort originalfilen från slicer-programmet.',
+ removeButton: 'Ta bort',
+ removeF3d: 'Ta bort F3D',
+ removeF3dConfirm: 'Är du säker på att du vill ta bort Fusion 360-designfilen från "{{name}}"?',
+ removeTimelapse: 'Ta bort timelapse',
+ removeTimelapseConfirm: 'Är du säker på att du vill ta bort timelapse-videon från "{{name}}"?',
+ timelapse: '{{name}} — Timelapse',
+ selectTimelapse: 'Välj timelapse',
+ selectTimelapseDesc: 'Ingen automatisk matchning hittad. Välj timelapse för denna utskrift:',
+ deleteArchives: 'Ta bort arkiv',
+ deleteArchivesConfirm: 'Är du säker på att du vill ta bort {{count}} arkiv? Denna åtgärd kan inte ångras.',
+ deleteCount: 'Ta bort {{count}}',
+ },
+ page: {
+ title: 'Arkiv',
+ printsCount: '{{filtered}} av {{total}} utskrifter',
+ dropFilesHere: 'Släpp .3mf-filer här',
+ releaseToUpload: 'Släpp för att ladda upp',
+ only3mfSupported: 'Endast .3mf-filer stöds',
+ close: 'Stäng',
+ selected: '{{count}} valda',
+ selectAll: 'Välj alla',
+ tags: 'Taggar',
+ project: 'Projekt',
+ favorite: 'Favorit',
+ delete: 'Ta bort',
+ toggledFavorites: 'Växlade favoriter för {{count}} arkiv',
+ failedUpdateFavorites: 'Kunde inte uppdatera favoriter',
+ archivesDeleted: '{{count}} arkiv borttagna',
+ failedDeleteArchives: 'Kunde inte ta bort arkiv',
+ photoDeleted: 'Foto borttaget',
+ failedDeletePhoto: 'Kunde inte ta bort foto',
+ },
+ list: {
+ name: 'Namn',
+ printer: 'Skrivare',
+ date: 'Datum',
+ size: 'Storlek',
+ actions: 'Åtgärder',
+ hasTimelapse: 'Har timelapse',
+ },
+ log: {
+ date: 'Datum',
+ printName: 'Utskriftsnamn',
+ printer: 'Skrivare',
+ user: 'Användare',
+ status: 'Status',
+ duration: 'Varaktighet',
+ filament: 'Filament',
+ filamentUsed: 'Filament använt',
+ cost: 'Kostnad',
+ energy: 'Energi',
+ energyCost: 'Energikostnad',
+ completedAt: 'Slutförd',
+ columns: 'Kolumner',
+ sortBy: 'Sortera efter {{column}}',
+ allPrinters: 'Alla skrivare',
+ allUsers: 'Alla användare',
+ allStatuses: 'Alla statusar',
+ cancelled: 'Avbruten',
+ skipped: 'Hoppad över',
+ dateFrom: 'Från',
+ dateTo: 'Till',
+ noEntries: 'Inga utskriftsloggposter hittade',
+ showing: 'Visar {{count}} av {{total}} poster',
+ rowsPerPage: 'Rader',
+ page: 'Sida',
+ prev: 'Föregående',
+ next: 'Nästa',
+ clearLog: 'Rensa logg',
+ clearLogTitle: 'Rensa utskriftslogg',
+ clearLogConfirm: 'Alla utskriftsloggposter kommer att tas bort permanent. Arkiv och köposter påverkas inte. Denna åtgärd kan inte ångras. Är du säker?',
+ clearLogButton: 'Rensa alla',
+ cleared: '{{count}} loggposter rensade',
+ clearFailed: 'Kunde inte rensa utskriftslogg',
+ deleteEntryTitle: 'Ta bort utskriftsloggpost',
+ deleteEntryConfirm: 'Denna post kommer att tas bort från loggen och dess bidrag till filament, tid och kostnad kommer att tas bort från Snabbstatistik. Matchande arkiv (om något) påverkas inte. Denna åtgärd kan inte ångras.',
+ entryDeleted: 'Utskriftsloggpost borttagen',
+ entryDeleteFailed: 'Kunde inte ta bort utskriftsloggpost',
+ editEntryTitle: 'Redigera utskriftsloggpost',
+ editEntryDescription: 'Klassificera denna utskrift. Widgeten för felanalys grupperar efter dessa värden, så uppdateringar flödar omedelbart till statistiken.',
+ entryUpdated: 'Utskriftsloggpost uppdaterad',
+ entryUpdateFailed: 'Kunde inte uppdatera utskriftsloggpost',
+ statuses: {
+ completed: 'Slutförd',
+ failed: 'Misslyckad',
+ stopped: 'Stoppad',
+ cancelled: 'Avbruten',
+ skipped: 'Hoppad över',
+ },
+ },
+ },
+
+ // Sticky upload-progress toast (#1625 follow-up — restored the
+ // legacy bg-dispatch toast UI for the scheduler-driven dispatch path).
+ // Strings mirror 0b43ac0d's backgroundDispatch namespace.
+ dispatchToast: {
+ untitled: 'Utskriftsjobb',
+ startingPrints: 'Startar utskrifter',
+ progressSummary: '{{complete}}/{{total}} klara • Bearbetar: {{processing}}',
+ expandDetails: 'Visa detaljer för utskicksinformation',
+ collapseDetails: 'Dölj detaljer för utskicksinformation',
+ awaitingPrinter: 'Väntar på skrivare…',
+ status: {
+ processing: 'Bearbetar',
+ completed: 'Slutförd',
+ failed: 'Misslyckad',
+ },
+ failed: {
+ generic: 'Utskick misslyckades',
+ upload_failed: 'Kunde inte ladda upp till skrivare',
+ start_command_failed: 'Skrivaren avvisade startkommandot',
+ },
+ dismiss: 'Avfärda',
+ },
+
+ // Pipeline Runs dashboard (#1425 PR C). Lists every Slicer Pipeline run
+ // across every pipeline with status + pipeline filters and pagination.
+ // Each row expands to per-copy job status; partial-failure runs get a
+ // Retry-failed button; in-flight runs get a Cancel button.
+ pipelineRuns: {
+ title: 'Pipeline-körningar',
+ loading: 'Laddar…',
+ empty: 'Inga pipeline-körningar än.',
+ filter: {
+ pipeline: 'Pipeline',
+ status: 'Status',
+ target: 'Mål',
+ all: 'Alla',
+ allPipelines: 'Alla pipelines',
+ allStatus: 'Alla statusar',
+ allTargets: 'Alla mål',
+ clear: 'Rensa filter',
+ noMatches: 'Inga körningar matchar de aktuella filtren.',
+ },
+ totalCount_one: '{{n}} körning',
+ totalCount_other: '{{n}} körningar',
+ copies: '{{n}} kopior',
+ failedCount: '{{n}} misslyckade',
+ copyN: 'Kopiera {{n}}',
+ retryFailed: 'Kunde inte försöka igen',
+ retryOf: 'omförsök av #{{n}}',
+ pagination: '{{start}}–{{end}} av {{total}}',
+ cancelledByUser: 'Avbruten av användare',
+ toast: {
+ cancelled: 'Körning avbruten',
+ cancelFailed: 'Kunde inte avbryta',
+ retryStarted: 'Omförsök påbörjat',
+ retryFailed: 'Kunde inte försöka igen',
+ cleared: '{{n}} körningar rensade',
+ clearFailed: 'Kunde inte rensa',
+ },
+ clearLog: 'Rensa logg',
+ clearConfirmTitle: 'Rensa logg?',
+ clearConfirmBody: 'Ta bort alla slutförda, misslyckade, avbrutna och delvis misslyckade pipeline-körningar? Pågående körningar behålls. Denna åtgärd kan inte ångras.',
+ clearConfirmAction: 'Rensa',
+ jobStatus: {
+ pending: 'väntande',
+ awaiting_printer: 'väntar på skrivare',
+ queued: 'i kö',
+ printing: 'skriver ut',
+ completed: 'slutförd',
+ failed: 'misslyckad',
+ cancelled: 'avbruten',
+ },
+ },
+
+ // Queue page
+ queue: {
+ title: 'Utskriftskö',
+ subtitle: 'Schemalägg och hantera dina utskriftsjobb',
+ filamentShort: {
+ rowBadge: 'Otillräckligt filament för den tilldelade rullen',
+ rowTooltip: 'Utskicksschemaläggaren markerade denna post. Klicka på Spela för att se underskottet per fack och bestämma om du vill skriva ut ändå.',
+ confirmTitle: 'Otillräckligt filament',
+ confirmIntro: 'Den tilldelade rullen kan inte tillfredsställa åtminstone ett fack. Skriv ut ändå?',
+ lineItem: 'Fack {{slot}}: behöver {{required}} g, {{remaining}} g kvar',
+ unknown: 'okänd',
+ printAnyway: 'Skriv ut ändå',
+ },
+ slicerAmsMapping: {
+ rowBadge: 'AMS-fack sparade för denna skrivare',
+ rowTooltip: 'Detta arkiv innehåller de exakta AMS-fack som slicer-programmet valde, sparade för den skrivare denna post riktar sig till. En omutskrift på den kan återanvända dessa brickor istället för att matcha igen efter typ och färg.',
+ },
+ // Print modal
+ editQueueItem: 'Redigera köad artikel',
+ selectAllPlates: 'Markera alla {{count}} plattor',
+ deselectAll: 'Avmarkera alla',
+ printQueued: 'Utskrift köad',
+ printQueuedWillStartWhenIdle: 'Kommer att starta när skrivaren är inaktiv',
+ itemsQueued: '{{count}} poster i kö',
+ sending: 'Skickar...',
+ sendingProgress: 'Skickar {{current}}/{{total}}...',
+ adding: 'Lägg till...',
+ addingProgress: 'Lägger till {{current}}/{{total}}...',
+ savingProgress: 'Sparar {{current}}/{{total}}...',
+ clearQueue: 'Rensa kö',
+ clearHistory: 'Rensa historik',
+ emptyQueue: 'Kön är tom',
+ position: 'Position',
+ scheduledTime: 'Schemalagd tid',
+ moveUp: 'Flytta upp',
+ moveDown: 'Flytta ner',
+ startNow: 'Starta nu',
+ printingInProgress: 'Skriver ut...',
+ viewArchive: 'Visa arkiv',
+ viewInFileManager: 'Visa i Filhanteraren',
+ itemCount: '{{count}} artikel',
+ itemCount_plural: '{{count}} artiklar',
+ dragToReorder: 'Dra för att ordna om (endast ASAP)',
+ reorderHint: 'Position påverkar endast ASAP-poster. Schemalagda poster körs vid sin inställda tid.',
+ sjf: {
+ label: 'SJF',
+ tooltip: 'Kortast jobb först — schemaläggaren prioriterar korta utskrifter',
+ },
+ addedBy: 'Tillagd av {{name}}',
+ nextInQueue: 'Nästa i kön',
+ clearPlateSuccess: 'Platta rensad — redo för nästa utskrift',
+ plateNumber: 'Platta {{index}}',
+ // Batch / quantity
+ quantity: 'Kvantitet',
+ quantityHint: 'Skapar {{count}} köade artiklar',
+ plateQuantityLabel: 'Körningar av {{plate}}',
+ plateQuantityTotal_one: '{{count}} körning totalt',
+ plateQuantityTotal_other: '{{count}} körningar totalt',
+ batchOrders: {
+ title: 'Batchordrar',
+ emptyTitle: 'Inga batchordrar',
+ emptyDescription: 'Köa en fil med flera plattor, eller mer än en kopia av en platta, så visas ordern här med dess framsteg.',
+ filter: {
+ active: 'Aktiv',
+ completed: 'Slutförd',
+ cancelled: 'Avbruten',
+ all: 'Alla',
+ },
+ status: {
+ active: 'Aktiv',
+ completed: 'Slutförd',
+ cancelled: 'Avbruten',
+ },
+ noTargets: 'Endast gruppering',
+ noTargetsHint: 'Skapad innan per-platta-mål existerade, så den spårar de körningar den köade istället för ett mål.',
+ dispatchRemaining: 'Kö {{count}} återstående',
+ dispatchPlate: 'Kö återstående',
+ strandedPlate: 'Inget kvar att kopiera inställningar från',
+ strandedNotice: 'Kan inte köa {{runs}} av {{owed}} körningar som fortfarande är i skuld: den plattan har ingen kö eller färdig körning kvar att kopiera inställningar från. Ställ plattan i kö en gång från filen, eller avbryt beställningen.',
+ dispatched: 'Köade de återstående körningarna för {{name}}',
+ plateProgress: '{{completed}} av {{target}} klara',
+ remaining: '{{count}} återstående',
+ failed: '{{count}} misslyckade',
+ printing: '{{count}} skriver ut',
+ wholeFile: 'Hela filen',
+ due: 'Förfaller {{date}}',
+ costSoFar: '{{amount}} hittills',
+ costRemaining: '({{amount}} kvar)',
+ },
+ activeBatches: 'Aktiva batcher',
+ batchProgress: '{{completed}} av {{total}} slutförda',
+ cancelBatch: 'Avbryt återstående',
+ batchCancelled: 'Återstående batchartiklar avbrutna',
+ cancelBatchConfirmTitle: 'Avbryt batch',
+ cancelBatchConfirmMessage: 'Avbryt alla återstående väntande artiklar i denna batch?',
+ batch: {
+ defaultName: 'Batch',
+ label: '{{count}} artikel',
+ label_plural: '{{count}} artiklar',
+ pendingCount: '{{count}} väntande',
+ pendingCount_plural: '{{count}} väntande',
+ expand: 'Expandera batch',
+ collapse: 'Minimera batch',
+ groupAsBatch: 'Gruppera som batch...',
+ groupAsBatchDescription: 'Kombinera de {{count}} markerade posterna till en enda hopfällbar batch.',
+ nameLabel: 'Batchnamn',
+ namePlaceholder: 't.ex. Fredagspresenter',
+ create: 'Skapa batch',
+ ungroup: 'Avgruppera',
+ ungroupConfirmTitle: 'Avgruppera batch?',
+ ungroupConfirmMessage: 'Posterna kommer att förbli i kön men kommer inte längre att grupperas ihop.',
+ dragGroup: 'Dra grupp',
+ },
+ // Tabs
+ tabs: {
+ batches: 'Batchar',
+ queue: 'Kö',
+ history: 'Historik',
+ timeline: 'Tidslinje',
+ pipelines: 'Pipelines',
+ },
+ // Layout toggle on the Queue tab — distinct from the sort dropdown
+ // (those control order; these control whether items render as one flat
+ // list or grouped under per-printer section headers).
+ layout: {
+ flatList: 'Lista',
+ byPrinter: 'Efter skrivare',
+ groupByPrinter: 'Gruppera efter skrivare',
+ },
+ // History tab empty state
+ history: {
+ emptyTitle: 'Ingen historik än',
+ emptyDescription: 'Slutförda, avbrutna och misslyckade utskrifter kommer att visas här.',
+ showMore: 'Visa mer',
+ showingCount: 'Visar {{shown}} av {{total}}',
+ },
+ // Drag ghost label when multi-dragging
+ dragGhost: {
+ multiCount: '{{count}} poster',
+ batch: '{{name}} ({{count}} kopia)',
+ batch_plural: '{{name}} ({{count}} kopior)',
+ },
+ // Sections
+ sections: {
+ currentlyPrinting: 'Skriver ut nu',
+ queued: 'I kö',
+ history: 'Historik',
+ },
+ // Status
+ status: {
+ pending: 'Väntande',
+ waiting: 'Väntar',
+ printing: 'Skriver ut',
+ paused: 'Pausad',
+ completed: 'Slutförd',
+ failed: 'Misslyckad',
+ skipped: 'Hoppad över',
+ cancelled: 'Avbruten',
+ },
+ // Summary cards
+ summary: {
+ printing: 'Skriver ut',
+ queued: 'I kö',
+ totalTime: 'Total kötid',
+ totalWeight: 'Total kövikt',
+ history: 'Historik',
+ },
+ // Filters
+ filter: {
+ allPrinters: 'Alla skrivare',
+ unassigned: 'Otilldelad',
+ allStatus: 'Alla statusar',
+ allLocations: 'Alla platser',
+ any: 'Valfri',
+ },
+ // Sort
+ sort: {
+ byPosition: 'Sortera efter position',
+ byName: 'Sortera efter namn',
+ byPrinter: 'Sortera efter skrivare',
+ bySchedule: 'Sortera efter schema',
+ byDate: 'Sortera efter datum',
+ ascendingOldest: 'Stigande (äldst först)',
+ descendingNewest: 'Fallande (nyast först)',
+ },
+ // Badges
+ badges: {
+ staged: 'Förberedd',
+ requiresPrevious: 'Kräver tidigare framgång',
+ autoPowerOff: 'Auto-avstängning',
+ gcodeInjection: 'G-kod',
+ },
+ // Empty state
+ empty: {
+ title: 'Inga utskrifter schemalagda',
+ description: 'Schemalägg en utskrift från Arkiv-sidan med hjälp av "Schemalägg"-alternativet i kontextmenyn, eller dra och släpp filer för att komma igång.',
+ },
+ // Time
+ time: {
+ etaIfStartedNow: 'Färdigställningstid om detta jobb startar nu',
+ whenFree: 'När en skrivare är ledig',
+ overdue: 'Försenad',
+ now: 'Nu',
+ lessThanMinute: 'Om mindre än en minut',
+ inMinutes: 'Om {{count}} min',
+ inHours: 'Om {{count}} timmar',
+ },
+ // Actions
+ actions: {
+ startPrint: 'Starta utskrift',
+ stopPrint: 'Stoppa utskrift',
+ requeue: 'Lägg tillbaka i kö',
+ },
+ // Bulk edit
+ bulkEdit: {
+ title: 'Redigera {{count}} post',
+ title_plural: 'Redigera {{count}} poster',
+ description: 'Endast ändrade inställningar kommer att tillämpas på valda poster.',
+ printer: 'Skrivare',
+ noChange: '— Ingen ändring —',
+ queueOptions: 'Köalternativ',
+ staged: 'Förberedd (manuell start)',
+ autoPowerOff: 'Auto-avstängning efter utskrift',
+ requirePrevious: 'Kräv tidigare framgång',
+ gcodeInjection: 'Injicera G-kod',
+ printOptions: 'Utskriftsalternativ',
+ bedLevelling: 'Byggplattsnivåinställning',
+ flowCalibration: 'Flödeskalibrering',
+ vibrationCalibration: 'Vibrationskalibrering',
+ layerInspection: 'Förstaskiktsinspektion',
+ timelapse: 'Timelapse',
+ useAms: 'Använd AMS',
+ nozzleOffsetCali: 'Munstyckesoffsetskalibrering',
+ applyChanges: 'Tillämpa ändringar',
+ selectAll: 'Markera alla',
+ deselectAll: 'Avmarkera alla',
+ selected: '{{count}} valda',
+ editSelected: 'Redigera valda',
+ cancelSelected: 'Avbryt valda',
+ },
+ // Confirmations
+ confirm: {
+ cancelTitle: 'Avbryt schemalagd utskrift',
+ cancelMessage: 'Är du säker på att du vill avbryta "{{name}}"?',
+ stopTitle: 'Stoppa utskrift',
+ stopMessage: 'Är du säker på att du vill stoppa den aktuella utskriften "{{name}}"? Detta kommer att avbryta utskriftsjobbet på skrivaren.',
+ removeTitle: 'Ta bort från historik',
+ removeMessage: 'Är du säker på att du vill ta bort "{{name}}" från köhistoriken?',
+ clearHistoryTitle: 'Rensa historik',
+ clearHistoryMessage: 'Är du säker på att du vill ta bort alla {{count}} poster från historiken?',
+ cancelButton: 'Avbryt utskrift',
+ stopButton: 'Stoppa utskrift',
+ thisPrint: 'denna utskrift',
+ thisItem: 'denna post',
+ },
+ // Toast messages
+ toast: {
+ cancelled: 'Köpost avbruten',
+ cancelFailed: 'Kunde inte avbryta post',
+ removed: 'Köpost borttagen',
+ keptForOrder: 'Avbruten istället för borttagen: detta är den enda körningen som dess batchorder kan återställa den här plattan från',
+ historyKeptForOrders: 'Behöll {{kept}} som batchordrar fortfarande behöver.',
+ removeFailed: 'Kunde inte ta bort post',
+ stopped: 'Utskrift stoppad',
+ stopFailed: 'Kunde inte stoppa utskrift',
+ released: 'Utskrift släppt till kö',
+ startFailed: 'Kunde inte starta utskrift',
+ reorderFailed: 'Kunde inte ordna om kö',
+ historyCleared: 'Rensade {{count}} historikpost(er)',
+ clearHistoryFailed: 'Kunde inte rensa historik',
+ updateFailed: 'Kunde inte uppdatera poster',
+ bulkCancelled: 'Avbröt {{count}} post(er)',
+ bulkCancelFailed: 'Kunde inte avbryta poster',
+ batchCreated: 'Batch "{{name}}" skapad',
+ batchCreateFailed: 'Kunde inte skapa batch',
+ batchUngrouped: 'Avgrupperade {{count}} post(er)',
+ batchUngroupFailed: 'Kunde inte avgruppera batch',
+ resumedAfterFailure: 'Återupptog kö — {{restored}} jobb återställda till väntande',
+ resumeAfterFailureFailed: 'Kunde inte återuppta kö',
+ },
+ resumeAfterFailure: {
+ banner: '{{printer}} är blockerad av ett tidigare utskriftsfel — {{count}} jobb hoppades över',
+ bannerHint: 'Åtgärda skrivarproblemet, sedan återuppta för att återställa de hoppade jobben och rensa blockeringen.',
+ button: 'Återuppta efter fel',
+ confirmTitle: 'Återuppta kö efter fel?',
+ confirmMessage: 'Återställ {{count}} hoppade jobb på {{printer}} till väntande och rensa blockeringen för tidigare utskrift. Se till att skrivaren är redo innan du fortsätter.',
+ },
+ // Timeline view
+ timeline: {
+ listView: 'Lista',
+ timelineView: 'Tidslinje',
+ unassigned: 'Otilldelad',
+ noData: 'Inga schemalagda utskrifter för denna dag',
+ nothingCommitted: 'Inga fastställda scheman i detta fönster. Förberedda poster, väntande poster och ASAP-jobb på inaktiva skrivare visas inte — ange en schemalagd tid eller släpp en förberedd post för att se den här.',
+ allDoneBy: 'Alla utskrifter beräknas vara klara till {{time}}',
+ staged: 'Förberedd',
+ filterAll: 'Visa alla',
+ filterPrinting: 'Skriver ut',
+ filterQueued: 'I kö',
+ time: {
+ anyMoment: 'när som helst',
+ minutesLeft: '{{minutes}}m kvar',
+ hoursLeft: '{{hours}}h kvar',
+ hoursMinutesLeft: '{{hours}}h {{minutes}}m kvar',
+ },
+ day: {
+ previous: 'Föregående dag',
+ next: 'Nästa dag',
+ today: 'Idag',
+ },
+ // Rolling-24h Gantt window
+ window: {
+ back12h: 'Bakåt 12 timmar',
+ forward12h: 'Framåt 12 timmar',
+ now: 'Nu',
+ },
+ printerColumnHeader: 'Skrivare',
+ },
+ // Permissions
+ permissions: {
+ noStopPrint: 'Du har inte behörighet att stoppa utskrifter',
+ noStartPrint: 'Du har inte behörighet att starta utskrifter',
+ noEdit: 'Du har inte behörighet att redigera denna köpost',
+ noCancel: 'Du har inte behörighet att avbryta denna köpost',
+ noRequeue: 'Du har inte behörighet att lägga tillbaka poster i kön',
+ noRemove: 'Du har inte behörighet att ta bort denna köpost',
+ noClearHistory: 'Du har inte behörighet att rensa all historik',
+ noEditItems: 'Du har inte behörighet att redigera köposter',
+ noCancelItems: 'Du har inte behörighet att avbryta köposter',
+ },
+ },
+
+ // Statistics page
+ stats: {
+ title: 'Statistik',
+ subtitle: 'Dra widgetar för att omordna. Klicka på ögonikonen för att dölja.',
+ overview: 'Översikt',
+ totalPrints: 'Totala utskrifter',
+ successRate: 'Framgångsgrad',
+ totalPrintTime: 'Total utskriftstid',
+ printTime: 'Utskriftstid',
+ totalFilament: 'Totalt filament använt',
+ filamentUsed: 'Filament använt',
+ filamentCost: 'Filamentkostnad',
+ totalCost: 'Total kostnad',
+ energyUsed: 'Energi använt',
+ energyCost: 'Energikostnad',
+ energyWarmingUpTooltip: 'Energispårning samlar fortfarande in timvisa ögonblicksbilder. Datumintervallstotaler kommer att bli korrekta när åtminstone en ögonblicksbild existerar före det valda intervallet. Tidiga värden kan underskattas.',
+ averagePrintTime: 'Genomsnittlig utskriftstid',
+ printsPerDay: 'Utskrifter per dag',
+ byPrinter: 'Efter skrivare',
+ printsByPrinter: 'Utskrifter efter skrivare',
+ byMaterial: 'Efter material',
+ byMonth: 'Efter månad',
+ last7Days: 'Senaste 7 dagarna',
+ last30Days: 'Senaste 30 dagarna',
+ last90Days: 'Senaste 90 dagarna',
+ allTime: 'All tid',
+ // Widgets
+ quickStats: 'Snabbstatistik',
+ printActivity: 'Utskriftsaktivitet',
+ filamentTypes: 'Filamenttyper',
+ filamentTrends: 'Filamenttrender',
+ failureAnalysis: 'Felanalys',
+ timeAccuracy: 'Tidsnoggrannhet',
+ successful: 'Lyckade:',
+ failed: 'Misslyckade:',
+ cancelled: 'Avbrutna:',
+ perfectEstimate: '100% = perfekt uppskattning',
+ noTimeAccuracyData: 'Ingen tidsnoggrannhetsdata än',
+ noFilamentData: 'Ingen filamentdata tillgänglig',
+ noPrinterData: 'Ingen skrivardata tillgänglig',
+ noPrintData: 'Ingen utskriftsdata tillgänglig',
+ noPrintDataLast30Days: 'Ingen utskriftsdata de senaste 30 dagarna',
+ failureReasons: 'Orsaker till fel',
+ topFailureReasons: 'Vanligaste orsaker till fel',
+ failedPrintsCount: '{{failed}} / {{total}} utskrifter misslyckades',
+ lastWeekRate: 'Förra veckan: {{rate}}%',
+ // Actions
+ resetLayout: 'Återställ layout',
+ recalculateCosts: 'Omberäkna kostnader',
+ recalculateCostsHint: 'Omberäkna alla arkivkostnader med nuvarande filamentpriser',
+ exportStats: 'Exportera statistik',
+ exportAsCsv: 'Exportera som CSV',
+ exportAsExcel: 'Exportera som Excel',
+ hiddenCount: '{{count}} dolda',
+ // Toast
+ exportDownloaded: 'Export nedladdad',
+ exportFailed: 'Kunde inte exportera',
+ layoutReset: 'Layout återställd',
+ recalculatedCosts: 'Omberäknade kostnader för {{count}} arkiv',
+ recalculateFailed: 'Kunde inte omberäkna kostnader',
+ // Loading
+ loadingStats: 'Laddar statistik...',
+ // Permissions
+ noPermissionResetLayout: 'Du har inte behörighet att återställa layout',
+ noPermissionRecalculate: 'Du har inte behörighet att omberäkna kostnader',
+ noPrintDataInRange: 'Ingen utskriftsdata i det valda intervallet',
+ periodFilament: 'Periodens filament',
+ periodCost: 'Periodens kostnad',
+ avgPerPrint: 'Genomsnitt per utskrift',
+ usageOverTime: 'Användning över tid',
+ energyOverTime: 'Energi över tid',
+ filamentByWeight: 'Vikt',
+ printDuration: 'Utskriftsvaraktighet',
+ printerUtilization: 'Skrivaranvändning',
+ filamentSuccess: 'Framgång efter material',
+ printHabits: 'Utskriftsvanor',
+ printTimeOfDay: 'Utskriftstid på dagen',
+ colorDistribution: 'Färgsfördelning',
+ noColorData: 'Ingen färgdata tillgänglig',
+ records: 'Rekord',
+ longestPrint: 'Längsta utskrift',
+ heaviestPrint: 'Tungsta utskrift',
+ mostExpensivePrint: 'Dyraste utskrift',
+ busiestDay: 'Mest aktiva dagen',
+ successStreak: 'Framgångssvit',
+ streakPrint: 'utskrift i rad',
+ streakPrints: '{{count}} utskrifter i rad',
+ printerStats: 'Skrivarstatistik',
+ hours: 'timmar',
+ avgPrints: 'Genomsnittliga utskrifter',
+ noArchiveData: 'Ingen utskriftsdata tillgänglig',
+ filamentByTime: 'Tid',
+ avgWeight: 'Genomsnittsvikt',
+ avgTime: 'Genomsnittstid',
+ filamentByPrints: 'Utskrifter',
+ timeframe: {
+ 'today': 'Idag',
+ 'this-week': 'Denna vecka',
+ 'this-month': 'Denna månad',
+ 'last-7': 'Senaste 7 dagarna',
+ 'last-30': 'Senaste 30 dagarna',
+ 'last-90': 'Senaste 90 dagarna',
+ 'this-year': 'Detta år',
+ 'all-time': 'All tid',
+ 'custom': 'Anpassat intervall',
+ from: 'Från',
+ to: 'Till',
+ },
+ // User filter
+ allUsers: 'Alla användare',
+ noUser: 'Ingen användare (System)',
+ filterByUser: 'Filtrera efter användare',
+ },
+
+ // Maintenance page
+ maintenance: {
+ title: 'Underhåll',
+ overview: 'Översikt',
+ allOk: 'Allt underhåll är gjort',
+ dueCount: '{{count}} underhåll uteblivet',
+ dueCount_plural: '{{count}} underhåll uteblivna',
+ warningCount: '{{count}} varning',
+ warningCount_plural: '{{count}} varningar',
+ totalPrintTime: 'Total utskriftstid',
+ nextMaintenance: 'Nästa underhåll',
+ nothingDue: 'Inget uteblivet',
+ tasks: 'Uppgifter',
+ lastPerformed: 'Senast utfört',
+ interval: 'Intervall',
+ hoursRemaining: '{{hours}}h kvar',
+ hoursOverdue: '{{hours}}h försenat',
+ markDone: 'Markera som klar',
+ performMaintenance: 'Utför underhåll',
+ history: 'Historik',
+ noHistory: 'Ingen underhållshistorik',
+ editPrintHours: 'Redigera utskriftstimmar',
+ currentHours: 'Aktuella timmar',
+ // Tabs
+ statusTab: 'Status',
+ settingsTab: 'Inställningar',
+ // Status
+ overdueCount: '{{count}} försenade',
+ dueSoonCount: '{{count}} förfaller snart',
+ dueSoon: 'Förfaller snart',
+ allGood: 'Allt bra',
+ overdueBy: 'Försenat med {{duration}}',
+ dueIn: 'Förfaller om {{duration}}',
+ timeLeft: '{{duration}} kvar',
+ // Duration formats
+ day: '1 dag',
+ days: '{{count}} dagar',
+ week: '1 vecka',
+ weeks: '{{count}} veckor',
+ month: '1 månad',
+ months: '{{count}} månader',
+ year: '1 år',
+ // Settings
+ maintenanceTypes: 'Underhållstyper',
+ maintenanceTypesDescription: 'Systemtyper och dina anpassade underhållsuppgifter',
+ addCustomType: 'Lägg till anpassad typ',
+ restoreDefaults: 'Återställ standarduppgifter',
+ intervalType: 'Intervalltyp',
+ intervalValue: 'Intervall ({{type}})',
+ icon: 'Ikon',
+ documentationLink: 'Dokumentationslänk (valfritt)',
+ assignToPrinters: 'Tilldela till skrivare',
+ selectAtLeastOnePrinter: 'Välj minst en skrivare',
+ addType: 'Lägg till typ',
+ custom: 'Anpassad',
+ printHours: 'Utskriftstimmar',
+ calendarDays: 'Kalenderdagar',
+ exampleName: 't.ex. Byt HEPA-filter',
+ viewDocumentation: 'Visa dokumentation',
+ timeBasedInterval: 'Tidsbaserat intervall',
+ // Interval overrides
+ intervalOverrides: 'Intervall-överskridningar',
+ intervalOverridesDescription: 'Anpassa intervall för specifika skrivare',
+ // Printer assignment
+ assignedToPrinters: 'Tilldelad till skrivare:',
+ noPrintersAssigned: 'Inga skrivare tilldelade',
+ addPrinterShort: 'Lägg till:',
+ printersAssignedClick: '{{count}} skrivare tilldelade – klicka för att hantera',
+ removeFromPrinter: 'Ta bort från denna skrivare',
+ // Types
+ types: {
+ lubricateCarbonRods: 'Smörj karbonstänger',
+ lubricateRails: 'Smörj linjära skenor',
+ cleanNozzle: 'Rengör munstycke/värmeblock',
+ checkBelts: 'Kontrollera remspänning',
+ cleanBuildPlate: 'Rengör byggplatta',
+ checkExtruder: 'Kontrollera extruderarkugghjul',
+ checkCooling: 'Kontrollera kylfläktar',
+ generalInspection: 'Allmän besiktning',
+ cleanCarbonRods: 'Rengör karbonstänger',
+ lubricateSteelRods: 'Smörj stålstänger',
+ cleanSteelRods: 'Rengör stålstänger',
+ cleanLinearRails: 'Rengör linjära skenor',
+ checkPtfeTube: 'Kontrollera PTFE-rör',
+ replaceHepaFilter: 'Byt HEPA-filter',
+ replaceCarbonFilter: 'Byt kolfilter',
+ lubricateLeftNozzleRail: 'Smörj vänster munstyckesskena',
+ },
+ // Toast
+ maintenanceComplete: 'Underhåll markerat som klart',
+ typeUpdated: 'Underhållstyp uppdaterad',
+ typeDeleted: 'Underhållstyp borttagen',
+ defaultsRestored: 'Återställde {{count}} standarduppgift(er)',
+ printHoursUpdated: 'Utskriftstimmar uppdaterade',
+ printerAssigned: 'Skrivare tilldelad',
+ printerRemoved: 'Skrivare borttagen',
+ // Confirmation
+ deleteTypeConfirm: 'Ta bort "{{name}}"?',
+ deleteSystemTypeTitle: 'Ta bort standardunderhållsuppgift?',
+ deleteSystemTypeMessage: 'Är du säker på att du vill ta bort standardunderhållsuppgift "{{name}}"?',
+ // Permissions
+ noPermissionUpdate: 'Du har inte behörighet att uppdatera underhållsposter',
+ noPermissionPerform: 'Du har inte behörighet att utföra underhåll',
+ noPermissionEditTypes: 'Du har inte behörighet att redigera underhållstyper',
+ noPermissionDeleteTypes: 'Du har inte behörighet att ta bort underhållstyper',
+ noPermissionEditHours: 'Du har inte behörighet att redigera utskriftstimmar',
+ noPermissionRemovePrinter: 'Du har inte behörighet att ta bort skrivartilldelningar',
+ noPermissionAssignPrinter: 'Du har inte behörighet att tilldela skrivare',
+ noPermissionEditIntervals: 'Du har inte behörighet att redigera intervall',
+ // Configure link
+ configureSettings: 'Konfigurera underhållstyper och intervall',
+ },
+
+ // Settings page
+ settings: {
+ sliceEngine: 'Slicer-motor',
+ sliceEngineSidecar: 'Server-sidecar',
+ sliceEngineSidecarHint: 'Slicing körs på servern, i slicerns sidecar-container.',
+ sliceEngineBrowser: 'I webbläsaren',
+ sliceEngineBrowserHint: 'Slicing körs på denna enhet, utan server inblandad.',
+ title: 'Inställningar',
+ general: 'Allmänt',
+ // Tab names
+ tabs: {
+ general: 'Allmänt',
+ smartPlugs: 'Smarta kontakter',
+ sensors: 'Sensorer',
+ notifications: 'Notiser',
+ queue: 'Arbetsflöde',
+ queueDispatch: 'Kö och utskick',
+ queuePipelines: 'Pipelines',
+ filament: 'Filament',
+ network: 'Nätverk',
+ apiKeys: 'API-nycklar',
+ virtualPrinter: 'Virtuell skrivare',
+ spoolbuddy: 'SpoolBuddy',
+ failureDetection: 'Feldetektering',
+ users: 'Autentisering',
+ backup: 'Säkerhetskopia',
+ emailAuth: 'E-postautentisering',
+ ldap: 'LDAP',
+ twoFa: 'Tvåfaktorsautentisering',
+ oidc: 'SSO / OIDC',
+ security: 'Säkerhet',
+ },
+ spoolbuddy: {
+ infoTitle: 'SpoolBuddy-enheter',
+ infoBody: 'SpoolBuddy-kiosker registrerar sig automatiskt via hjärtklapp. Avregistrera en enhet här om den inte längre används eller om en gammal dubblett lämnats kvar av en krasch i demonen.',
+ duplicatesTitle: '{{count}} enheter registrerade',
+ duplicatesBody: 'Endast den första registrerade enheten används av kiosk-gränssnittet. Om en av dessa är en gammal dubblett från en krasch, avregistrera den — en online-enhet kommer att registrera sig själv igen vid nästa hjärtklapp.',
+ empty: 'Inga SpoolBuddy-enheter registrerade än.',
+ online: 'Online',
+ offline: 'Offline',
+ unregister: 'Avregistrera',
+ unregisterSuccess: 'Enhet avregistrerad',
+ unregisterError: 'Kunde inte avregistrera enhet',
+ confirmTitle: 'Avregistrera SpoolBuddy-enhet?',
+ confirmBody: 'Detta kommer att ta bort "{{hostname}}" ({{deviceId}}) från databasen. Om enheten är online kommer den att registrera sig själv igen vid nästa hjärtklapp.',
+ ipAddress: 'IP-adress',
+ firmware: 'Firmware',
+ lastSeen: 'Senast sedd',
+ daemonUptime: 'Demonens upptid',
+ systemUptime: 'Systemets upptid',
+ never: 'aldrig',
+ nfc: 'NFC',
+ scale: 'Skala',
+ cpuTemp: 'CPU-temperatur',
+ cpuLoad: 'CPU-belastning',
+ memory: 'Minne',
+ disk: 'Disk',
+ // Device actions
+ update: 'Uppdatera',
+ updateConfirmTitle: 'Uppdatera SpoolBuddy-demon?',
+ updateConfirmBody: 'Trigga en programuppdatering på "{{hostname}}"? Demonen kommer att starta om när uppdateringen är klar.',
+ restartBrowser: 'Starta om webbläsaren',
+ restartBrowserConfirmTitle: 'Starta om kiosk-webbläsaren?',
+ restartBrowserConfirmBody: 'Starta om kiosk-webbläsaren på "{{hostname}}"? Skärmen kommer att vara tom en kort stund.',
+ restartDaemon: 'Starta om demonen',
+ restartDaemonConfirmTitle: 'Starta om SpoolBuddy-demonen?',
+ restartDaemonConfirmBody: 'Starta om SpoolBuddy-demonen på "{{hostname}}"? Enheten kommer att vara offline i några sekunder.',
+ reboot: 'Starta om',
+ rebootConfirmTitle: 'Starta om enheten?',
+ rebootConfirmBody: 'Starta om "{{hostname}}"? Enheten kommer att vara offline i cirka en minut.',
+ shutdown: 'Stäng av',
+ shutdownConfirmTitle: 'Stäng av enhet?',
+ shutdownConfirmBody: 'Stäng av "{{hostname}}"? Du kommer att behöva fysisk åtkomst för att starta den igen.',
+ commandConfirm: 'Bekräfta',
+ commandQueued: 'Kommando köat',
+ commandError: 'Kunde inte skicka kommando',
+ },
+ // LDAP settings
+ ldap: {
+ title: 'LDAP-autentisering',
+ enabledDesc: 'LDAP-autentisering är aktiverat',
+ disabledDesc: 'LDAP-autentisering är inaktiverat',
+ disabledHint: 'Konfigurera och spara LDAP-inställningar nedan, aktivera sedan.',
+ enabled: 'LDAP-autentisering aktiverad',
+ disabled: 'LDAP-autentisering inaktiverad',
+ feature1: 'Användare kan logga in med LDAP-uppgifter',
+ feature2: 'Lokalt admin-konto kvarstår som reserv',
+ feature3: 'LDAP-grupper mappas till Bambuddy-grupper vid inloggning',
+ serverConfig: 'LDAP-serverkonfiguration',
+ serverUrl: 'Server-URL',
+ serverUrlHint: 'Använd ldaps:// för SSL eller ldap:// med StartTLS',
+ security: 'Säkerhet',
+ securityHint: 'StartTLS uppgraderar en vanlig anslutning till TLS. LDAPS använder TLS från början.',
+ bindDn: 'Bind DN (Service-konto)',
+ bindPassword: 'Bind-lösenord',
+ searchBase: 'Sök bas-DN',
+ userFilter: 'Användarsökfilter',
+ userFilterHint: '{username} ersätts med inloggningsanvändarnamnet. Använd (uid={username}) för OpenLDAP.',
+ advanced: 'Avancerat',
+ autoProvision: 'Auto-provisionera användare',
+ autoProvisionHint: 'Skapa automatiskt ett Bambuddy-konto vid första LDAP-inloggning',
+ defaultGroup: 'Standardgrupp',
+ defaultGroupNone: '— Ingen (ingen reserv) —',
+ defaultGroupHint: 'Reservgrupp som tilldelas när en LDAP-användare autentiseras men inte finns i någon mappad LDAP-grupp. Lämna tomt för att lämna omappade användare utan behörigheter.',
+ groupMapping: 'Gruppmappning (JSON)',
+ groupMappingHint: 'Mappa LDAP-grupp-DN till Bambuddy-grupper. Tillgängliga grupper: ',
+ testConnection: 'Testa anslutning',
+ settingsSaved: 'LDAP-inställningar sparade',
+ errors: {
+ serverRequired: 'LDAP-server-URL krävs',
+ searchBaseRequired: 'Sök bas-DN krävs',
+ enableAuthFirst: 'Aktivera autentisering först',
+ configureLdapFirst: 'Spara LDAP-inställningar först',
+ },
+ },
+ // Email settings
+ email: {
+ smtpSettings: 'SMTP-konfiguration',
+ smtpHost: 'SMTP-server',
+ smtpPort: 'SMTP-port',
+ security: 'Säkerhet',
+ authentication: 'Autentisering',
+ username: 'Användarnamn',
+ password: 'Lösenord',
+ fromEmail: 'Från e-post',
+ fromName: 'Från namn',
+ testConnection: 'Testa SMTP-anslutning',
+ testRecipient: 'Testmottagare e-post',
+ sendTest: 'Skicka test-e-post',
+ sending: 'Skickar...',
+ save: 'Spara inställningar',
+ saving: 'Sparar...',
+ advancedAuth: 'Avancerad autentisering',
+ advancedAuthEnabled: 'Avancerad autentisering är aktiverad',
+ advancedAuthEnabledDesc: 'E-postbaserade användarhanteringsfunktioner är aktiva. Nya användare kommer att få autogenererade lösenord via e-post, och användare kan återställa sina lösenord via "Glömt lösenord"-funktionen.',
+ advancedAuthDisabled: 'Avancerad autentisering är inaktiverad',
+ advancedAuthDisabledDesc: 'Aktivera avancerad autentisering för att aktivera e-postbaserade funktioner för användarhantering.',
+ enable: 'Aktivera',
+ disable: 'Inaktivera',
+ feature1: 'Lösenord genereras automatiskt och skickas via e-post till nya användare',
+ feature2: 'Användare kan logga in med användarnamn eller e-post',
+ feature3: '"Glömt lösenord"-funktionen är tillgänglig',
+ feature4: 'Admin kan återställa användarlösenord via e-post',
+ // Error messages
+ errors: {
+ requiredFields: 'Fyll i alla obligatoriska fält',
+ usernameRequired: 'Användarnamn krävs när autentisering är aktiverad',
+ enterTestEmail: 'Ange en test-e-postadress',
+ smtpServerAndEmail: 'Fyll i SMTP-server och Från e-post innan du testar',
+ usernamePasswordRequired: 'Användarnamn och lösenord krävs när autentisering är aktiverat',
+ configureSmtpFirst: 'Konfigurera och testa SMTP-inställningar först',
+ enableAuthFirst: 'Aktivera autentisering först för att använda e-postbaserade funktioner.',
+ },
+ // Success messages
+ success: {
+ settingsSaved: 'SMTP-inställningar sparade',
+ },
+ // Security options
+ securityOptions: {
+ starttls: 'STARTTLS (Port 587)',
+ ssl: 'SSL/TLS (Port 465)',
+ none: 'Ingen (Port 25)',
+ },
+ // Authentication options
+ authOptions: {
+ enabled: 'Aktiverad',
+ disabled: 'Inaktiverad',
+ },
+ },
+ appearance: 'Utseende',
+ notifications: 'Notiser',
+ smartPlugs: 'Smarta kontakter',
+ spoolman: 'Spoolman',
+ updates: 'Uppdateringar',
+ language: 'Språk',
+ languageDescription: 'Välj ditt föredragna språk',
+ theme: 'Tema',
+ themeLight: 'Ljust',
+ themeDark: 'Mörkt',
+ themeSystem: 'System',
+ defaultView: 'Standardvy',
+ defaultViewDescription: 'Sida att visa när appen öppnas',
+ checkForUpdates: 'Sök efter uppdateringar',
+ autoUpdate: 'Auto-uppdatering',
+ currentVersion: 'Aktuell version',
+ latestVersion: 'Senaste version',
+ upToDate: 'Du har den senaste versionen',
+ updateAvailable: 'Uppdatering tillgänglig',
+ // Notifications
+ notificationLanguage: 'Notisspråk',
+ notificationLanguageDescription: 'Språk för push-notiser',
+ bedCooledThreshold: 'Tröskelvärde för sval byggplatta',
+ bedCooledThresholdDescription: 'Temperatur under vilken byggplattan anses sval efter en utskrift',
+ userNotificationsEnabled: 'Användarnotiser',
+ userNotificationsEnabledDescription: 'Aktivera menyn för användarnotiser och e-postnotiser för utskriftshändelser. Kräver avancerad autentisering.',
+ userNotificationsDisabledHint: 'Aktivera avancerad autentisering för att använda användarnotiser.',
+ notificationProviders: 'Notisleverantörer',
+ addProvider: 'Lägg till leverantör',
+ editProvider: 'Redigera leverantör',
+ providerType: 'Leverantörstyp',
+ testNotification: 'Testnotis',
+ testSuccess: 'Testnotis skickad',
+ testFailed: 'Kunde inte skicka testnotis',
+ quietHours: 'Tysta timmar',
+ quietHoursDescription: 'Stör ej under dessa timmar',
+ quietHoursStart: 'Start',
+ quietHoursEnd: 'Slut',
+ events: {
+ title: 'Notishändelser',
+ printStart: 'Utskrift startad',
+ printComplete: 'Utskrift slutförd',
+ printFailed: 'Utskrift misslyckades',
+ printStopped: 'Utskrift stoppad',
+ printProgress: 'Förloppsmilstolpar',
+ printProgressDescription: 'Notis vid 25%, 50%, 75%',
+ printerOffline: 'Skrivare offline',
+ printerError: 'Skrivarfel',
+ filamentLow: 'Lågt filament',
+ maintenanceDue: 'Underhåll behövs',
+ maintenanceDueDescription: 'Notis när underhåll behövs',
+ },
+ // Smart Plugs
+ smartPlug: {
+ title: 'Smarta kontakter',
+ add: 'Lägg till smart kontakt',
+ edit: 'Redigera smart kontakt',
+ name: 'Namn',
+ ipAddress: 'IP-adress',
+ linkedPrinter: 'Länkad skrivare',
+ autoOn: 'Auto ström på',
+ autoOnDescription: 'Sätt på när utskrift startar',
+ autoOff: 'Auto ström av',
+ autoOffDescription: 'Stäng av efter att utskrift slutförts',
+ offDelay: 'Avstängningsfördröjning',
+ offDelayMinutes: 'Minuter efter utskrift',
+ offDelayTemp: 'När munstycket är under temperatur',
+ currentState: 'Aktuell status',
+ turnOn: 'Sätt på',
+ turnOff: 'Stäng av',
+ },
+ // Filament Tracking Mode
+ filamentTracking: 'Filamentspårning',
+ filamentTrackingDesc: 'Välj hur du vill spåra dina filamentrullar. Du kan använda det inbyggda inventariet eller ansluta till en extern Spoolman-server.',
+ autoAddUnknownRfid: 'Lägg automatiskt till okända RFID-rullar',
+ autoAddUnknownRfidDesc: 'Skapa automatiskt en inventariepost när en rulle med en okänd RFID-tagg upptäcks. Stäng av om du förregistrerar nya rullar manuellt för att undvika dubbletter.',
+ filamentChecks: 'Filamentkontroller',
+ disableFilamentWarnings: 'Inaktivera filamentvarningar',
+ disableFilamentWarningsDesc: 'Visa inte varningar om otillräckligt filament vid utskrift eller kö',
+ preferLowestFilament: 'Föredra rulle med minst filament kvar',
+ preferLowestFilamentDesc: 'När flera rullar matchar, använd den med minst filament kvar',
+ preferLowestFilamentBackupNote: 'Gäller endast när AMS filamentbackup är aktiverat på skrivaren — annars kan skrivaren inte byta till en andra rulle när den valda tar slut.',
+ trackingModeBuiltIn: 'Inbyggd inventering',
+ trackingModeBuiltInDesc: 'RFID-auto-matchning och användningsspårning inkluderad',
+ trackingModeSpoolmanDesc: 'Extern filamenthanteringsserver',
+ builtInFeatureRfid: 'Upptäcker automatiskt Bambu Lab RFID-rullar i AMS',
+ builtInFeatureUsage: 'Spårar filamentförbrukning per utskrift',
+ builtInFeatureCatalog: 'Hantera rullar, färger och K-faktorprofiler',
+ builtInFeatureThirdParty: 'Tredjepartsrullar kan tilldelas till inventeringsrullar',
+ amsSyncButton: 'Synkronisera vikter från AMS',
+ amsSyncTitle: 'Synkronisera rullvikter från AMS',
+ amsSyncMessage: 'Detta kommer att skriva över alla rullar i inventariet med vikter från de aktuella AMS-procentvärdena från anslutna skrivare. Använd detta för att återställa från korrupt viktdata. Skrivaren måste vara online.',
+ amsSyncing: 'Synkroniserar...',
+ amsSyncSuccess: '{{synced}} rulle(ar) synkroniserade, {{skipped}} hoppades över',
+ amsSyncError: 'Kunde inte synkronisera vikter från AMS',
+ spoolmanAmsSyncButton: 'Synkronisera Spoolman-vikter från AMS',
+ spoolmanAmsSyncTitle: 'Synkronisera Spoolman-rullvikter från AMS',
+ spoolmanAmsSyncMessage: 'Detta kommer att uppdatera alla Spoolman-rullars vikter baserat på de aktuella AMS-procentvärdena från anslutna skrivare. Skrivaren måste vara online.',
+ spoolmanAmsSyncing: 'Synkroniserar...',
+ spoolmanAmsSyncSuccess: '{{synced}} rulle(ar) synkroniserade, {{skipped}} hoppades över',
+ spoolmanAmsSyncError: 'Kunde inte synkronisera Spoolman-vikter från AMS',
+ spoolmanAmsSyncErrorUnreachable: 'Kunde inte synkronisera Spoolman-vikter (Spoolman otillgänglig)',
+ spoolmanAmsSyncErrorNotConfigured: 'Kunde inte synkronisera Spoolman-vikter (Spoolman ej konfigurerad)',
+ spoolmanNotConfigured: 'Spoolman ej konfigurerad',
+ // Spoolman filament catalog section in spool catalog settings
+ spoolmanFilamentCatalogTitle: 'Spoolman filamentkatalog',
+ spoolmanFilamentCatalogDesc: 'Filamentnamn och taravikter från din Spoolman-instans. Namn och rullvikt kan redigeras här; alla andra egenskaper hanteras direkt i Spoolman.',
+ // Spoolman settings
+ spoolmanUrl: 'Spoolman-URL',
+ spoolmanUrlHint: 'URL till din Spoolman-server (t.ex. http://localhost:7912)',
+ spoolmanConnected: 'Ansluten',
+ spoolmanDisconnected: 'Frånkopplad',
+ status: 'Status',
+ connect: 'Anslut',
+ disconnect: 'Koppla från',
+ howSyncWorks: 'Hur synkronisering fungerar',
+ syncInfoRfidOnly: 'Endast officiella Bambu Lab-rullar med RFID synkroniseras',
+ syncInfoAutoCreate: 'Nya rullar skapas automatiskt i Spoolman vid första synkronisering',
+ syncInfoThirdPartySkipped: 'Icke-Bambu Lab-rullar (tredjepart, påfyllda) hoppas över',
+ linkingExistingSpools: 'Länka befintliga rullar',
+ linkingExistingSpoolsDesc: 'För att länka befintliga Spoolman-rullar till din AMS, håll musen över ett AMS-fack och klicka på "Länka till Spoolman".',
+ syncMode: 'Synkroniseringsläge',
+ syncModeAuto: 'Automatisk',
+ syncModeManual: 'Endast manuell',
+ syncModeAutoDesc: 'AMS-data synkroniseras automatiskt när ändringar upptäcks',
+ syncModeManualDesc: 'Synkroniserar endast vid manuell aktivering',
+ syncAmsData: 'Synkronisera AMS-data',
+ syncAmsDataDesc: 'Synkronisera skrivarens AMS-data till Spoolman manuellt',
+ allPrinters: 'Alla skrivare',
+ // Default printer
+ noDefaultPrinter: 'Ingen standard (fråga varje gång)',
+ // Sidebar
+ sidebarOrder: 'Sidofältsordning',
+ // Camera
+ saveThumbnails: 'Spara minibilder',
+ captureFinishPhoto: 'Ta färdig-foto',
+ noPrintersConfigured: 'Inga skrivare konfigurerade',
+ // Archive settings
+ archiveMode: {
+ always: 'Skapa alltid arkivpost',
+ never: 'Skapa aldrig arkivpost',
+ ask: 'Fråga varje gång',
+ },
+ // Updates
+ checkForUpdatesLabel: 'Sök efter uppdateringar',
+ checkPrinterFirmware: 'Kontrollera skrivarens firmware',
+ includeBetaUpdates: 'Inkludera betaversioner',
+ includeBetaUpdatesDesc: 'Notis om betaversioner och förhandsversioner vid sökning av uppdateringar',
+ localLogin: {
+ disable: 'Inaktivera lokal användarnamn/lösenordsinloggning',
+ disableHint: 'När aktiverat kan endast SSO-leverantörer logga in. LDAP påverkas inte. Sätt BAMBUDDY_LOCAL_LOGIN=true på servern för att behålla en återställningsväg.',
+ },
+ // Queue
+ enableRetry: 'Aktivera försök igen',
+ // Home Assistant
+ homeAssistantDescription: 'Kontrollera smarta kontakter via Home Assistant',
+ environmentManagedLabel: '(Hanterad av miljövariabler)',
+ autoEnabledViaEnv: 'Automatiskt aktiverad via miljövariabler',
+ urlFromEnvReadOnly: 'Värde inställt av HA_URL-miljövariabel (skrivskyddad)',
+ tokenFromEnvReadOnly: 'Värde inställt av HA_TOKEN-miljövariabel (skrivskyddad)',
+ // MQTT
+ mqttConnectedTo: 'Ansluten till',
+ // Prometheus
+ prometheusDescription: 'Exponera skrivardata i Prometheus-format',
+ // Smart plugs empty state
+ noSmartPlugsTitle: 'Inga smarta kontakter konfigurerade',
+ noSmartPlugsDescription: 'Lägg till en Tasmota-baserad smart kontakt för att spåra energiförbrukning och automatisera strömkontroll.',
+ // Notifications empty state
+ noProvidersTitle: 'Inga leverantörer konfigurerade',
+ noProvidersDescription: 'Lägg till en leverantör för att ta emot aviseringar.',
+ noTemplatesAvailable: 'Inga mallar tillgängliga. Starta om backend för att ladda standardmallar.',
+ // API permissions
+ apiPermissionView: 'Visa skrivarstatus och kö',
+ apiPermissionEdit: 'Lägg till och ta bort poster från utskriftskö',
+ // API keys
+ apiKeysEmptyTitle: 'Inga API-nycklar',
+ apiKeysEmptyDescription: 'Skapa en API-nyckel för att integrera med externa tjänster.',
+ // Users
+ noUsersFound: 'Inga användare hittade',
+ noGroupsFound: 'Inga grupper hittade',
+ noGroupsAvailable: 'Inga grupper tillgängliga',
+ passwordsDoNotMatch: 'Lösenorden matchar ej',
+ systemGroupWarning: 'Systemgruppnamn kan inte ändras',
+ // Auth disabled
+ authDisabledTitle: 'Autentisering är inaktiverat',
+ authDisabledFeature1: 'Kräv inloggning för att komma åt systemet',
+ authDisabledFeature2: 'Skapa flera användare med gruppbaserade behörigheter',
+ authDisabledFeature3: 'Kontrollera åtkomst med 50+ detaljerade behörigheter',
+ // User deletion
+ userHasCreated: 'Denna användare har skapat:',
+ userItemsQuestion: 'Vad vill du göra med dessa objekt?',
+ deleteUserConfirm: 'Är du säker på att du vill ta bort denna användare?',
+ actionCannotBeUndone: 'Denna åtgärd kan inte ångras.',
+ // Smart plugs
+ addFirstSmartPlug: 'Lägg till din första smarta kontakt',
+ // Notifications
+ providers: 'Leverantörer',
+ log: 'Logg',
+ testAll: 'Testa alla',
+ testResults: 'Testresultat',
+ testPassedCount: '{{count}} godkända',
+ testFailedCount: '{{count}} misslyckade',
+ messageTemplates: 'Meddelandemallar',
+ messageTemplatesDescription: 'Anpassa notismeddelanden för varje händelse.',
+ // API Keys section
+ apiKeys: 'API-nycklar',
+ apiKeysDescription: 'Skapa API-nycklar för externa integrationer och webhooks.',
+ createKey: 'Skapa nyckel',
+ apiKeyCreated: 'API-nyckel skapad',
+ apiKeyCopyWarning: 'Kopiera denna nyckel nu – den kommer inte att visas igen!',
+ useInApiBrowser: 'Använd i API-läsaren',
+ apiKeyQrButton: 'QR-kod',
+ apiKeyQrTitle: 'Skanna för att konfigurera',
+ apiKeyQrCaption: 'Skanna med din mobilapp för att lägga till denna server och API-nyckel.',
+ apiKeyQrWarning: 'Innehåller din hemliga API-nyckel — dela inte eller ta skärmdump där andra kan se den.',
+ createNewApiKey: 'Skapa ny API-nyckel',
+ keyName: 'Nyckelnamn',
+ keyNamePlaceholder: 't.ex. Home Assistant, OctoPrint',
+ readStatus: 'Läs status',
+ readStatusDescription: 'Visa skrivarstatus och kö',
+ manageQueue: 'Hantera kö',
+ manageQueueDescription: 'Lägg till och ta bort poster från utskriftskö',
+ controlPrinter: 'Kontrollera skrivare',
+ controlPrinterDescription: 'Pausa, fortsätt och stoppa utskrifter',
+ manageLibrary: 'Hantera bibliotek',
+ manageLibraryDescription: 'Ladda upp, döp om och ta bort biblioteksfiler; importera modeller från MakerWorld',
+ manageInventory: 'Hantera inventarier',
+ manageInventoryDescription: 'Skapa, uppdatera och ta bort rullar och inventeringsposter. Krävs för SpoolBuddy-kiosker (NFC-skanning, vågläsningar, kiosksystemkommandon).',
+ manageMaintenance: 'Hantera underhåll',
+ manageMaintenanceDescription: 'Logga slutfört underhåll, återställ räknare, redigera intervall och hantera underhållstypkatalogen. Lämplig för Home Assistant-automatiseringar som registrerar "Jag rengjorde munstycket" utan att ge bredare skrivarkontroll.',
+ manageArchives: 'Hantera arkiv',
+ manageArchivesDescription: 'Redigera och ta bort utskriftsarkiv, inklusive borttagning av gamla utskrifter. Inkluderar inte borttagning av deras statistikbidrag. Lämplig för automatiseringar som rensar utskriftshistoriken.',
+ manageProjects: 'Hantera projekt',
+ manageProjectsDescription: 'Skapa, uppdatera och ta bort projekt, samt lägg till arkiv i dem. Lämplig för automatiseringar som organiserar utskrifter i projekt.',
+ libraryBadge: 'Bibliotek',
+ inventoryBadge: 'Inventarium',
+ maintenanceBadge: 'Underhåll',
+ archivesBadge: 'Arkiv',
+ projectsBadge: 'Projekt',
+ cloudAccess: 'Tillåt molnåtkomst',
+ cloudAccessDescription: 'Läs Bambu Cloud-inställningar och filament å dina vägnar. Kräver att du är inloggad på Bambu Cloud.',
+ cloudBadge: 'Moln',
+ updateEnergyCost: 'Uppdatera elpris',
+ updateEnergyCostDescription: 'Tillåt denna nyckel att POST:a ett nytt pris per kWh till /settings/electricity-price. Användbart för Home Assistants dynamiska tariffautomatiseringar (Tibber, Octopus, etc.). Detta är det enda inställningsfält som kan skrivas via API-nyckel.',
+ energyCostBadge: 'Energi',
+ legacyKey: 'Äldre nyckel',
+ legacyKeyTooltip: 'Skapad före användarägande; skapa igen för att använda molnåtkomst',
+ unnamedKey: 'Namnlös nyckel',
+ lastUsed: 'Senast använd',
+ read: 'Läs',
+ control: 'Kontroll',
+ createFirstKey: 'Skapa din första nyckel',
+ webhookEndpoints: 'Webhook-ändpunkter',
+ webhookApiKeyHint: 'Använd din API-nyckel i adressinformationen för X-API-Key.',
+ webhook: {
+ getAllStatus: 'Hämta alla skrivarstatusar',
+ getSpecificStatus: 'Hämta specifik skrivarstatus',
+ addToQueue: 'Lägg till i utskriftskö',
+ pausePrint: 'Pausa utskrift',
+ resumePrint: 'Fortsätt utskrift',
+ stopPrint: 'Stoppa utskrift',
+ },
+ apiBrowser: 'API-läsare',
+ apiBrowserDescription: 'Utforska och testa alla tillgängliga API-ändpunkter.',
+ apiKeyForTesting: 'API-nyckel för testning',
+ apiKeyPlaceholder: 'Klistra in din API-nyckel här för att testa autentiserade ändpunkter...',
+ apiKeyHint: 'Denna nyckel kommer att skickas som X-API-Key-adressinformation med förfrågningarna.',
+ deleteApiKeyTitle: 'Ta bort API-nyckel',
+ deleteApiKeyMessage: 'Är du säker på att du vill ta bort denna API-nyckel? Alla integrationer som använder denna nyckel kommer att sluta fungera.',
+ deleteKey: 'Ta bort nyckel',
+ // Filament tab
+ amsDisplayThresholds: 'AMS-visningströskelvärden',
+ amsThresholdsDescription: 'Konfigurera färgernas tröskelvärden för AMS:ens luftfuktighets- och temperaturindikatorer.',
+ humidity: 'Luftfuktighet',
+ goodGreen: 'God (grön)',
+ fairOrange: 'Godkänd (orange)',
+ aboveFairBad: 'Över godkänt tröskelvärde visas som röd (dålig)',
+ fairAlsoDryingThreshold: 'Detta tröskelvärde används också för att trigga auto-torkning när det är aktiverat',
+ fairBelowDryingFloor: 'Auto-torkning kan inte nå detta värde: en AMS läser {{floor}}% eller mer medan dess värmare körs, så varje cykel skulle sluta och starta om omedelbart. Höj den över {{floor}}% om auto-torkning är på.',
+ temperature: 'Temperatur',
+ goodBlue: 'God (blå)',
+ aboveFairHot: 'Över godkänt tröskelvärde visas som röd (varm)',
+ tempAlarmThreshold: 'Larma över',
+ tempAlarmSeparateFromBand: 'Endast larmtröskeln skickar meddelanden — God och Godkänd färgar displayen. Lämna den tom för att larma vid Godkänd-tröskeln, som tidigare.',
+ tempAlarmMustBePositive: 'Ett tröskelvärde på 0 eller lägre ignoreras — larmet faller tillbaka till Godkänd-tröskelvärdet.',
+ historyRetention: 'Bevarande av historik',
+ keepSensorHistory: 'Bevara sensordata för',
+ historyRetentionDescription: 'Äldre luftfuktighets- och temperaturdata kommer att tas bort automatiskt',
+ defaultPrintOptions: 'Standardutskriftsalternativ',
+ defaultPrintOptionsDescription: 'Sätt standardvärden för utskriftsalternativ när nya utskrifter startas. Dessa kan åsidosättas per utskrift i utskriftsdialogrutan.',
+ defaultBedLevelling: 'Byggplattsnivåinställning',
+ defaultBedLevellingDesc: 'Ställer in byggplattan automatiskt före utskrift',
+ defaultFlowCali: 'Flödeskalibrering',
+ defaultFlowCaliDesc: 'Kalibrera extruderingsflöde',
+ defaultVibrationCali: 'Vibrationskalibrering',
+ defaultVibrationCaliDesc: 'Minska ringningsartefakter',
+ defaultLayerInspect: 'Förstaskiktsinspektion',
+ defaultLayerInspectDesc: 'AI-inspektion av första skiktet',
+ defaultTimelapse: 'Timelapse',
+ defaultTimelapseDesc: 'Spela in timelapse-video',
+ defaultNozzleOffsetCali: 'Munstyckesoffsetkalibrering',
+ defaultNozzleOffsetCaliDesc: 'Kalibrera munstyckesoffsetar mellan extruderare',
+ tempFanPresetsTitle: 'Temperatur- och fläktförinställningar',
+ tempFanPresetsDescription: 'Anpassa snabbvalsvärden som visas i skrivarkortets temperatur- och fläkthastighets-popovers. Av-knappen visas alltid.',
+ tempFanPresetsNozzle: 'Munstyckstemperatur',
+ tempFanPresetsBed: 'Byggplattstemperatur',
+ tempFanPresetsChamber: 'Kammartemperatur',
+ tempFanPresetsFan: 'Fläkthastighet',
+ tempFanPresetsReset: 'Återställ till standard',
+ concurrentUploadsTitle: 'Samtidiga uppladdningar',
+ concurrentUploadsDescription: 'Hur många skrivare kön kan skicka filer till samtidigt. Skrivare tar emot filer långsamt (en stor utskrift kan ta flera minuter), och varje skrivare väntar på sin tur — så med en större flotta är det detta som förhindrar att den sista skrivaren i en batch väntar på alla överföringar före den. Sänk detta om ditt nätverk eller din Bambuddy-värd har svårt med parallella överföringar.',
+ concurrentUploadsLabel: 'Skrivare som laddas upp till samtidigt',
+ concurrentUploadsHelp: '1 skickar till en skrivare i taget (gammalt beteende). Standard är 4.',
+ staggeredStart: 'Förskjuten start',
+ staggeredStartDescription: 'Standardgruppstorlek och intervall vid förskjuten start av batchutskrifter på flera skrivare. Kan åsidosättas per batch i utskriftsdialogrutan.',
+ preheatTitle: 'Förvärme',
+ preheatDescription: 'Värm byggplattan (och kammaren, om stödjes) och håll temperaturen före varje köad utskrift startar. Användbart för tekniska filament (PA, ABS) på skrivare utan aktiv kammarvärmare — byggplattan värmer kammaren med värmestrålning medan varmhållningstimern går. Måltemperaturen för byggplattan läses från utskriftsfilen; kammarbeteendet beror på skrivarmodell.',
+ preheatEnabled: 'Aktivera förvärme och varmhållning',
+ preheatEnabledDesc: 'När avstängt skickas köade utskrifter omedelbart. Varje köpost kan åsidosättas per utskrift.',
+ preheatFilamentTargetsLabel: 'Kammarmål per filamenttyp (°C)',
+ preheatFilamentTargetsHint: 'Bambuddy väljer det högsta målet bland de laddade AMS-facken; utskrifter med endast PLA får värdet 0 och hoppar automatiskt över kammarfasen.',
+ preheatFilamentTargetsReset: 'Återställ till standard',
+ preheatFilamentTargetsDefaultRow: 'Övrigt / omappat',
+ preheatMaxWait: 'Max väntetid (sekunder)',
+ preheatMaxWaitHelp: 'Begränsning för kammaruppvärmningsfasen innan den hoppar över.',
+ preheatSoak: 'Varmhållning (sekunder)',
+ preheatSoakHelp: 'Hålltid efter att måltemperaturen nåtts eller max-väntetiden passerat.',
+ preheatHardwareTitle: 'Handlingssätt per skrivare:',
+ preheatHardwareDetail: 'H2C/H2D/H2D Pro/H2S/X2D/X1E värmer kammaren aktivt via M141. X1C/P2S läser kammartemperatur men förlitar sig på byggplattans byggplattsvärme. P1S/P1P/A1/A1 Mini har ingen kammarsensor — endast varmhållningstimern gäller.',
+ preheatPerItemDesc: 'Värm byggplattan och kammaren före denna utskrift startar. Standard är den globala inställningen Inställningar → Arbetsflöde.',
+ preheatOverride_inherit: 'Ärv',
+ preheatOverride_on: 'På',
+ preheatOverride_off: 'Av',
+ calibrationMode_off: 'Av',
+ calibrationMode_on: 'På',
+ calibrationMode_auto: 'Auto',
+ preheatTargetOverride: 'Kammartemperatur-åsidosättning (°C, tom = standard för filamentet)',
+ preheatTargetOverrideHelp: '0 värmer plattan och kör uppvärmningen utan kammaren. Lämna tomt och en utskrift utan kammarkrav hoppar över förvärmningen helt.',
+ plateClear: 'Bekräftelse för tömd platta',
+ requirePlateClear: 'Kräv bekräftelse för tömd platta',
+ requirePlateClearDescription: 'När aktiverat väntar schemaläggaren på bekräftelse för tömd platta per skrivare innan köade utskrifter startar på skrivare med slutförda jobb. Om detta inaktiveras döljs också plattstatusbadgen och knappen "Markera platta som tömd" på skrivarkorten.',
+ keepBedWarm: 'Håll plattan varm mellan utskrifterna',
+ keepBedWarmDesc: 'Tills plattan är tömd, håll plattan vid varmhållningstemperaturen nedan så att kammaren förblir varm - eller vid nästa utskrifts egen plattemperatur om den är högre. Gäller endast när nästa utskrift behöver kammarvärme (ASA, ABS, PA, PC etc.). Kräver en bekräftelse på tömd platta för att vara aktiverad.',
+ keepWarmBedTemp: 'Varmhållningstemperatur för platta (°C)',
+ keepWarmBedTempHelp: 'Används när plattans uppgift är att värma upp kammaren — hålla värmen och förvärma när utskriftsfilen inte har någon plattemperatur. En högre plattemperatur från utskriftsfilen vinner alltid.',
+ keepWarmMaxMinutes: 'Sluta hålla värmen efter (minuter)',
+ keepWarmMaxMinutesHelp: 'Om plattan inte töms inom denna tid, stängs värmarna av istället för att hållas på obestämd tid.',
+ gcodeInjection: 'G-kodinjicering',
+ gcodeInjectionDescription: 'Konfigurera anpassad G-kod för att injiceras i början och/eller slutet av utskrifter för auto-utskriftssystem som Farmloop, SwapMod, AutoClear och Printflow 3D. Kodsnuttar konfigureras per skrivarmodell och tillämpas när "Injicera G-kod" är aktiverat på en köpost.',
+ gcodeInjectionNoPrinters: 'Inga skrivare hittade. Lägg till skrivare för att konfigurera G-kod-snuttar.',
+ gcodeStartLabel: 'Start-G-kod',
+ gcodeEndLabel: 'Slut-G-kod',
+ gcodeStartPlaceholder: 'G-kod som läggs till före utskriftens start...',
+ gcodeEndPlaceholder: 'G-kod som läggs till efter utskriftens slut...',
+ staggerGroupSize: 'Gruppstorlek',
+ staggerGroupSizeHelp: 'Skrivare som startar samtidigt per grupp',
+ staggerInterval: 'Intervall (minuter)',
+ staggerIntervalHelp: 'Fördröjning mellan varje grupps start',
+ queueDrying: 'Köa auto-torkning',
+ queueDryingDescription: 'Torka AMS-filament automatiskt när skrivaren är inaktiv mellan köade utskrifter. Använder tröskelvärdet för luftfuktighet ovan för att trigga torkning.',
+ queueDryingEnabled: 'Aktivera auto-torkning',
+ queueDryingEnabledDescription: 'Starta AMS-torkning automatiskt när skrivaren är inaktiv och luftfuktigheten är över tröskelvärdet',
+ queueDryingBlock: 'Vänta på att torkning slutförts',
+ queueDryingBlockDescription: 'Blockera utskriftskön tills torkningen är klar. När avstängt prioriteras utskrifter över torkning.',
+ ambientDryingEnabled: 'Bakgrundstorkning',
+ ambientDryingEnabledDescription: 'Torka filament automatiskt på inaktiva skrivare när luftfuktigheten överskrider tröskelvärdet, även utan köade utskrifter.',
+ printDryingEnabled: 'Fortsätt torka under utskrift',
+ printDryingEnabledDescription: 'Tillåt auto-torkning att fortsätta köra under en utskrift på hårdvara som stödjer det (H2D, H2C, H2S, P2S, H2D Pro, X2D, X1C, A2L med senaste firmware). Torkningstemperaturen begränsas automatiskt till 5°C under idle-förinställningen för att skydda rullar.',
+ dryingPresets: 'Torkningsförinställningar',
+ dryingPresetsDescription: 'Temperatur och varaktighet per filamenttyp. AMS 2 Pro använder lägre temperaturer, AMS-HT stöder högre temperaturer.',
+ dryingFilament: 'Filament',
+ humidityThresholds: 'Tröskelvärden för luftfuktighet',
+ humidityThresholdsDescription: 'Luftfuktighetsutlösare per filament för auto-torkning och larm. Blandade laddningar i en AMS använder det lägsta tröskelvärdet.',
+ humidityThresholdCol: 'Tröskelvärde',
+ humidityThresholdDefault: 'Standard (okända typer)',
+ printModal: 'Utskriftsdialogruta',
+ expandCustomMapping: 'Expandera anpassad mappning som standard',
+ expandCustomMappingDescription: 'När utskrift skickas till flera skrivare, visa utökad AMS-mappning per skrivare',
+ // User management
+ authentication: 'Autentisering',
+ authEnabledDescription: 'Din instans är skyddad med användarautentisering',
+ authDisabledDescription: 'Aktivera för att kräva inloggning och hantera användaråtkomst',
+ authDisabledMessage: 'Aktivera autentisering för att skapa användarkonton, hantera behörigheter och skydda din Bambuddy-instans.',
+ enableAuthentication: 'Aktivera autentisering',
+ currentUser: 'Aktuell användare',
+ changePassword: 'Ändra lösenord',
+ admin: 'Admin',
+ users: 'Användare',
+ addUser: 'Lägg till användare',
+ groups: 'Grupper',
+ addGroup: 'Lägg till grupp',
+ system: 'System',
+ noDescription: 'Ingen beskrivning',
+ userCount: '{{count}} användare',
+ permissionCount: '{{count}} behörigheter',
+ createUser: 'Skapa användare',
+ username: 'Användarnamn',
+ enterUsername: 'Ange användarnamn',
+ password: 'Lösenord',
+ enterPassword: 'Ange lösenord',
+ passwordRequirements: 'Minst 8 tecken, med en versal, en gemen, en siffra och ett specialtecken.',
+ confirmPassword: 'Bekräfta lösenord',
+ confirmPasswordPlaceholder: 'Bekräfta lösenord',
+ // Title tooltips
+ viewReleaseOnGitHub: 'Visa release på GitHub',
+ turnAllPlugsOn: 'Sätt på alla kontakter',
+ turnAllPlugsOff: 'Stäng av alla kontakter',
+ // Modal: Clear logs
+ clearNotificationLogs: 'Rensa notisloggar',
+ clearLogsMessage: 'Detta kommer permanent ta bort alla notisloggar äldre än 30 dagar. Denna åtgärd kan inte ångras.',
+ clearLogs: 'Rensa loggar',
+ // Modal: Reset UI
+ resetUiPreferences: 'Återställ UI-inställningar',
+ resetUiPreferencesMessage: 'Detta kommer återställa alla UI-inställningar till standard: sidofältsordning, tema, instrumentpanelslayout, visningslägen och sorteringsinställningar. Dina skrivare, arkiv och serverinställningar kommer INTE att påverkas. Sidan kommer att laddas om efter rensning.',
+ resetPreferences: 'Återställ inställningar',
+ // Modal: Delete group
+ deleteGroupTitle: 'Ta bort grupp',
+ deleteGroupMessage: 'Är du säker på att du vill ta bort denna grupp? Användare i denna grupp kommer att förlora dessa behörigheter.',
+ deleteGroup: 'Ta bort grupp',
+ // Modal: Disable auth
+ disableAuthenticationTitle: 'Inaktivera autentisering',
+ disableAuthenticationMessage: 'Är du säker på att du vill inaktivera autentisering? Detta kommer att göra din Bambuddy-instans tillgänglig utan inloggning. Alla användare kommer att finnas kvar i databasen, men autentisering kommer att vara inaktiverat.',
+ disableAuthentication: 'Inaktivera autentisering',
+ // Additional settings
+ configureBambuddy: 'Konfigurera Bambuddy',
+ systemDefault: 'Systemstandard',
+ archiveSettings: 'Arkivinställningar',
+ newWindow: 'Nytt fönster',
+ embeddedOverlay: 'Inbäddat överlägg',
+ preferredSlicer: 'Föredragen slicer',
+ preferredSlicerDescription: 'Slicer som används för slicing i appen via API-sidecar',
+ openInSlicerLabel: 'Öppna i slicer',
+ openInSlicerInherit: 'Samma som API-slicer',
+ openInSlicerDescription: "Slicer som används av knappen 'Öppna i slicer'. Lämna på 'Samma som API-slicer' för att ärva, eller välj en annan slicer för lokal användning.",
+ orcaslicerKnownIssuesWarning: 'OrcaSlicer 2.3.2 / 2.4.0-dev har kända CLI-buggar som blockerar slicing av många Bambu-skapade 3MF-filer — se uppströmsproblem #12426 (segmenteringsfel på målade multi-extruder-filer) och #13386 (parameterintervall strikt valideringsavvisning). Bambu Studio rekommenderas tills uppströmsfixarna landar.',
+ useSlicerApi: 'Använd Slicer API',
+ useSlicerApiDescription: 'När aktiverat öppnar slicing-åtgärder den inbyggda slicing-dialogrutan och anropar slicer-API-sidecar. När avstängt (standard) skickas de vidare till slicern via URI-schema.',
+ slicerCard: 'Slicer',
+ orcaslicerApiUrl: 'OrcaSlicer sidecar-URL',
+ bambuStudioApiUrl: 'Bambu Studio sidecar-URL',
+ slicerStallTimeout: 'Slicer-timeout (minuter)',
+ slicerStallTimeoutDescription: 'Ge upp på en slicing efter denna tid utan framsteg från sidecaren. Tunga modeller som fortsätter rapportera framsteg avbryts aldrig, oavsett hur lång tid de tar. Sidecars som inte rapporterar framsteg använder denna tid som total tidsgräns istället.',
+ slicerApiUrlDescription: 'URL till slicing-API-sidecar-containern. Lämna tomt för att använda SLICER_API_URL / BAMBU_STUDIO_API_URL-miljövariablerna som standard.',
+ externalCameras: 'Externa kameror',
+ costTracking: 'Kostnadsspårning',
+ billingEnabled: 'Aktivera fakturering',
+ billingEnabledDescription: 'Debitera användare för utskrifter och aktivera finansfunktioner',
+ printerKillSwitch: 'Nödstoppsknapp för obehörig utskrift',
+ printerKillSwitchDescription: 'Stoppa omedelbart utskrifter som startar utan auktorisering.',
+ financeBudgetReset: 'Återställ månadsbudget för finanser',
+ financeBudgetResetDay: 'Återställningsdag',
+ financeBudgetResetDayHelp: 'För korta månader används den sista dagen i månaden för återställning.',
+ financeBudgetResetTimezone: 'Återställningstidszon',
+ financeBudgetResetTimezoneHelp: 'Budgetfönstrets start beräknas i denna tidszon.',
+ rebuildLedger: 'Regenerera bokföring',
+ rebuildLedgerStarted: 'Regenerering av bokföring påbörjad',
+ rebuildLedgerConfirmTitle: 'Regenerera bokföring?',
+ rebuildLedgerConfirmMessage:
+ 'Detta kommer att regenerera bokföringen för att reparera historiska saldovärden. Kör detta endast om du vet vad du gör.',
+ rebuildLedgerInProgress: 'Startar regenerering...',
+ printsOnly: 'Endast utskrifter',
+ totalConsumption: 'Total förbrukning',
+ dataManagement: 'Datahantering',
+ storageUsage: 'Lagringsanvändning',
+ storageUsageDescription: 'Fördelning av dataanvändning per kategori',
+ storageUsageTotal: 'Totalt',
+ storageUsageErrors: 'Felmeddelanden',
+ storageUsageOtherBreakdown: 'Övrigt (inkluderar statiska objekt, skript och konfigurationsfiler)',
+ storageUsageSystem: 'System',
+ storageUsageData: 'Data',
+ storageUsageUnavailable: 'Lagringsinformation ej tillgänglig',
+ clearNotificationLogsDescription: 'Ta bort notisloggar äldre än 30 dagar',
+ resetUiPreferencesDescription: 'Återställ sidofältsordning, tema, visningslägen och layoutinställningar. Skrivare, arkiv och inställningar påverkas inte.',
+ enableHomeAssistant: 'Aktivera Home Assistant',
+ enableMqtt: 'Aktivera MQTT',
+ useTls: 'Använd TLS',
+ enableMetricsEndpoint: 'Aktivera mätvärdesändpunkt',
+ availableMetrics: 'Tillgängliga mätvärden',
+ editUser: 'Redigera användare',
+ deleteUserTitle: 'Ta bort användare',
+ groupName: 'Gruppnamn',
+ // Placeholders
+ leaveEmptyForAnonymous: 'Lämna tomt för anonym',
+ leaveEmptyForNoAuth: 'Lämna tomt för ingen autentisering',
+ enterNewPassword: 'Ange nytt lösenord',
+ confirmNewPassword: 'Bekräfta nytt lösenord',
+ enterGroupName: 'Ange gruppnamn',
+ enterDescriptionOptional: 'Ange beskrivning (valfritt)',
+ enterCurrentPassword: 'Ange nuvarande lösenord',
+ enterNewPasswordMin6: 'Ange nytt lösenord (minst 6 tecken)',
+ toast: {
+ keyCopied: 'Nyckel kopierad till urklipp',
+ copyFailed: 'Kunde inte kopiera nyckel',
+ keyAddedToBrowser: 'Nyckel tillagd till API-läsaren',
+ clearLogsFailed: 'Kunde inte rensa loggar',
+ uiPreferencesReset: 'UI-inställningar återställda. Uppdaterar...',
+ authDisabled: 'Autentisering inaktiverad',
+ authDisableFailed: 'Kunde inte inaktivera autentisering',
+ apiKeyCreated: 'API-nyckel skapad',
+ apiKeyDeleted: 'API-nyckel borttagen',
+ userCreated: 'Användare skapad',
+ userUpdated: 'Användare uppdaterad',
+ userDeleted: 'Användare borttagen',
+ groupCreated: 'Grupp skapad',
+ groupUpdated: 'Grupp uppdaterad',
+ groupDeleted: 'Grupp borttagen',
+ fillRequiredFields: 'Fyll i alla obligatoriska fält',
+ passwordsDoNotMatch: 'Lösenorden matchar ej',
+ passwordTooShort: 'Lösenordet måste vara minst 8 tecken',
+ passwordNeedsUppercase: 'Lösenordet måste innehålla minst en versal',
+ passwordNeedsLowercase: 'Lösenordet måste innehålla minst en gemen',
+ passwordNeedsDigit: 'Lösenordet måste innehålla minst en siffra',
+ passwordNeedsSpecial: 'Lösenordet måste innehålla minst ett specialtecken',
+ enterGroupName: 'Ange ett gruppnamn',
+ settingsSaved: 'Inställningar sparade',
+ noPermissionUpdate: 'Du har inte behörighet att ändra inställningar',
+ cameraSettingsSaved: 'Kamerainställningar sparade',
+ enterCameraUrl: 'Ange URL för en kamera',
+ passwordChanged: 'Lösenord ändrat',
+ connectionFailed: 'Anslutning misslyckades',
+ testFailed: 'Test misslyckades',
+ cameraConnected: 'Kamera ansluten{{resolution}}',
+ cameraConnectedCoalesced: 'Kamera ansluten{{resolution}} (delad med en pågående inspelning)',
+ },
+ testConnection: 'Testa anslutning',
+ catalog: {
+ spoolCatalog: 'Rullkatalog',
+ spoolCatalogDescription: 'Tomma rullvikter efter märke/typ. Används för automatisk viktsökning när rullar läggs till.',
+ searchCatalog: 'Sök i katalogen...',
+ addNewEntry: 'Lägg till ny post',
+ namePlaceholder: 'Namn (t.ex. Bambu Lab - Plast)',
+ weight: 'Vikt',
+ type: 'Typ',
+ default: 'Standard',
+ custom: 'Anpassad',
+ noMatch: 'Inga poster matchar din sökning',
+ empty: 'Inga poster i katalogen',
+ deleteEntry: 'Ta bort post',
+ deleteConfirm: 'Är du säker på att du vill ta bort "{{name}}"?',
+ resetCatalog: 'Återställ katalog',
+ resetConfirm: 'Återställ katalog till standard? Detta kommer att ta bort alla anpassade poster.',
+ loadFailed: 'Kunde inte ladda rullkatalogen',
+ nameWeightRequired: 'Namn och vikt krävs',
+ entryAdded: 'Post tillagd',
+ addFailed: 'Kunde inte lägga till post',
+ entryUpdated: 'Post uppdaterad',
+ updateFailed: 'Kunde inte uppdatera post',
+ entryDeleted: 'Post borttagen',
+ deleteFailed: 'Kunde inte ta bort post',
+ resetSuccess: 'Katalog återställd till standard',
+ resetFailed: 'Kunde inte återställa katalog',
+ exported: 'Exporterade {{count}} poster',
+ imported: 'Importerade {{added}} poster ({{skipped}} hoppade över)',
+ importFailed: 'Kunde inte importera: ogiltigt JSON-format',
+ exportTooltip: 'Exportera katalog till JSON',
+ importTooltip: 'Importera katalog från JSON',
+ resetTooltip: 'Återställ till standard',
+ selectedCount: '{{count}} valda',
+ deleteSelected: 'Ta bort valda',
+ bulkDeleteConfirm: 'Är du säker på att du vill ta bort {{count}} poster?',
+ bulkDeleted: 'Tog bort {{count}} poster',
+ bulkDeleteFailed: 'Kunde inte ta bort poster',
+ material: 'Material',
+ spoolWeight: 'Rullvikt',
+ color: 'Färg',
+ updateSpoolWeight: 'Uppdatera rullvikt',
+ filamentUpdated: 'Filament uppdaterat',
+ filamentUpdateFailed: 'Kunde inte uppdatera filament',
+ filamentUpdateInvalid: 'Ogiltig filamentdata',
+ keepExistingSpoolWeight: 'Behåll tidigare vikt för befintliga rullar',
+ keepExistingSpoolWeightDesc: 'Rullar som redan skapats med denna filamenttyp behåller den gamla taravikten. Nya rullar använder den uppdaterade vikten.',
+ applyToAllSpools: 'Tillämpa på alla rullar',
+ applyToAllSpoolsDesc: 'Alla viktberäkningar för denna filamenttyp använder omedelbart den nya taravikten.',
+ },
+ colorCatalog: {
+ title: 'Färgkatalog',
+ description: 'Filamentfärger efter tillverkare/material. Används för automatisk färgsökning när rullar läggs till.',
+ searchColors: 'Sök färger...',
+ allManufacturers: 'Alla tillverkare',
+ addNewColor: 'Lägg till ny färg',
+ manufacturer: 'Tillverkare',
+ colorName: 'Färgnamn',
+ hex: 'Hex',
+ materialOptional: 'Material (valfritt)',
+ showing: 'Visar {{filtered}} av {{total}} färger',
+ noMatch: 'Inga färger matchar din sökning',
+ empty: 'Inga färger i katalogen',
+ deleteColor: 'Ta bort färg',
+ deleteConfirm: 'Är du säker på att du vill ta bort "{{name}}"?',
+ resetCatalog: 'Återställ färgkatalog',
+ resetConfirm: 'Återställ katalog till standard? Detta kommer att ta bort alla anpassade färger.',
+ sync: 'Synkronisera',
+ starting: 'Startar...',
+ syncTooltip: 'Synkronisera från FilamentColors.xyz (2000+ färger, kan ta en minut)',
+ loadFailed: 'Kunde inte ladda färgkatalogen',
+ fieldsRequired: 'Tillverkare, färgnamn och hex-färg krävs',
+ colorAdded: 'Färg tillagd',
+ addFailed: 'Kunde inte lägga till färg',
+ colorUpdated: 'Färg uppdaterad',
+ updateFailed: 'Kunde inte uppdatera färg',
+ colorDeleted: 'Färg borttagen',
+ deleteFailed: 'Kunde inte ta bort färg',
+ resetSuccess: 'Färgkatalog återställd till standard',
+ resetFailed: 'Kunde inte återställa katalog',
+ syncUpToDate: 'Redan uppdaterad ({{count}} färger kontrollerade)',
+ syncComplete: 'Lade till {{added}} nya färger ({{skipped}} fanns redan)',
+ syncError: 'Synkroniseringsfel',
+ syncFailed: 'Kunde inte synkronisera från FilamentColors.xyz',
+ exported: 'Exporterade {{count}} färger',
+ imported: 'Importerade {{added}} färger ({{skipped}} hoppade över)',
+ importFailed: 'Kunde inte importera: ogiltigt JSON-format',
+ selectedCount: '{{count}} valda',
+ deleteSelected: 'Ta bort valda',
+ bulkDeleteConfirm: 'Är du säker på att du vill ta bort {{count}} färger?',
+ bulkDeleted: 'Tog bort {{count}} färger',
+ bulkDeleteFailed: 'Kunde inte ta bort färger',
+ },
+ // General tab
+ dateFormat: 'Datumformat',
+ dateFormatUs: 'US (MM/DD/ÅÅÅÅ)',
+ dateFormatEu: 'EU (DD/MM/ÅÅÅÅ)',
+ dateFormatIso: 'ISO (ÅÅÅÅ-MM-DD)',
+ timeFormat: 'Tidsformat',
+ timeFormat12: '12-timmars (3:30 PM)',
+ timeFormat24: '24-timmars (15:30)',
+ defaultPrinter: 'Standardskrivare',
+ defaultPrinterDescription: 'Förvälj denna skrivare för uppladdningar, omutskrifter och andra operationer.',
+ slicerBambuStudio: 'Bambu Studio',
+ slicerOrcaSlicer: 'OrcaSlicer',
+ sidebarOrderDescription: 'Använd sidofältet för att ordna om objekt, återställa synlighet och hantera anpassade länkar.',
+ setDefault: 'Sätt som standard',
+ sidebarOrderSetDefaultHint: 'Sätt som standard tillämpar den aktuella menyordningen på användare som inte har anpassat sin.',
+ sidebarDefaultSet: 'Standardmenyordning har ställts in.',
+ sidebarDefaultCleared: 'Standardmenyordning rensad.',
+ sidebarDefaultFailed: 'Kunde inte ställa in standardmenyordning.',
+ reset: 'Återställ',
+ // Appearance
+ darkMode: 'Mörkt läge',
+ lightMode: 'Ljust läge',
+ active: '(aktivt)',
+ background: 'Bakgrund',
+ accent: 'Accent',
+ style: 'Stil',
+ bgNeutral: 'Neutral',
+ bgWarm: 'Varm',
+ bgCool: 'Kall',
+ bgOled: 'OLED Svart',
+ bgSlate: 'Skifferblå',
+ bgForest: 'Skogsgrön',
+ accentGreen: 'Grön',
+ accentTeal: 'Blågrön',
+ accentBlue: 'Blå',
+ accentOrange: 'Orange',
+ accentPurple: 'Lila',
+ accentRed: 'Röd',
+ styleClassic: 'Klassisk',
+ styleGlow: 'Sken',
+ styleVibrant: 'Livfull',
+ themeToggleHint: 'Växla mellan mörkt, ljust och systemläge med ikonen i sidomenyn.',
+ progressInTitle: 'Utskriftsförlopp i flik',
+ progressInTitleDescription: 'Visa den aktiva utskriftens procentförlopp och en ring i webbläsarfliken.',
+ // Archive
+ autoArchivePrints: 'Auto-arkivera utskrifter',
+ autoArchiveDescription: 'Spara automatiskt 3MF-filer när utskrifter slutförs',
+ saveThumbnailsDescription: 'Extrahera och spara förhandsvisningsbilder från 3MF-filer',
+ captureFinishPhotoDescription: 'Ta ett foto från skrivarkameran när utskriften slutförs. Bambuddy spelar in en kort timelapse under utskriften så att fotot kan hämtas från ögonblicket innan byggplattan sänks; timelapse-filen behålls om du aktiverat timelapse för denna utskrift, annars tas den automatiskt bort efter att fotot tagits.',
+ finishPhotoRestorePlate: 'Återställ platta för foto',
+ finishPhotoRestorePlateDescription: 'Skrivaren sänker byggplattan cirka 100 mm när en utskrift slutförs, vilket lämnar den färdiga utskriften under kamerans bildruta. Bambuddy höjer tillbaka den precis ovanför det sista utskrivna skiktet, tar fotot och sänker den igen. Hoppas över när utskriftshöjden är okänd eller om ett annat jobb är köat.',
+ ffmpegNotInstalled: 'ffmpeg är inte installerat',
+ ffmpegRequired: 'Kamerainspelning kräver ffmpeg. Installera det via brew install ffmpeg (macOS) eller apt install ffmpeg (Linux).',
+ // Camera
+ camera: 'Kamera',
+ cameraViewMode: 'Kameravy',
+ cameraOverlayDescription: 'Kameran öppnas i en storleksändringsbart överlägg på huvudskärmen',
+ cameraWindowDescription: 'Kameran öppnas i ett separat webbläsarfönster',
+ externalCamerasDescription: 'Konfigurera externa kameror för att ersätta den inbyggda skrivarkameran. Stöder MJPEG-strömmar, RTSP, HTTP-ögonblicksbilder och USB-kameror (V4L2). När aktiverad används den externa kameran för live-vy och färdigbilder.',
+ cameraPlaceholderUsb: 'Enhetssökväg (/dev/video0)',
+ cameraPlaceholderUrl: 'Kamera-URL (rtsp://... eller http://...)',
+ cameraTypeMjpeg: 'MJPEG-ström',
+ cameraTypeRtsp: 'RTSP-ström',
+ cameraTypeSnapshot: 'HTTP-ögonblicksbild',
+ cameraTypeUsb: 'USB-kamera (V4L2)',
+ cameraSnapshotUrl: 'Ögonblicksbild-URL (valfritt)',
+ cameraSnapshotUrlPlaceholder: 'http://192.168.1.61:1984/api/frame.jpeg?src=printer',
+ cameraSnapshotUrlHelp: 'Enkelbild-URL som används för notisminibilder, färdigbilder, layer-timelapse-bilder och plattdetektering. Timelapse och plattdetektering kräver varsin per-skrivare-växel — denna URL är bara bildkällan de använder när aktiva. Lämna tomt för att fånga från live-strömmen ovan. Användbart för go2rtc (/api/frame.jpeg) och IP-kameror med en dedikerad ögonblicksbild-ändpunkt.',
+ cameraRotation: 'Rotation',
+ test: 'Test',
+ connected: 'Ansluten',
+ disconnected: 'Frånkopplad',
+ // Cost tracking
+ currency: 'Valuta',
+ defaultFilamentCost: 'Standardfilamentkostnad (per kg)',
+ electricityCost: 'Elkostnad per kWh',
+ energyDisplayMode: 'Energivisningsläge',
+ energyModePrintDescription: 'Instrumentpanelen visar summan av energin som använts under utskrifter',
+ energyModeTotalDescription: 'Instrumentpanelen visar livstidsenergi från smarta kontakter',
+ // File Manager
+ fileManager: 'Filhanterare',
+ createArchiveEntry: 'Skapa arkivpost vid utskrift',
+ createArchiveEntryDescription: 'När du skriver ut från Filhanteraren, skapa en arkivpost',
+ lowDiskSpaceWarning: 'Varning för lågt diskutrymme',
+ lowDiskSpaceDescription: 'Visa en varning när tillgängligt diskutrymme är under detta tröskelvärde',
+ // Updates
+ printerFirmware: 'Skrivarens firmware',
+ checkFirmwareDescription: 'Kontrollera om det finns firmwareuppdateringar för skrivaren från Bambu Lab',
+ bambuddySoftware: 'Bambuddy-programvara',
+ autoCheckDescription: 'Sök automatiskt efter nya versioner vid start',
+ checkNow: 'Sök nu',
+ updateAvailableVersion: 'Uppdatering tillgänglig: v{{version}}',
+ releaseNotes: 'Versionsinformation',
+ updateViaDocker: 'Uppdatera via Docker Compose:',
+ composeDirectory: 'Compose-katalog',
+ composeDirectoryHint: 'Mappen som innehåller din docker-compose.yml. Lämna tomt för att utelämna cd.',
+ copyUpdateCommand: 'Kopiera uppdateringskommando',
+ updateViaHomeAssistant: 'Uppdateringar hanteras av Home Assistant Supervisor. Öppna Inställningar → Tillägg → Bambuddy i Home Assistant för att installera den nya versionen.',
+ updateViaWindowsInstaller: 'Windows-installationer uppdateras genom att köra installationsprogrammet igen. Ladda ner den nya versionen nedan — din data, inställningar och skrivare bevaras.',
+ downloadWindowsInstaller: 'Ladda ner installationsprogram för v{{version}}',
+ installUpdate: 'Installera uppdatering',
+ latestVersionRunning: 'Du kör den senaste versionen',
+ failedToCheckUpdates: 'Kunde inte kontrollera uppdateringar: {{error}}',
+ // Data Management
+ backupRestore: 'Säkerhetskopia och återställning',
+ backupRestoreDescription: 'Exportera/importera inställningar och konfigurera GitHub-säkerhetskopia',
+ goToBackup: 'Gå till säkerhetskopia',
+ // Network tab
+ externalUrl: 'Extern URL',
+ externalUrlDescription: 'Den externa URL:en där Bambuddy kan nås. Används för notisbilder och externa integrationer.',
+ bambuddyUrl: 'Bambuddy-URL',
+ externalUrlHint: 'Inkludera protokoll och port (t.ex. http://192.168.1.100:8000)',
+ ftpRetry: 'FTP-omförsök',
+ ftpRetryDescription: 'Försök igen med FTP-operationer när skrivarens WiFi är opålitlig. Gäller för 3MF-nedladdningar, utskriftsuppladdningar, timelapse-nedladdningar och firmwareuppdateringar.',
+ autoRetryDescription: 'Försök automatiskt igen med misslyckade FTP-operationer',
+ retryAttempts: 'Försök igen',
+ retryDelay: 'Fördröjning mellan försök',
+ connectionTimeout: 'Anslutningstimeout',
+ time_one: '{{count}} gång',
+ time_other: '{{count}} gånger',
+ second_one: '{{count}} sekund',
+ second_other: '{{count}} sekunder',
+ nSeconds: '{{count}} sekunder',
+ increaseForWeakWifi: 'Öka för skrivare med svag WiFi',
+ // Home Assistant
+ homeAssistant: 'Home Assistant',
+ homeAssistantFullDescription: 'Anslut till Home Assistant för att styra smarta kontakter via HA:s REST API. Stöder switch, light, input_boolean och script-entiteter.',
+ homeAssistantUrl: 'Home Assistant-URL',
+ longLivedAccessToken: 'Långlivad åtkomst-token',
+ haTokenHint: 'Skapa en token i HA: Profil → Långlivade åtkomst-tokens → Skapa token',
+ connectionSuccessful: 'Anslutning lyckades',
+ connectionFailed: 'Anslutning misslyckades',
+ haConnectionSuccess: 'Anslöt framgångsrikt till Home Assistant.',
+ haConnectionFailed: 'Kunde inte ansluta till Home Assistant.',
+ // MQTT
+ mqttPublishing: 'MQTT-publicering',
+ mqttDescription: 'Publicera Bambuddy-händelser till en extern MQTT-mäklare för integration med Node-RED, Home Assistant och andra automatiseringssystem.',
+ mqttEnableDescription: 'Publicera händelser till extern MQTT-mäklare',
+ brokerHostname: 'Mäklarens värdnamn',
+ port: 'Port',
+ usernameOptional: 'Användarnamn (valfritt)',
+ passwordOptional: 'Lösenord (valfritt)',
+ topicPrefix: 'Ämnesprefix',
+ topicPrefixHint: 'Ämnen kommer att vara: {{prefix}}/printers//status, etc.',
+ // Prometheus
+ prometheusMetrics: 'Prometheus-mätvärden',
+ prometheusEndpointDescription: 'Exponera skrivarmätvärden på /api/v1/metrics för Prometheus/Grafana-övervakning.',
+ bearerTokenOptional: 'Bärar-token (valfritt)',
+ bearerTokenHint: 'Om inställt måste förfrågningar inkludera Authorization: Bearer ',
+ metricsConnectionStatus: 'Anslutningsstatus',
+ metricsPrinterState: 'Skrivarstatus (inaktiv/skriver ut/etc)',
+ metricsPrintProgress: 'Utskriftsförlopp 0-100%',
+ metricsBedTemp: 'Byggplattstemperatur',
+ metricsNozzleTemp: 'Munstyckstemperatur',
+ metricsPrintsTotal: 'Totala utskrifter efter resultat',
+ metricsMore: '...och mer (skikt, fläktar, kö, filamentanvändning)',
+ // Smart Plugs
+ smartPlugsDescription: 'Anslut smarta kontakter (Tasmota eller Home Assistant) för att automatisera strömkontroll och spåra energiförbrukning för dina skrivare.',
+ allOn: 'Sätt på alla',
+ allOff: 'Stäng av alla',
+ addSmartPlug: 'Lägg till smart kontakt',
+ energySummary: 'Energisammanfattning',
+ currentPower: 'Aktuell effekt',
+ plugsOnline: '{{reachable}}/{{total}} kontakter online',
+ today: 'Idag',
+ yesterday: 'Igår',
+ total: 'Totalt',
+ enablePlugsForSummary: 'Aktivera kontakter för att se energisammanfattning',
+ addNotificationProvider: 'Lägg till',
+ // Users
+ systemBadge: '(System)',
+ creating: 'Skapar...',
+ changing: 'Ändrar...',
+ deleteUserAndItems: 'Ta bort användare OCH deras objekt',
+ deleteUserKeepItems: 'Ta bort användare, behåll objekt (blir ägarlösa)',
+ ok: 'OK',
+
+ // 2FA settings
+ twoFa: {
+ totpTitle: 'Autentiseringsapp (TOTP)',
+ totpDesc: 'Använd en autentiseringsapp som Google Authenticator, Aegis eller Authy.',
+ emailOtpTitle: 'E-post OTP',
+ emailOtpDesc: 'Skicka en engångskod till {{email}} när du loggar in.',
+ emailOtpNoEmail: 'Lägg till en e-postadress till ditt konto för att aktivera denna metod.',
+ addEmailFirst: 'Ditt konto har ingen e-postadress. Be en admin att lägga till en innan du aktiverar e-post OTP.',
+ setupTotp: 'Ställ in autentiseringsapp',
+ setupAuthApp: 'Ställ in autentiseringsapp',
+ setupInstructions: 'Skanna QR-koden nedan med din autentiseringsapp, bekräfta sedan med en kod.',
+ manualEntry: 'Kan du inte skanna? Ange denna hemlighet manuellt:',
+ scannedContinue: 'Jag har skannat koden — fortsätt',
+ enterCodeToConfirm: 'Ange den 6-siffriga koden från din autentiseringsapp för att bekräfta konfigurationen.',
+ activate: 'Aktivera',
+ disableTotp: 'Inaktivera autentiseringsapp',
+ disableConfirmHint: 'Ange en giltig TOTP-kod eller en reservkod för att inaktivera autentiseraren.',
+ totpDisabled: 'Autentiseringsapp inaktiverad.',
+ emailOtpEnabled: 'E-post OTP aktiverad.',
+ emailOtpDisabled: 'E-post OTP inaktiverad.',
+ smtpRequired: 'Konfigurera och testa SMTP-inställningar först.',
+ invalidCode: 'Ogiltig kod. Försök igen.',
+ enableEmailOtp: 'Aktivera e-post OTP',
+ disableEmailOtp: 'Inaktivera e-post OTP',
+ emailSetupEnterCode: 'En verifieringskod har skickats till din e-postadress. Ange den nedan för att bekräfta att du äger denna inkorg.',
+ verifyAndEnable: 'Verifiera och aktivera',
+ emailDisablePasswordHint: 'Ange ditt kontolösenord för att bekräfta inaktiveringen av e-post OTP.',
+ passwordPlaceholder: 'Ange ditt lösenord',
+ backupCodesTitle: 'Spara dina reservkoder',
+ backupCodesWarning: 'Spara dessa koder på en säker plats. Varje kod kan endast användas en gång och de kommer inte att visas igen.',
+ backupCodesRemaining: '{{count}} reservkoder kvar',
+ savedCodes: 'Jag har sparat mina koder',
+ regenBackup: 'Regenerera reservkoder',
+ regenBackupHint: 'Ange din nuvarande TOTP-kod för att generera 10 nya reservkoder. Alla befintliga reservkoder kommer att ogiltigförklaras.',
+ newBackupCodes: 'Nya reservkoder',
+ linkedAccounts: 'Länkade SSO-konton',
+ linkedAccountsDesc: 'Dessa externa identitetsleverantörer är länkade till ditt konto.',
+ oidcUnlinked: 'Konto bortkopplat.',
+ },
+
+ // Session Policy (#1706) — admin-configurable session lifetime ceiling.
+ sessionPolicy: {
+ title: 'Sessionspolicy',
+ description: 'Maximal sessionstid för nya användarinloggningar. Redan utfärdade tokens behåller sin ursprungliga utgångstid.',
+ preset24h: '24 timmar',
+ preset7d: '7 dagar',
+ preset30d: '30 dagar',
+ customHoursLabel: 'Anpassad sessionstid i timmar',
+ hoursSuffix: 'timmar',
+ warning: 'Längre sessioner minskar skyddet mot automatisk utloggning. Rekommenderas endast för betrodda enanvändarutplaceringar.',
+ },
+
+ // OIDC provider settings
+ oidc: {
+ title: 'SSO / OIDC-leverantörer',
+ desc: 'Konfigurera OpenID Connect-leverantörer för att tillåta enstegsinloggning via externa identitetsleverantörer.',
+ addProvider: 'Lägg till leverantör',
+ newProvider: 'Ny leverantör',
+ empty: 'Inga OIDC-leverantörer konfigurerade än.',
+ created: 'Leverantör skapad.',
+ updated: 'Leverantör uppdaterad.',
+ deleted: 'Leverantör borttagen.',
+ refreshIcon: 'Uppdatera ikon',
+ removeIcon: 'Ta bort ikon',
+ iconRefreshed: 'Ikon uppdaterad.',
+ iconRemoved: 'Ikon borttagen.',
+ iconFetchFailed: 'Kunde inte hämta ikon från leverantörs-URL:en.',
+ deleteTitle: 'Ta bort leverantör',
+ deleteMessage: 'Ta bort "{{name}}"? Alla länkade användarkonton kommer att kopplas från.',
+ form: {
+ name: 'Visningsnamn',
+ issuerUrl: 'Utgivar-URL',
+ clientId: 'Klient-ID',
+ clientSecret: 'Klienthemlighet',
+ scopes: 'Omfång',
+ iconUrl: 'Ikon-URL (valfritt)',
+ enabled: 'Aktiverad',
+ autoCreate: 'Auto-skapa användare',
+ autoCreateDesc: 'Skapa automatiskt ett lokalt konto vid första inloggning.',
+ autoLink: 'Auto-länka befintliga konton',
+ autoLinkDesc: 'Länka befintliga lokala konton genom att matcha e-post vid första inloggning.',
+ secretHint: 'lämna tomt för att behålla nuvarande',
+ secretPlaceholder: 'ny hemlighet',
+ emailClaim: 'E-postanspråk',
+ emailClaimDesc: 'JWT-anspråk som används som e-postidentitet. Använd "preferred_username" eller "upn" för Azure Entra ID (som inte skickar email_verified). Använd endast betrodda anspråksnamn.',
+ emailClaimPlaceholder: 'email',
+ emailClaimCustomClaimAutoLinkWarning: 'Anpassade anspråk är säkra för auto-länk endast när värdet administreras av hyresvärden (t.ex. Azure Entra ID upn / preferred_username). Aktivera inte auto-länk om din IdP tillåter användare att självhävda detta anspråk.',
+ requireEmailVerified: 'Kräv verifierad e-post',
+ requireEmailVerifiedDesc: 'Acceptera endast e-postanspråket när leverantören markerar det som verifierat.',
+ requireEmailVerifiedWarning: 'Varning: e-post kommer att accepteras även utan verifiering. Använd endast med betrodda leverantörer.',
+ requireEmailVerifiedAutoLink: 'Inaktivera auto-länk först för att ändra denna inställning.',
+ defaultGroup: 'Standardgrupp',
+ defaultGroupDesc: 'Grupp som tilldelas auto-skapade användare. Faller tillbaka till Åskådare om inte inställt.',
+ defaultGroupViewersFallback: 'Åskådare (standard)',
+ autologin: 'Autologin',
+ autologinDesc: 'Omdirigera icke autentiserade besökare direkt till denna leverantör. Endast en leverantör kan ha denna flagga.',
+ },
+ },
+
+ encryption: {
+ title: 'MFA-krypteringsstatus',
+ enabledFromEnv: 'Kryptering i vila aktiverad (nyckel från MFA_ENCRYPTION_KEY-miljövariabel)',
+ enabledFromFile: 'Kryptering i vila aktiverad (nyckel laddad från datakatalog)',
+ enabledGenerated: 'Kryptering i vila aktiverad med auto-genererad nyckel',
+ notConfigured: 'Kryptering i vila ej konfigurerad',
+ notConfiguredDesc: 'TOTP-hemligheter och OIDC client_secrets lagras i klartext. Sätt MFA_ENCRYPTION_KEY eller starta om Bambuddy med en skrivbar datakatalog för att auto-generera en.',
+ allEncrypted: 'Alla MFA-hemligheter är krypterade i vila.',
+ legacyRowsLabel: 'Äldre klartextrader',
+ encryptedRowsLabel: 'Krypterade rader',
+ legacyRowsWarning: '{{count}} äldre klartextrad(er) upptäckta. Spara om OIDC-leverantören eller registrera om användarens autentiseringsapp för att migrera till krypterad lagring.',
+ backupHint: 'Den auto-genererade nyckeln lagras på DATA_DIR/.mfa_encryption_key och ingår i lokala säkerhetskopior. Håll dina säkerhetskopior säkra eller sätt MFA_ENCRYPTION_KEY explicit.',
+ decryptionBrokenTitle: 'Krypteringsnyckel saknas',
+ decryptionBrokenError: '{{count}} krypterad(e) post(er) kan inte dekrypteras eftersom krypteringsnyckeln inte längre är tillgänglig. Återställ den tidigare MFA_ENCRYPTION_KEY eller DATA_DIR/.mfa_encryption_key för att återställa.',
+ migrationErrorWarning: '{{count}} äldre rad(er) kunde inte krypteras om vid start. Kontrollera serverloggar och starta om Bambuddy för att försöka igen.',
+ },
+
+ // Slicer Pipeline limits (#1425 PR C). Admin-tunable cap that constrains
+ // the copies input in the Run-with-pipeline modal. Lives on the Workflow
+ // tab's Queue & Dispatch sub-tab.
+ pipelineLimits: {
+ title: 'Gränser för slicer-pipeline',
+ maxCopiesLabel: 'Max kopior per körning',
+ maxCopiesDesc: 'Övre gräns för antalet kopior som operatörer kan begära vid körning av en pipeline. Server-sidans hårda gräns är 1000.',
+ },
+
+ // Slicer Pipelines (#1425): list/edit/delete preset bundles users saved
+ // from the Slice dialog. Lives in Settings → Workflow → Pipelines sub-tab.
+ pipelines: {
+ title: 'Slicer-pipelines',
+ subtitle: 'Återanvändbara inställningspaket (skrivare + process + filament + byggplattstyp). Spara en från slicing-dialogrutan och använd den med ett klick på nästa fil.',
+ loading: 'Laddar pipelines...',
+ loadError: 'Kunde inte ladda pipelines.',
+ confirmDelete: 'Ta bort denna pipeline? Detta kan inte ångras.',
+ staleWarning: 'En eller flera refererade inställningar finns inte längre. Spara om denna pipeline från slicing-dialogrutan för att åtgärda.',
+ empty: {
+ title: 'Inga pipelines än.',
+ howto: 'Öppna slicing-dialogrutan för någon fil, välj din skrivare/process/filament/byggplattstyp, klicka sedan på "Spara som pipeline". Dina sparade pipelines kommer att visas här.',
+ },
+ field: {
+ name: 'Pipeline-namn',
+ description: 'Beskrivning',
+ targetPrinter: 'Målskrivare',
+ noTarget: '— Inget mål —',
+ // PR C
+ targetKind: 'Måltyp',
+ targetKindSpecific: 'Specifik skrivare',
+ targetKindClass: 'Skrivarklass',
+ targetModelClass: 'Skrivarmodell',
+ fanoutStrategy: 'Fördelningsstrategi',
+ fanout: {
+ max_parallel: 'Max parallell — fördela över alla lediga matchande skrivare',
+ round_robin: 'Rotationskö — cykla genom berättigade skrivare',
+ fill_one_first: 'Fyll en först — fäst alla kopior till en skrivare',
+ },
+ // Short labels for the inline target chip on each pipeline card.
+ // The verbose ones above explain the strategy in the editor; the
+ // card just needs a compact reminder of which one is in use.
+ fanoutShort: {
+ max_parallel: 'parallell',
+ round_robin: 'rotationskö',
+ fill_one_first: 'fyll en först',
+ },
+ },
+ action: {
+ save: 'Spara',
+ cancel: 'Avbryt',
+ rename: 'Döp om',
+ delete: 'Ta bort',
+ },
+ slot: {
+ printer: 'Skrivare',
+ process: 'Process',
+ filament: 'Filament',
+ filamentN: 'Filament {{n}}',
+ filamentAll: 'Alla {{n}} fack',
+ bed: 'Byggplatta',
+ },
+ // PR C polish — grouped sections in the card body, plus the
+ // panel-level search + filter row.
+ group: {
+ profiles: 'Profiler',
+ filaments: 'Filament',
+ },
+ searchPlaceholder: 'Sök pipelines...',
+ filterTargetType: 'Filtrera efter måltyp',
+ filterTarget: 'Filtrera efter mål',
+ filter: {
+ all: 'Alla mål',
+ noTarget: 'Inget mål inställt',
+ count: '{{shown}} / {{total}}',
+ noMatches: 'Inga pipelines matchar de aktuella filtren.',
+ },
+ toast: {
+ saved: 'Pipeline sparad',
+ saveFailed: 'Kunde inte spara',
+ deleted: 'Pipeline borttagen',
+ deleteFailed: 'Kunde inte ta bort',
+ },
+ // PR B target binding + last-run summary
+ noTargetHint: 'Sätt en målskrivare för att köra denna',
+ noTargetWarning: 'Sätt en målskrivare innan du kör denna pipeline.',
+ runs: {
+ lastRun: 'Senaste körning',
+ status: {
+ queued: 'köad',
+ slicing: 'slicar',
+ dispatching: 'skickar',
+ in_progress: 'skriver ut',
+ completed: 'slutförd',
+ failed: 'misslyckad',
+ partial_failure: 'delvis misslyckad',
+ cancelled: 'avbruten',
+ },
+ },
+ },
+ },
+
+ // Notifications (for push notifications)
+ notification: {
+ printStarted: {
+ title: 'Utskrift startad',
+ body: '{{printer}}: {{filename}} har börjat skrivas ut',
+ },
+ printCompleted: {
+ title: 'Utskrift slutförd',
+ body: '{{printer}}: {{filename}} slutfördes',
+ },
+ printFailed: {
+ title: 'Utskrift misslyckades',
+ body: '{{printer}}: {{filename}} misslyckades',
+ },
+ printStopped: {
+ title: 'Utskrift stoppad',
+ body: '{{printer}}: {{filename}} stoppades',
+ },
+ printProgress: {
+ title: 'Utskriftsförlopp',
+ body: '{{printer}}: {{filename}} är {{percent}}% klar',
+ },
+ printerOffline: {
+ title: 'Skrivare offline',
+ body: '{{printer}} är offline',
+ },
+ printerError: {
+ title: 'Skrivarfel',
+ body: '{{printer}}: {{error}}',
+ },
+ filamentLow: {
+ title: 'Lågt filament',
+ body: '{{printer}}: Filamentet håller på att ta slut',
+ },
+ maintenanceDue: {
+ title: 'Underhåll uteblivet',
+ body: '{{printer}}: {{items}} behöver uppmärksamhet',
+ },
+ },
+
+ // Errors
+errors: {
+ generic: 'Något gick fel',
+ networkError: 'Nätverksfel. Kontrollera din anslutning.',
+ notFound: 'Hittades ej',
+ unauthorized: 'Ej behörig',
+ serverError: 'Serverfel',
+ validationError: 'Kontrollera din inmatning',
+ printerConnectionFailed: 'Kunde inte ansluta till skrivare',
+ saveFailed: 'Kunde inte spara ändringar',
+ deleteFailed: 'Kunde inte ta bort',
+ loadFailed: 'Kunde inte ladda data',
+ },
+
+ // HMS Errors modal
+ hmsErrors: {
+ title: 'Fel - {{name}}',
+ noErrors: 'Inga fel',
+ viewOnWiki: 'Visa på Bambu Lab Wiki',
+ mqttVerifyFailedRemedy: 'Aktivera utvecklarläge på skrivaren, starta om skrivaren och starta sedan jobbet igen.',
+ unknownCode: 'Okänd HMS-kod — se Bambu Lab wiki för detaljer.',
+ clearInstructions: 'Rensa fel på skrivaren för att avfärda dem här.',
+ clearErrors: 'Rensa fel',
+ clearSuccess: 'HMS-fel rensade',
+ clearFailed: 'Kunde inte rensa HMS-fel',
+ actionSuccess: 'Åtgärd skickad till skrivare',
+ actionFailed: 'Kunde inte skicka åtgärd',
+ runoutExpectedSlot: 'Filamentet tog slut i {{ranOut}}. Skrivaren väntar nu på kompatibelt filament i {{expected}}. Sätt in en rulle i {{expected}} och välj sedan Försök igen.',
+ runoutExpectedSlotOnly: 'Skrivaren väntar på kompatibelt filament i {{expected}}. Sätt in en rulle där och välj sedan Försök igen.',
+ runoutSlotUnknown: 'Filament tog slut och utskriften är pausad. Bambuddy kunde inte avgöra vilket fack skrivaren nu väntar på — kontrollera skrivarens skärm för det begärda facket.',
+ actions: {
+ RESUME_PRINTING: "Fortsätt utskrift",
+ RESUME_PRINTING_DEFECTS: "Fortsätt (defekter acceptabla)",
+ RESUME_PRINTING_PROBELM_SOLVED: "Fortsätt (problem löst)",
+ STOP_PRINTING: "Stoppa utskrift",
+ CHECK_ASSISTANT: "Kontrollera assistent",
+ FILAMENT_EXTRUDED: "Filament extruderat, fortsätt",
+ RETRY_FILAMENT_EXTRUDED: "Inte extruderat än, försök igen",
+ CONTINUE: "Klar, fortsätt",
+ LOAD_VIRTUAL_TRAY: "Ladda filament",
+ OK_BUTTON: "OK",
+ FILAMENT_LOAD_RESUME: "Filament laddat, fortsätt",
+ JUMP_TO_LIVEVIEW: "Visa livevy",
+ NO_REMINDER_NEXT_TIME: "Ingen påminnelse nästa gång",
+ REFRESH_NOZZLE: "Kontrollera om",
+ IGNORE_NO_REMINDER_NEXT_TIME: "Ignorera. Ingen påminnelse nästa gång",
+ IGNORE_RESUME: "Ignorera och fortsätt",
+ PROBLEM_SOLVED_RESUME: "Problemet löst och fortsätt",
+ TURN_OFF_FIRE_ALARM: "Förstått, stäng av brandlarmet.",
+ RETRY_PROBLEM_SOLVED: "Försök igen (problem löst)",
+ CANCLE: "Avbryt",
+ STOP_DRYING: "Stoppa torkning",
+ PROCEED: "Fortsätt",
+ OK_JUMP_RACK: "OK",
+ ABORT: "Avbryt",
+ DISABLE_PURIFICATION: "Inaktivera rening för denna utskrift",
+ DONT_REMIND_NEXT_TIME: "Påminn mig inte nästa gång",
+ DBL_CHECK_CANCEL: "Avbryt",
+ DBL_CHECK_DONE: "Klar",
+ DBL_CHECK_RETRY: "Försök igen",
+ DBL_CHECK_RESUME: "Fortsätt",
+ DBL_CHECK_OK: "Bekräfta",
+ REMOVE_CLOSE_BTN: "Stäng",
+ }
+ },
+
+ // MQTT Debug modal
+ mqttDebug: {
+ title: 'MQTT-felsökningslogg',
+ searchPlaceholder: 'Sök ämne eller nyttolast...',
+ noMessages: 'Inga meddelanden loggade än',
+ startLoggingHint: 'Klicka på "Starta loggning" för att börja fånga MQTT-meddelanden',
+ noMessagesMatch: 'Inga meddelanden matchar ditt filter',
+ adjustFilterHint: 'Försök justera din sökning eller filterkriterier',
+ incoming: 'Inkommande',
+ outgoing: 'Utgående',
+ loggingStopped: 'Loggning stoppad',
+ loggingActive: 'Loggning aktiv - meddelanden kommer att uppdateras automatiskt',
+ startLogging: 'Starta loggning',
+ stopLogging: 'Stoppa loggning',
+ clearLog: 'Rensa logg',
+ topic: 'Ämne',
+ timestamp: 'Tidsstämpel',
+ direction: 'Riktning',
+ all: 'Alla',
+ },
+
+ // Printer File Manager modal (printer internal storage)
+ printerFiles: {
+ zipStarted_one: 'Startade ZIP-nedladdning för {{count}} fil',
+ zipStarted_other: 'Startade ZIP-nedladdning för {{count}} files',
+ zipPartial: 'Startade ZIP-nedladdning med {{successful}} av {{total}} filer; resten kunde inte hämtas',
+ downloadFailed: 'Nedladdning misslyckades: {{error}}',
+ unknownError: 'Okänt fel',
+ shiftSelectHint: 'Skift-klicka för att välja ett intervall',
+ selectFile: 'Markera {{name}}',
+ deselectFile: 'Avmarkera {{name}}',
+ title: 'Filhanterare',
+ storageUsed: 'Använt:',
+ storageFree: 'Ledigt:',
+ filterPlaceholder: 'Filtrera filer...',
+ deleteButton: 'Ta bort',
+ deleteFiles: 'Ta bort {{count}} filer',
+ deleteFileConfirm: 'Ta bort "{{name}}"? Detta kan inte ångras.',
+ deleteFilesConfirm: 'Ta bort {{count}} markerade filer? Detta kan inte ångras.',
+ noFiles: 'Inga filer på skrivaren',
+ printerUnavailable: 'Skrivarfiltjänsten är inte tillgänglig. Försök igen när skrivaren är tillgänglig.',
+ loadingFiles: 'Laddar filer...',
+ failedToLoad: 'Kunde inte ladda filer',
+ toast: {
+ filesDeleted: 'Tog bort {{count}} fil(er)',
+ deleteFailed: 'Kunde inte ta bort: {{error}}',
+ },
+ },
+
+ // Confirmations
+ confirm: {
+ delete: 'Är du säker på att du vill ta bort detta?',
+ unsavedChanges: 'Du har osparade ändringar. Är du säker på att du vill lämna?',
+ clearQueue: 'Är du säker på att du vill rensa kön?',
+ },
+
+ // Login page
+ login: {
+ title: 'Bambuddy-inloggning',
+ subtitle: 'Logga in på ditt konto',
+ username: 'Användarnamn',
+ usernamePlaceholder: 'Ange ditt användarnamn',
+ usernameOrEmail: 'Användarnamn eller e-post',
+ usernameOrEmailPlaceholder: 'Användarnamn eller @ e-post',
+ password: 'Lösenord',
+ passwordPlaceholder: 'Ange ditt lösenord',
+ signIn: 'Logga in',
+ signingIn: 'Loggar in...',
+ rememberMe: 'Kom ihåg mig',
+ forgotPassword: 'Glömt ditt lösenord?',
+ autologinFailed: 'Automatisk SSO-inloggning misslyckades. Välj en leverantör nedan för att fortsätta.',
+ localDisabledNotice: 'Lokal inloggning är inaktiverad. Använd en av SSO-leverantörerna nedan.',
+ loginSuccess: 'Inloggad',
+ loginFailed: 'Inloggning misslyckades',
+ enterCredentials: 'Ange användarnamn och lösenord',
+ enterEmail: 'Ange din e-postadress',
+ oidcLoginFailed: 'OIDC-inloggning misslyckades',
+ oidcErrors: {
+ providerError: 'Identitetsleverantören returnerade ett fel',
+ missingParameters: 'OIDC-återanrop saknar obligatoriska parametrar',
+ invalidState: 'OIDC-status är ogiltig eller har redan använts',
+ stateExpired: 'OIDC-inloggningssession har löpt ut — försök igen',
+ providerNotFound: 'OIDC-leverantör hittades ej',
+ discoveryFailed: 'Kunde inte hämta OIDC-upptäcktsdokument',
+ invalidDiscovery: 'OIDC-upptäcktsdokument är ogiltigt',
+ networkError: 'Nätverksfel under OIDC-tokenutbyte',
+ badResponse: 'Oväntat svar under OIDC-tokenutbyte',
+ noIdToken: 'OIDC-leverantören returnerade ingen ID-token',
+ validationFailed: 'OIDC-tokenvalidering misslyckades',
+ nonceMismatch: 'OIDC-nonce-felmatchning — möjlig replay-attack',
+ missingSubClaim: 'OIDC-token saknar sub-anspråk',
+ noLinkedAccount: 'Ingen lokal användare är länkad till denna OIDC-identitet',
+ accountInactive: 'Ditt konto är inaktivt',
+ userResolutionFailed: 'Kunde inte lösa ditt konto',
+ internalError: 'Ett internt fel uppstod under OIDC-inloggning',
+ tokenExchangeFailed: 'OIDC-tokenutbyte misslyckades',
+ },
+ forgotPasswordTitle: 'Glömt lösenord',
+ forgotPasswordMessage: 'Om du har glömt ditt lösenord, kontakta din systemadministratör för att återställa det.',
+ forgotPasswordEmailMessage: 'Ange din e-postadress så skickar vi ett nytt lösenord till dig.',
+ emailAddress: 'E-postadress',
+ emailPlaceholder: 'din.e-post@example.com',
+ cancel: 'Avbryt',
+ sending: 'Skickar...',
+ sendResetEmail: 'Skicka återställnings-e-post',
+ howToReset: 'Så här återställer du ditt lösenord:',
+ resetStep1: 'Kontakta din Bambuddy-administratör',
+ resetStep2: 'Be dem återställa ditt lösenord i Användarhantering',
+ resetStep3: 'De kan sätta ett nytt tillfälligt lösenord åt dig',
+ resetStep4: 'Logga in med det nya lösenordet och ändra det under Inställningar',
+ gotIt: 'Förstått',
+ resetPassword: {
+ title: 'Sätt nytt lösenord',
+ subtitle: 'Ange och bekräfta ditt nya lösenord nedan.',
+ newPassword: 'Nytt lösenord',
+ newPasswordPlaceholder: 'Minst 8 tecken',
+ confirmPassword: 'Bekräfta lösenord',
+ confirmPasswordPlaceholder: 'Upprepa nytt lösenord',
+ saving: 'Sparar…',
+ submit: 'Sätt nytt lösenord',
+ backToLogin: 'Tillbaka till inloggning',
+ passwordsDoNotMatch: 'Lösenorden matchar inte',
+ passwordTooShort: 'Lösenordet måste vara minst 8 tecken',
+ resetFailed: 'Lösenordsåterställning misslyckades. Länken kan ha löpt ut.',
+ },
+ twoFA: {
+ title: 'Tvåfaktorsautentisering',
+ subtitle: 'Ditt konto är skyddat med 2FA. Ange verifieringskoden nedan.',
+ methodAuthenticator: 'Autentiseringsapp',
+ methodEmail: 'E-postkod',
+ methodBackup: 'Reservkod',
+ instructionsTotp: 'Öppna din autentiseringsapp och ange den 6-siffriga koden för Bambuddy.',
+ instructionsEmail: 'En 6-siffrig kod har skickats till din e-postadress. Den går ut om 10 minuter.',
+ instructionsEmailNotSent: 'Klicka på knappen nedan för att ta emot en verifieringskod via e-post.',
+ instructionsBackup: 'Ange en av dina 8-teckens reservåterställningskoder. Varje kod kan endast användas en gång.',
+ sendCodeButton: 'Skicka kod via e-post',
+ sendingCode: 'Skickar...',
+ resendCode: 'Skicka kod igen',
+ codeLabel: 'Verifieringskod',
+ backupCodeLabel: 'Reservkod',
+ codePlaceholder: '000000',
+ backupCodePlaceholder: 'XXXXXXXX',
+ verifyButton: 'Verifiera',
+ verifyingButton: 'Verifierar...',
+ backToLogin: '← Tillbaka till inloggning',
+ orContinueWith: 'eller fortsätt med',
+ signInWith: 'Logga in med {{provider}}',
+ enterCode: 'Ange verifieringskoden',
+ sendCodeFailed: 'Kunde inte skicka verifieringskod',
+ invalidCode: 'Ogiltig kod. Försök igen.',
+ },
+
+ },
+
+ // Setup page
+ setup: {
+ title: 'Bambuddy-inställning',
+ subtitle: 'Konfigurera autentisering för din Bambuddy-instans',
+ enableAuth: 'Aktivera autentisering',
+ adminAccount: 'Admin-konto',
+ adminAccountDesc: 'Om admin-användare redan finns kommer autentisering att aktiveras med de befintliga admin-kontona. Lämna fälten nedan tomma för att använda befintliga admins, eller ange nya inloggningsuppgifter för att skapa en ny admin-användare.',
+ adminUsername: 'Admin-användarnamn',
+ adminPassword: 'Admin-lösenord',
+ optionalIfAdminExists: '(valfritt om admin-användare finns)',
+ adminUsernamePlaceholder: 'Ange admin-användarnamn (valfritt)',
+ adminPasswordPlaceholder: 'Ange admin-lösenord (valfritt)',
+ confirmPassword: 'Bekräfta lösenord',
+ confirmPasswordPlaceholder: 'Bekräfta admin-lösenord',
+ settingUp: 'Ställer in...',
+ completeSetup: 'Slutför inställning',
+ toast: {
+ authEnabledAdminCreated: 'Autentisering aktiverad och admin-användare skapad',
+ authEnabledExistingAdmins: 'Autentisering aktiverad med befintliga admin-användare',
+ setupCompleted: 'Inställning slutförd',
+ enterBothCredentials: 'Ange både admin-användarnamn och lösenord, eller lämna båda tomma för att använda befintliga admin-användare',
+ passwordsDoNotMatch: 'Lösenorden matchar ej',
+ passwordTooShort: 'Lösenordet måste vara minst 6 tecken',
+ },
+ },
+
+ // Password change
+ changePassword: {
+ title: 'Ändra lösenord',
+ currentPassword: 'Nuvarande lösenord',
+ currentPasswordPlaceholder: 'Ange nuvarande lösenord',
+ newPassword: 'Nytt lösenord',
+ newPasswordPlaceholder: 'Ange nytt lösenord (minst 6 tecken)',
+ confirmPassword: 'Bekräfta nytt lösenord',
+ confirmPasswordPlaceholder: 'Bekräfta nytt lösenord',
+ passwordsDoNotMatch: 'Lösenorden matchar ej',
+ passwordTooShort: 'Lösenordet måste vara minst 6 tecken',
+ changing: 'Ändrar...',
+ success: 'Lösenord ändrat',
+ failed: 'Kunde inte ändra lösenord',
+ },
+
+ // Plate detection alert
+ plateAlert: {
+ title: 'Utskrift pausad!',
+ message: 'Objekt upptäckta på byggplattan. Utskriften har pausats automatiskt. Töm plattan och fortsätt utskriften.',
+ understand: 'Jag förstår',
+ },
+
+ // Camera page
+ camera: {
+ title: 'Kameravy',
+ invalidPrinterId: 'Ogiltigt skrivar-ID',
+ live: 'Live',
+ snapshot: 'Ögonblicksbild',
+ restartStream: 'Starta om strömmen',
+ refreshSnapshot: 'Uppdatera ögonblicksbild',
+ fullscreen: 'Helskärm',
+ exitFullscreen: 'Avsluta helskärm',
+ connectingToCamera: 'Ansluter till kamera...',
+ capturingSnapshot: 'Fångar ögonblicksbild...',
+ connectionLost: 'Anslutning tappades',
+ connectionFailed: 'Kunde inte ansluta till kamera',
+ reconnecting: 'Återansluter om {{countdown}}s... (försök {{attempt}}/{{max}})',
+ reconnectNow: 'Återanslut nu',
+ cameraUnavailable: 'Kamera otillgänglig',
+ cameraUnavailableDesc: 'Se till att skrivaren är påslagen och ansluten.',
+ noCamera: 'Ingen kamera tillgänglig',
+ retry: 'Försök igen',
+ cameraStream: 'Kameraström',
+ zoomOut: 'Zooma ut',
+ zoomIn: 'Zooma in',
+ resetZoom: 'Återställ zoom',
+ recording: 'Spelar in',
+ startRecording: 'Starta inspelning',
+ stopRecording: 'Stoppa inspelning',
+ chamberLight: 'Växla kammarljus',
+ unavailable: 'Kamera ej tillgänglig',
+ diagnose: {
+ button: 'Diagnostisera',
+ modalTitle: 'Kameradiagnostik',
+ running: 'Kör diagnostik...',
+ runFailed: 'Kunde inte köra diagnostik: {{error}}',
+ retry: 'Kör igen',
+ stage: {
+ tcp_reachable: 'Nätverksåtkomlighet',
+ first_frame: 'Bildtagning',
+ live_stream_active: 'Liveström aktiv',
+ },
+ summary: {
+ all_ok: 'Kameran fungerar. Diagnostiken slutförde alla steg.',
+ live_stream_active_healthy: 'Kameran strömmar för närvarande med nya bilder — ingen test nödvändig.',
+ printer_unreachable: 'Skrivaren är ej nåbar. Kontrollera IP-adress, nätverksanslutning och att skrivaren är påslagen.',
+ camera_port_closed: 'Skrivaren är nåbar men kameraporten är stängd. Se till att LAN-läge och utvecklarläge är aktiverade i skrivarinställningarna.',
+ no_frame: 'Ansluten till kameran men inga bilder togs emot. Försök igen eller kontrollera att kameran är aktiverad i skrivarinställningarna.',
+ unknown_failure: 'Kameradiagnostik misslyckades av okänd anledning. Kontrollera supportloggen för detaljer.',
+ },
+ meta: {
+ protocol: 'Protokoll',
+ port: 'Port',
+ profile: 'Profil',
+ },
+ },
+ },
+
+ // Groups management
+ groups: {
+ title: 'Grupphantering',
+ subtitle: 'Hantera behörighetsgrupper för åtkomstkontroll',
+ backToSettings: 'Tillbaka till inställningar',
+ createGroup: 'Skapa grupp',
+ noPermission: 'Du har inte behörighet att komma åt denna sida.',
+ system: 'System',
+ noDescription: 'Ingen beskrivning',
+ usersCount: '{{count}} användare',
+ permissionsCount: '{{count}} behörigheter',
+ edit: 'Redigera',
+ delete: 'Ta bort',
+ toast: {
+ created: 'Grupp skapad',
+ updated: 'Grupp uppdaterad',
+ deleted: 'Grupp borttagen',
+ enterGroupName: 'Ange ett gruppnamn',
+ },
+ modal: {
+ editGroup: 'Redigera grupp',
+ createGroup: 'Skapa grupp',
+ cancel: 'Avbryt',
+ saving: 'Sparar...',
+ creating: 'Skapar...',
+ saveChanges: 'Spara ändringar',
+ },
+ form: {
+ groupName: 'Gruppnamn',
+ groupNamePlaceholder: 'Ange gruppnamn',
+ systemGroupWarning: 'Systemgruppnamn kan inte ändras',
+ description: 'Beskrivning',
+ descriptionPlaceholder: 'Ange beskrivning (valfritt)',
+ permissions: 'Behörigheter ({{count}} valda)',
+ },
+ deleteModal: {
+ title: 'Ta bort grupp',
+ message: 'Är du säker på att du vill ta bort denna grupp? Användare i denna grupp kommer att förlora dessa behörigheter.',
+ confirm: 'Ta bort grupp',
+ },
+ editor: {
+ title: 'Redigera grupp',
+ createTitle: 'Skapa grupp',
+ search: 'Sök behörigheter...',
+ selectAll: 'Markera alla',
+ clearAll: 'Avmarkera alla',
+ permissionsSelected: '{{count}} valda',
+ noResults: 'Inga behörigheter matchar din sökning',
+ websocketHint: 'Krävs för liveuppdateringar. Utan det faller gränssnittet tillbaka på periodisk avfrågning.',
+ },
+ },
+
+ // Users management
+ users: {
+ title: 'Användarhantering',
+ subtitle: 'Hantera användare och deras åtkomst till din Bambuddy-instans',
+ backToSettings: 'Tillbaka till inställningar',
+ createUser: 'Skapa användare',
+ noPermission: 'Du har inte behörighet att komma åt denna sida.',
+ admin: 'Admin',
+ noGroups: 'Inga grupper',
+ active: 'Aktiv',
+ inactive: 'Inaktiv',
+ edit: 'Redigera',
+ delete: 'Ta bort',
+ system: 'System',
+ noGroupsAvailable: 'Inga grupper tillgängliga',
+ table: {
+ username: 'Användarnamn',
+ groups: 'Grupper',
+ status: 'Status',
+ actions: 'Åtgärder',
+ },
+ toast: {
+ created: 'Användare skapad',
+ updated: 'Användare uppdaterad',
+ deleted: 'Användare borttagen',
+ fillRequired: 'Fyll i alla obligatoriska fält',
+ passwordsDoNotMatch: 'Lösenorden matchar ej',
+ passwordTooShort: 'Lösenordet måste vara minst 6 tecken',
+ ldapProvisioned: 'LDAP-användare "{{username}}" skapad',
+ },
+ modal: {
+ createUser: 'Skapa användare',
+ editUser: 'Redigera användare',
+ cancel: 'Avbryt',
+ creating: 'Skapar...',
+ saving: 'Sparar...',
+ saveChanges: 'Spara ändringar',
+ advancedAuthSubtitle: 'med avancerad autentisering',
+ // LDAP manual provisioning
+ tabsAriaLabel: 'Användarkälla',
+ localTab: 'Lokal',
+ ldapTab: 'LDAP',
+ ldapSearchLabel: 'Sök katalog',
+ ldapSearchPlaceholder: 'Ange användarnamn, namn eller e-post...',
+ ldapMinChars: 'Ange minst 2 tecken för att söka',
+ ldapTypeToSearch: 'Börja skriva för att söka i LDAP-katalogen',
+ ldapSearching: 'Söker i katalog...',
+ ldapNoResults: 'Inga matchande användare i katalogen',
+ ldapSearchError: 'Katalogsökning misslyckades. Kontrollera LDAP-serverstatus.',
+ ldapAlreadyProvisioned: 'Redan skapad',
+ ldapSelectedLabel: 'Valda',
+ ldapProvision: 'Skapa användare',
+ ldapProvisioning: 'Skapar...',
+ ldapErrorProvision: 'Kunde inte skapa användare. Kontrollera LDAP-serverstatus och försök igen.',
+ },
+ form: {
+ username: 'Användarnamn',
+ usernamePlaceholder: 'Ange användarnamn',
+ email: 'E-post',
+ emailPlaceholder: 'användare@example.com',
+ password: 'Lösenord',
+ passwordPlaceholder: 'Ange lösenord',
+ confirmPassword: 'Bekräfta lösenord',
+ confirmPasswordPlaceholder: 'Bekräfta lösenord',
+ newPasswordPlaceholder: 'Ange nytt lösenord',
+ confirmNewPasswordPlaceholder: 'Bekräfta nytt lösenord',
+ leaveBlankToKeep: 'lämna tomt för att behålla nuvarande',
+ groups: 'Grupper',
+ optional: 'valfritt',
+ autoGeneratedPassword: 'Ett säkert lösenord kommer att genereras automatiskt och skickas via e-post till användaren.',
+ passwordManagedByAdvancedAuth: 'Lösenordet hanteras av avancerad autentisering. Använd "Återställ lösenord" för att skicka ett nytt lösenord till användaren via e-post.',
+ resetPassword: 'Återställ lösenord',
+ resettingPassword: 'Återställer lösenord...',
+ },
+ deleteModal: {
+ title: 'Ta bort användare',
+ message: 'Är du säker på att du vill ta bort denna användare? Denna åtgärd kan inte ångras.',
+ confirm: 'Ta bort användare',
+ },
+ },
+
+ // Stream overlay
+ streamOverlay: {
+ title: 'Strömöverlägg',
+ invalidPrinterId: 'Ogiltigt skrivar-ID',
+ cameraStream: 'Kameraström',
+ progress: 'Förlopp',
+ eta: 'Beräknad tid',
+ printerIdle: 'Skrivaren är inaktiv',
+ printerOffline: 'Skrivare offline',
+ builder: {
+ title: 'Strömningsöverlägg',
+ description: 'Bygg URL:en för ett strömningsöverlägg — en helskärmskameravy med live utskriftsdata som visas ovanpå. Välj de fält du vill ha och kopiera URL:en.',
+ printer: 'Skrivare',
+ size: 'Textstorlek',
+ sizeSmall: 'Liten',
+ sizeMedium: 'Mellanstor',
+ sizeLarge: 'Stor',
+ fps: 'Bildfrekvens',
+ fpsHint: 'A1- och P1-kameror når upp till cirka 5 fps oavsett vad du anger.',
+ token: 'Strömningsöverläggs-token (valfritt)',
+ tokenHint: 'Krävs endast när inloggning är aktiverad: OBS har ingen egen session. Skapa en ovan med strömningsöverläggsomfång.',
+ tokenWarning: 'Denna URL innehåller en token — vem som helst som kan läsa den kan titta på strömmen och se filnamnet. Återkalla token för att blockera den.',
+ fields: 'Fält att visa',
+ fieldPrinter: 'Skrivarnamn',
+ fieldFilename: 'Filnamn',
+ fieldStatus: 'Status',
+ fieldProgress: 'Förloppsindikator',
+ fieldLayers: 'Antal skikt',
+ fieldEta: 'Återstående tid och beräknad tid',
+ fieldCamera: 'Kameramatning',
+ chamberHint: 'Kammartemperatur visas endast på modeller med en riktig kammarsensor — P1- och A1-skrivare rapporterar ett meningslöst värde, så det utelämnas där.',
+ urlTitle: 'Överläggs-URL',
+ open: 'Öppna',
+ showPreview: 'Visa förhandsvisning',
+ hidePreview: 'Dölj förhandsvisning',
+ previewTitle: 'Förhandsvisning av överlägg',
+ },
+ status: {
+ printing: 'Skriver ut',
+ paused: 'Pausad',
+ finished: 'Slutförd',
+ failed: 'Misslyckad',
+ idle: 'Inaktiv',
+ unknown: 'Okänd',
+ },
+ },
+
+ // Profiles
+ profiles: {
+ title: 'Profiler',
+ subtitle: 'Hantera dina slicer-inställningar och tryckkalibreringar',
+ tabs: {
+ bambuCloud: 'Bambu Cloud',
+ orcaCloud: 'Orca Cloud',
+ local: 'Lokala profiler',
+ kprofiles: 'K-profiler',
+ },
+ orcaCloud: {
+ connectedAs: 'Ansluten som',
+ connectedShort: 'Ansluten till Orca Cloud',
+ logout: 'Koppla från',
+ noLogoutPermission: 'Du har inte behörighet att koppla från',
+ noConnectPermission: 'Du har inte behörighet att ansluta till Orca Cloud',
+ retry: 'Försök igen',
+ connectButton: 'Anslut till Orca Cloud',
+ connect: {
+ title: 'Anslut till Orca Cloud',
+ description: 'Logga in på ditt Orca Cloud-konto för att synkronisera dina slicer-profiler till Bambuddy.',
+ },
+ device: {
+ title: 'Godkänn Bambuddy i Orca Cloud',
+ instruction: 'Öppna Orca Cloud och godkänn denna kod. Bambuddy ansluter automatiskt när du godkänner.',
+ codeLabel: 'Din parningskod',
+ openButton: 'Öppna Orca Cloud-godkänningssida',
+ manualHint: 'Eller gå till {{url}} och ange koden ovan.',
+ waiting: 'Väntar på att du godkänner...',
+ cancel: 'Avbryt',
+ },
+ profiles: {
+ title: 'Dina Orca Cloud-profiler ({{count}})',
+ refresh: 'Uppdatera',
+ empty: 'Inga profiler hittade i ditt Orca Cloud-konto än.',
+ },
+ toast: {
+ disconnected: 'Kopplad från Orca Cloud',
+ },
+ errors: {
+ startFailed: 'Kunde inte starta Orca Cloud-parning.',
+ denied: 'Parningen nekades i Orca Cloud.',
+ expired: 'Parningskoden har löpt ut. Klicka på Anslut för att försöka igen.',
+ pollFailed: 'Förlorade anslutningen medan vi väntade på godkännande. Försök igen.',
+ },
+ },
+ localProfiles: {
+ title: 'Lokala profiler',
+ subtitle: 'Importera och hantera slicer-inställningar från OrcaSlicer',
+ import: 'Importera profiler',
+ importDesc: 'Släpp .bbscfg, .bbsflmt, .orca_filament, .zip eller .json-filer här',
+ importing: 'Importerar...',
+ search: 'Sök lokala förinställningar...',
+ noPresets: 'Inga lokala förinställningar än',
+ noSearchResults: 'Inga förinställningar matchar din sökning',
+ badge: 'Lokal',
+ edit: 'Redigera',
+ delete: 'Ta bort',
+ cancel: 'Avbryt',
+ deleteConfirmTitle: 'Ta bort förinställning',
+ deleteConfirm: 'Är du säker på att du vill ta bort denna förinställning? Detta kan inte ångras.',
+ source: 'Källa',
+ inheritsFrom: 'Ärver',
+ filamentType: 'Typ',
+ vendor: 'Leverantör',
+ compatiblePrinters: 'Skrivare',
+ nozzleTemp: 'Munstyckstemperatur',
+ cost: 'Kostnad',
+ density: 'Densitet',
+ pressureAdvance: 'Tryckförskott',
+ filament: 'Filament',
+ process: 'Process',
+ printer: 'Skrivare',
+ toast: {
+ importSuccess: '{{count}} förinställning(ar) importerade',
+ importSkipped: '{{count}} förinställning(ar) hoppade över (dubbletter)',
+ importError: '{{count}} fel under import',
+ deleted: 'Förinställning borttagen',
+ updated: 'Förinställning uppdaterad',
+ },
+ },
+ connectedAs: 'Ansluten som',
+ signInExpiredTitle: 'Bambu Cloud-inloggning har löpt ut',
+ signInExpiredBody: 'Bambu Lab accepterar inte längre lagrad token. Logga in igen för att återställa molnprofiler, MakerWorld-import och firmware-kontroller.',
+ logout: 'Logga ut',
+ noLogoutPermission: 'Du har inte behörighet att logga ut',
+ failedToLoad: 'Kunde inte ladda profiler',
+ retry: 'Försök igen',
+ time: {
+ justNow: 'Just nu',
+ minsAgo: '{{count}}m sedan',
+ hoursAgo: '{{count}}t sedan',
+ daysAgo: '{{count}}d sedan',
+ },
+ toast: {
+ loggedOut: 'Utloggad',
+ },
+ login: {
+ title: 'Anslut till Bambu Cloud',
+ subtitle: 'Synkronisera dina slicer-inställningar över enheter',
+ email: 'E-post',
+ password: 'Lösenord',
+ region: 'Region',
+ regionGlobal: 'Global',
+ regionChina: 'Kina',
+ verificationCode: 'Verifieringskod',
+ totpCode: 'Autentiseringskod',
+ checkEmail: 'Kontrollera din e-post ({{email}}) för en 6-siffrig kod',
+ enterTotpHint: 'Ange den 6-siffriga koden från din autentiseringsapp',
+ accessToken: 'Åtkomsttoken',
+ accessTokenHint: 'Klistra in din Bambu Cloud-åtkomsttoken. Konton i Kina måste använda denna metod (telefonbundet — e-postinloggning ej tillgänglig). Se wikin för hur du hämtar token från MakerWorld-cookies.',
+ back: 'Tillbaka',
+ loginButton: 'Logga in',
+ verifyButton: 'Verifiera',
+ setTokenButton: 'Sätt token',
+ useToken: 'Använd åtkomsttoken istället',
+ captchaTitle: 'Bambu Cloud begär en CAPTCHA',
+ captchaBody: 'Bambu utmanar ditt nätverk innan det accepterar en inloggning, och utmaningen kan inte besvaras från Bambuddy. Ditt e-post och lösenord är inte problemet. Blockeringen är kopplad till din offentliga IP-adress och brukar lösa sig själv inom några timmar — upprepade försök gör att den varar längre. För att logga in nu, använd en åtkomsttoken från en webbläsarsession istället.',
+ useEmail: 'Logga in med e-post istället',
+ toast: {
+ loggedIn: 'Inloggad',
+ codeSent: 'Verifieringskod skickad till din e-post',
+ enterTotp: 'Ange kod från din autentiseringsapp',
+ tokenSet: 'Token inställd',
+ },
+ },
+ presets: {
+ myPreset: 'Min förinställning (redigerbar)',
+ duplicate: 'Duplicera',
+ editable: 'Redigerbar',
+ failedToLoadDetails: 'Kunde inte ladda förinställningsdetaljer',
+ deleteConfirm: 'Ta bort denna förinställning?',
+ deleteWarning: 'Detta kommer permanent ta bort "{{name}}" från Bambu Cloud. Detta kan inte ångras.',
+ noDuplicatePermission: 'Du har inte behörighet att duplicera förinställningar',
+ noEditPermission: 'Du har inte behörighet att redigera förinställningar',
+ noDeletePermission: 'Du har inte behörighet att ta bort förinställningar',
+ types: {
+ filament: 'Filamentförinställning',
+ printer: 'Skrivarförinställning',
+ process: 'Processförinställning',
+ },
+ toast: {
+ deleted: 'Förinställning borttagen',
+ created: 'Förinställning skapad',
+ updated: 'Förinställning uppdaterad',
+ duplicated: 'Förinställning duplicerad',
+ fieldAdded: 'Fält "{{key}}" tillagt',
+ exported: 'Förinställning exporterad',
+ },
+ baseLabel: 'Bas: {{name}}',
+ currentLabel: 'Nuvarande: {{name}}',
+ newPreset: 'Ny förinställning',
+ editPreset: 'Redigera förinställning',
+ duplicatePreset: 'Duplicera förinställning',
+ createNewPreset: 'Skapa ny förinställning',
+ customizeSettings: 'Anpassa inställningar för din nya förinställning',
+ compareWithBase: 'Jämför med basförinställning',
+ compare: 'Jämför',
+ // CreatePresetModal - Basic Info
+ basePreset: 'Basförinställning',
+ selectBasePreset: 'Välj basförinställning...',
+ presetName: 'Förinställningsnamn',
+ myCustomPreset: 'Min anpassade förinställning',
+ inheritsFrom: 'Ärver',
+ dropJsonToImport: 'Släpp JSON för att importera',
+ // CreatePresetModal - Tabs
+ tabs: {
+ common: 'Allmänna',
+ allFields: 'Alla fält',
+ },
+ // CreatePresetModal - All Fields Tab
+ availableFields: 'Tillgängliga fält',
+ searchFieldsPlaceholder: 'Sök fält...',
+ noMatchingFields: 'Inga matchande fält',
+ allFieldsAdded: 'Alla fält tillagda',
+ addCustomField: 'Lägg till anpassat fält',
+ yourOverrides: 'Dina åsidosättningar',
+ noOverridesYet: 'Inga åsidosättningar än',
+ clickFieldsToAdd: 'Klicka på fält till vänster för att lägga till dem',
+ saveAsTemplate: 'Spara som mall',
+ jsonTip: 'Tips: Dra och släpp en .json-fil var som helst i denna modal för att importera inställningar',
+ },
+ cloudView: {
+ searchPlaceholder: 'Sök förinställningar...',
+ templates: 'Mallar',
+ refresh: 'Uppdatera',
+ newPreset: 'Ny förinställning',
+ clearFilters: 'Rensa filter',
+ // Compare mode
+ compareMode: 'Jämförelseläge',
+ selectAnotherPreset: 'Välj en annan {{type}}-förinställning',
+ clickTwoPresets: 'Klicka på två förinställningar av samma typ för att jämföra',
+ selectFirst: '1. Välj första',
+ selectSecond: '2. Välj andra',
+ compareNow: 'Jämför nu',
+ // Status row
+ lastSynced: 'Senast synkroniserad:',
+ showingCount: 'Visar {{showing}} av {{total}} förinställningar',
+ noPresetsFound: 'Inga förinställningar hittade',
+ // Column headers
+ columns: {
+ filament: 'Filament',
+ process: 'Process',
+ printer: 'Skrivare',
+ },
+ noFilamentPresets: 'Inga filamentförinställningar',
+ noProcessPresets: 'Inga processförinställningar',
+ noPrinterPresets: 'Inga skrivarförinställningar',
+ // Filters
+ filters: {
+ type: 'Typ',
+ owner: 'Ägare',
+ printer: 'Skrivare',
+ nozzle: 'Munstycke',
+ filament: 'Filament',
+ layer: 'Skikt',
+ all: 'Alla',
+ myPresets: 'Mina förinställningar',
+ builtIn: 'Inbyggda',
+ process: 'Process',
+ },
+ // Permissions
+ noTemplatesPermission: 'Du har inte behörighet att hantera mallar',
+ noRefreshPermission: 'Du har inte behörighet att uppdatera profiler',
+ noCreatePermission: 'Du har inte behörighet att skapa förinställningar',
+ },
+ templates: {
+ title: 'Snabbmallar',
+ noTemplates: 'Inga mallar än',
+ createFirst: 'Skapa mallar från förinställningseditorn',
+ typeFilter: 'Typ:',
+ deleteTitle: 'Ta bort mall',
+ deleteWarning: 'Denna åtgärd kan inte ångras',
+ deleteConfirm: 'Är du säker på att du vill ta bort "{{name}}"?',
+ namePlaceholder: 'Mallnamn',
+ descriptionPlaceholder: 'Beskrivning',
+ settingsJson: 'Inställningar (JSON)',
+ fieldsCount: '{{count}} fält',
+ shownInModals: 'Visas i modaler',
+ hiddenInModals: 'Dold i modaler',
+ apply: 'Tillämpa',
+ toast: {
+ deleted: 'Mall borttagen',
+ updated: 'Mall uppdaterad',
+ created: 'Mall skapad',
+ applied: 'Mall tillämpad',
+ },
+ },
+ },
+
+ // Support/Debug
+ support: {
+ debugLoggingActive: 'Felsökningsloggning är aktiv',
+ manageLogs: 'Hantera',
+ collectItem7: 'Skrivaranslutning och firmwareversioner',
+ collectItem8: 'Integrationsstatus (Spoolman, MQTT, HA)',
+ collectItem9: 'Nätverksgränssnitt (endast subnät)',
+ collectItem10: 'Python-paketversioner',
+ collectItem11: 'Databashälsokontroller',
+ collectItem12: 'Docker-miljödetaljer',
+ bundleGenerating: 'Genererar paket...',
+ bundleStepConnection: 'Kör skrivaranslutningskontroller',
+ bundleStepVirtualPrinters: 'Kör kontroll av virtuella skrivare',
+ bundleStepLogScan: 'Söker i senaste loggar efter kända problem',
+ bundleStepBuild: 'Bygger supportpaket-ZIP',
+ },
+
+ // File manager
+ fileManager: {
+ variants: {
+ badge: '{{count}} versioner',
+ groupAction: 'Gruppera som versioner',
+ groupTooltip: 'Markera dessa filer som samma jobb slicade för olika skrivare',
+ grouped: 'Grupperade {{count}} filer som versioner',
+ printAlternatives: 'Skriv ut ({{count}} alternativ)',
+ },
+ title: 'Filhanterare',
+ subtitle: 'Organisera och hantera dina utskriftsfiler',
+ uploadFiles: 'Ladda upp filer',
+ newFolder: 'Ny mapp',
+ folderName: 'Mappnamn',
+ folderNamePlaceholder: 't.ex. Funktionella delar',
+ renameFile: 'Döp om fil',
+ renameFolder: 'Döp om mapp',
+ invalidFilenameChar: 'Tecknet "{{char}}" är inte tillåtet i utskriftsfilnamn. Skrivarens SD-kort avvisar: < > : " / \\ | ? *',
+ moveFiles: 'Flytta {{count}} fil(er)',
+ rootNoFolder: 'Rot (ingen mapp)',
+ current: 'nuvarande',
+ linkFolder: 'Länka mapp',
+ linkFolderDescription: 'Länka "{{name}}" till ett projekt eller arkiv för snabb åtkomst.',
+ project: 'Projekt',
+ archive: 'Arkiv',
+ noProjectsFound: 'Inga projekt hittade',
+ noArchivesFound: 'Inga arkiv hittade',
+ unlink: 'Avlänka',
+ link: 'Länka',
+ dragDropFiles: 'Dra och släpp filer här',
+ dropFilesHere: 'Släpp filer här',
+ releaseToUpload: 'Släpp för att ladda upp',
+ orClickToBrowse: 'eller klicka för att bläddra',
+ allFileTypesSupported: 'Alla filtyper stöds. ZIP-filer kommer att packas upp.',
+ zipFilesDetected: 'ZIP-filer upptäckta',
+ zipExtractOptions: 'ZIP-filer kommer att packas upp. Välj hur du vill hantera mappstrukturen:',
+ preserveZipStructure: 'Bevara mappstruktur från ZIP',
+ createFolderFromZip: 'Skapa mapp från ZIP-filnamn',
+ stlThumbnailGeneration: 'Generering av STL-minibilder',
+ zipMayContainStl: 'ZIP-filer kan innehålla STL-filer. Minibilder kan genereras under extraktion.',
+ thumbnailsCanBeGenerated: 'Minibilder kan genereras för STL-filer. Stora modeller kan ta längre tid att bearbeta.',
+ generateThumbnailsForStl: 'Generera minibilder för STL-filer',
+ threemfDetected: '3MF-filer upptäckta',
+ threemfExtractionInfo: 'Skrivarmodell, material, färg och utskriftsinställningar kommer automatiskt att extraheras från 3MF-filer.',
+ willBeExtracted: 'Kommer att extraheras',
+ filesExtracted: '{{count}} filer extraherade',
+ uploadComplete: 'Uppladdning slutförd: {{succeeded}} lyckades',
+ uploadFailed: 'Kunde inte ladda upp',
+ zipFilesFailed: '{{count}} filer misslyckades',
+ uploading: 'Laddar upp...',
+ changeLink: 'Ändra länk...',
+ linkTo: 'Länka till...',
+ linkToProjectOrArchive: 'Länka till projekt eller arkiv',
+ generateThumbnail: 'Generera minibild',
+ generateThumbnails: 'Generera minibilder',
+ generateThumbnailsForMissing: 'Generera minibilder för STL-filer som saknar dem',
+ gridView: 'Rutnätsvy',
+ listView: 'Listvy',
+ lowDiskSpaceWarning: 'Varning för lågt diskutrymme',
+ lowDiskSpaceDetails: 'Endast {{free}} ledigt av {{total}} totalt. Tröskelvärdet är inställt på {{threshold}} GB i inställningar.',
+ files: 'Filer',
+ folders: 'Mappar',
+ size: 'Storlek',
+ free: 'Ledigt',
+ allFiles: 'Alla filer',
+ allExternal: 'Externa',
+ externalIsEmpty: 'Inga externa filer',
+ externalEmptyDescription: 'Filer i dina länkade externa mappar kommer att visas här.',
+ wrap: 'Radbryt',
+ enableTextWrapping: 'Aktivera radbrytning',
+ disableTextWrapping: 'Inaktivera radbrytning',
+ collapse: 'Fäll in',
+ collapseFoldersByDefault: 'Fäll in mappar som standard',
+ expandFoldersByDefault: 'Fäll ut mappar som standard',
+ folderSort: 'Sortera mappar',
+ folderSortByName: 'Efter namn',
+ folderSortByActivity: 'Efter senaste aktivitet',
+ dragToResizeTooltip: 'Dra för att ändra storlek, dubbelklicka för att återställa',
+ searchFiles: 'Sök filer...',
+ searchSubfoldersHint: 'Inklusive undermappar',
+ readme: {
+ truncated: 'Avkortad',
+ show: 'Visa README',
+ hide: 'Dölj README',
+ label: 'README',
+ },
+ tags: {
+ title: 'Taggar',
+ subtitle: 'Märk filer för tvärgående filtrering — leksaker, barnsäkra, endast PETG, vad som helst.',
+ manage: 'Taggar',
+ manageTitle: 'Hantera taggkatalog',
+ add: 'Ny tagg',
+ edit: 'Döp om tagg',
+ name: 'Namn',
+ fileCount: 'Filer',
+ empty: 'Inga taggar än. Skapa en för att börja märka filer.',
+ noMatches: 'Inga matchande taggar.',
+ createPlaceholder: 't.ex. leksaker, barnsäkra, petg',
+ createButton: 'Skapa',
+ nameRequired: 'Namn krävs.',
+ searchPlaceholder: 'Filtrera taggar...',
+ created: 'Tagg skapad.',
+ updated: 'Tagg omdöpt.',
+ deleted: 'Tagg borttagen.',
+ saveFailed: 'Kunde inte spara tagg.',
+ deleteFailed: 'Kunde inte ta bort tagg.',
+ applyFailed: 'Kunde inte tillämpa taggar.',
+ applyAdd: 'Lägg till taggar',
+ applyRemove: 'Ta bort taggar',
+ applyAddSuccess: 'Lade till {{count}} tagg(ar) på {{files}} fil(er).',
+ applyRemoveSuccess: 'Tog bort {{count}} tagg(ar) från {{files}} fil(er).',
+ actionAdd: 'Lägg till på valda filer',
+ actionRemove: 'Ta bort från valda filer',
+ tagAction: 'Tagg',
+ bulkTitle: 'Tagga {{count}} valda fil(er)',
+ bulkTooltip: 'Lägg till eller ta bort taggar på alla valda filer.',
+ noPermission: 'Du har inte behörighet att tagga filer.',
+ filterLabel: 'Filtrerar efter:',
+ clearAll: 'Rensa alla',
+ confirmDelete: 'Ta bort tagg "{{name}}"?',
+ confirmDeleteMessage: 'Detta tar bort taggen från katalogen. Filerna behåller sina andra taggar.',
+ confirmDeleteInUseMessage: 'Denna tagg används på {{count}} fil(er). Om du tar bort den kommer taggen att tas bort från alla filer; själva filerna påverkas ej.',
+ editAria: 'Redigera {{name}}',
+ deleteAria: 'Ta bort {{name}}',
+ },
+ allTypes: 'Alla typer',
+ prints: 'Utskrifter',
+ ascending: 'Stigande',
+ descending: 'Fallande',
+ showModified: 'Visa ändringsdatum',
+ hideModified: 'Dölj ändringsdatum',
+ lastModified: 'Senast ändrad',
+ lastActivity: 'Senaste aktivitet',
+ resultsCount: 'Visar {{showing}} av {{total}} filer',
+ selectAll: 'Markera alla',
+ deselectAll: 'Avmarkera alla',
+ selected: '{{count}} valda',
+ adding: 'Lägger till...',
+ loadingFiles: 'Laddar filer...',
+ folderIsEmpty: 'Mappen är tom',
+ noFilesYet: 'Inga filer än',
+ folderEmptyDescription: 'Ladda upp filer eller flytta filer till denna mapp för att komma igång.',
+ noFilesDescription: 'Ladda upp filer för att börja organisera dina utskriftsrelaterade filer.',
+ noMatchingFiles: 'Inga matchande filer',
+ noMatchingFilesDescription: 'Inga filer matchar din aktuella sökning eller filterkriterier.',
+ clearFilters: 'Rensa filter',
+ printedCount: 'Utskrivet {{count}} gånger',
+ uploadedBy: 'Uppladdad av',
+ deleteFolder: 'Ta bort mapp',
+ deleteFile: 'Ta bort fil',
+ deleteFilesCount: 'Ta bort {{count}} filer',
+ deleteFolderConfirm: 'Är du säker på att du vill ta bort denna mapp? Alla filer inuti kommer också att tas bort.',
+ deleteFileConfirm: 'Är du säker på att du vill ta bort denna fil?',
+ deleteFilesConfirm: 'Är du säker på att du vill ta bort {{count}} valda filer? Denna åtgärd kan inte ångras.',
+ deleting: 'Tar bort...',
+ noPermissionRenameFolder: 'Du har inte behörighet att döpa om mappar',
+ noPermissionLinkFolder: 'Du har inte behörighet att länka mappar',
+ noPermissionDeleteFolder: 'Du har inte behörighet att ta bort mappar',
+ onlyEmptyFoldersDeletable: 'Du kan endast ta bort tomma mappar',
+ noPermissionPrint: 'Du har inte behörighet att skriva ut',
+ noPermissionAddToQueue: 'Du har inte behörighet att lägga till i kö',
+ noPermissionSlice: 'Du har inte behörighet att slica filer',
+ noPermissionDownload: 'Du har inte behörighet att ladda ner filer',
+ noPermissionPreview: 'Du har inte behörighet att förhandsgranska filer',
+ preview3d: '3D-förhandsgranskning',
+ noPermissionRenameFile: 'Du har inte behörighet att döpa om denna fil',
+ noPermissionGenerateThumbnail: 'Du har inte behörighet att generera minibilder',
+ noPermissionDeleteFile: 'Du har inte behörighet att ta bort denna fil',
+ noPermissionCreateFolder: 'Du har inte behörighet att skapa mappar',
+ noPermissionUpload: 'Du har inte behörighet att ladda upp filer',
+ noPermissionMoveFiles: 'Du har inte behörighet att flytta filer',
+ noPermissionDeleteFiles: 'Du har inte behörighet att ta bort filer',
+ // External folder
+ linkExternal: 'Länka extern',
+ linkExternalFolder: 'Länka extern mapp',
+ linkExternalFolderDescription: 'Montera en värdkatalog (NAS, USB, nätverksdelning) i Filhanteraren. Filerna kopieras inte — de nås direkt från den ursprungliga sökvägen.',
+ externalFolderNamePlaceholder: 't.ex. NAS-utskrifter',
+ externalPath: 'Värdsökväg',
+ externalPathHelp: 'Absolut sökväg till katalogen på Docker-värden. Måste vara bind-monterad i containern.',
+ readOnly: 'Skrivskyddad',
+ readOnlyHelp: 'förhindrar uppladdningar och borttagningar',
+ showHiddenFiles: 'Visa dolda filer (doldfiler)',
+ externalFolder: 'Extern mapp',
+ scanFolder: 'Sök',
+ toast: {
+ folderCreated: 'Mapp skapad',
+ openInSlicerFailed: 'Kunde inte öppna i slicer',
+ folderDeleted: 'Mapp borttagen',
+ fileDeleted: 'Fil borttagen',
+ filesDeleted: 'Tog bort {{count}} filer',
+ filesMoved: 'Filer flyttade',
+ folderLinked: 'Mapp länkad',
+ folderUnlinked: 'Mapp avlänkad',
+ externalFolderLinked: 'Extern mapp länkad och skannad',
+ folderScanned: 'Skanning slutförd: {{added}} tillagda, {{removed}} borttagna',
+ addedToQueue: 'Lade till {{count}} fil(er) i kön',
+ addedToQueuePartial: 'Lade till {{added}} fil(er), {{failed}} misslyckades',
+ failedToAddToQueue: 'Kunde inte lägga till filer: {{error}}',
+ fileRenamed: 'Fil omdöpt',
+ folderRenamed: 'Mapp omdöpt',
+ thumbnailsGenerated: 'Genererade {{count}} minibild(er)',
+ thumbnailsGeneratedPartial: 'Genererade {{succeeded}} minibild(er), {{failed}} misslyckades',
+ noStlMissingThumbnails: 'Inga STL-filer saknar minibilder',
+ failedToGenerateThumbnails: 'Kunde inte generera minibilder: {{error}}',
+ thumbnailGenerated: 'Minibild genererad',
+ failedToGenerateThumbnail: 'Kunde inte generera minibild: {{error}}',
+ },
+ },
+
+ // Projects
+ projects: {
+ parentLabel: 'Överordnat projekt',
+ parentNone: 'Ingen (toppnivåprojekt)',
+ parentHint: 'Placera detta projekt under ett annat så att dess siffror rullas upp i ett huvudprojekt',
+ partOf: 'Del av {{name}}',
+ subProjectCount: '{{count}} delprojekt',
+ subProjectsOf: 'Delprojekt till {{name}}',
+ collapseSubProjects: 'Fäll in delprojekt',
+ expandSubProjects: 'Fäll ut delprojekt',
+ title: 'Projekt',
+ subtitle: 'Organisera och spåra dina 3D-utskriftsprojekt',
+ newProject: 'Nytt projekt',
+ editProject: 'Redigera projekt',
+ deleteProject: 'Ta bort projekt',
+ projectName: 'Projektnamn',
+ description: 'Beskrivning',
+ noProjects: 'Inga projekt än',
+ noProjectsFiltered: 'Inga {{status}}-projekt',
+ noProjectsFilteredHelp: 'Du har inga {{status}}-projekt. Projekt kommer att visas här när deras status ändras.',
+ createFirst: 'Skapa ditt första projekt för att börja organisera relaterade utskrifter, spåra framsteg och hantera dina byggen.',
+ createFirstButton: 'Skapa ditt första projekt',
+ create: 'Skapa',
+ files: 'Filer',
+ prints: 'Utskrifter',
+ plates: 'plattor',
+ parts: 'delar',
+ lastModified: 'Senast ändrad',
+ deleteConfirm: 'Är du säker på att du vill ta bort detta projekt? Arkiv och köposter kommer att avlänkas men inte tas bort.',
+ addFiles: 'Lägg till filer',
+ removeFile: 'Ta bort fil',
+ viewDetails: 'Visa detaljer',
+ // Modal fields
+ namePlaceholder: 't.ex. Voron 2.4 bygge',
+ descriptionPlaceholder: 'Valfri beskrivning...',
+ urlLabel: 'URL',
+ urlPlaceholder: 'https://makerworld.com/...',
+ urlInvalid: 'URL måste börja med http:// eller https://',
+ openExternalUrl: 'Öppna projekt-URL',
+ coverImageLabel: 'Omslagsbild',
+ coverImageAlt: 'Projektomslagsbild',
+ coverImageUpload: 'Ladda upp',
+ coverImageReplace: 'Ersätt',
+ coverImageRemove: 'Ta bort',
+ color: 'Färg',
+ targetPlates: 'Målplattor',
+ targetPlatesPlaceholder: 't.ex. 25',
+ targetPlatesHelp: 'Antal utskriftsjobb',
+ targetParts: 'Måldelar',
+ targetPartsPlaceholder: 't.ex. 150',
+ targetPartsHelp: 'Totalt antal objekt som behövs',
+ targetSets: 'Kopior per fil',
+ targetSetsPlaceholder: 't.ex. 10',
+ targetSetsHelp: 'Antal gånger varje utskriftsbar fil ska skrivas ut',
+ tagsLabel: 'Taggar (komma-separerade)',
+ tagsPlaceholder: 't.ex. voron, funktionella, present',
+ dueDate: 'Förfallodatum',
+ priority: 'Prioritet',
+ priorityLow: 'Låg',
+ priorityNormal: 'Normal',
+ priorityHigh: 'Hög',
+ priorityUrgent: 'Brådskande',
+ // Status
+ statusActive: 'Aktivt',
+ statusCompleted: 'Slutfört',
+ statusArchived: 'Arkiverat',
+ done: 'Klar',
+ completed: 'slutförd',
+ failed: 'misslyckad',
+ inQueue: 'i kö',
+ noPrintsYet: 'Inga utskrifter än',
+ // Footer stats
+ printJobs: 'Utskriftsjobb (plattor)',
+ partsPrinted: 'Delar utskrivna',
+ failedParts: 'Misslyckade delar',
+ // Actions
+ import: 'Importera',
+ export: 'Exportera',
+ importProject: 'Importera projekt',
+ exportAll: 'Exportera alla projekt',
+ loading: 'Laddar projekt...',
+ // Permissions
+ noEditPermission: 'Du har inte behörighet att redigera projekt',
+ noDeletePermission: 'Du har inte behörighet att ta bort projekt',
+ noCreatePermission: 'Du har inte behörighet att skapa projekt',
+ noImportPermission: 'Du har inte behörighet att importera projekt',
+ noExportPermission: 'Du har inte behörighet att exportera projekt',
+ // Toast
+ toast: {
+ created: 'Projekt skapat',
+ updated: 'Projekt uppdaterat',
+ deleted: 'Projekt borttaget',
+ imported: 'Projekt importerat',
+ multipleImported: '{{count}} projekt importerade',
+ importFailed: 'Kunde inte importera',
+ exported: 'Projekt exporterade (endast metadata)',
+ },
+ },
+
+ // Project detail page
+ projectDetail: {
+ notFound: 'Projekt hittades ej',
+ backToProjects: 'Tillbaka till projekt',
+ export: 'Exportera',
+ exportProject: 'Exportera projekt',
+ noExportPermission: 'Du har inte behörighet att exportera projekt',
+ noEditPermission: 'Du har inte behörighet att redigera projekt',
+ partOf: 'Del av:',
+ priorityLabel: 'Prioritet:',
+ noPrints: 'Inga utskrifter i detta projekt än',
+ status: {
+ active: 'Aktivt',
+ completed: 'Slutfört',
+ archived: 'Arkiverat',
+ },
+ priority: {
+ low: 'Låg',
+ normal: 'Normal',
+ high: 'Hög',
+ urgent: 'Brådskande',
+ },
+ dueDate: {
+ overdue: 'Försenad',
+ today: 'Förfaller idag',
+ daysLeft: '{{count}} dagar kvar',
+ },
+ progress: {
+ platesProgress: 'Plattförlopp',
+ partsProgress: 'Delförlopp',
+ setsProgress: 'Kompletta set',
+ sets: 'set',
+ setsHint: 'Ett set är komplett när varje utskriftsbar fil har nått detta antal',
+ printJobs: 'utskriftsjobb',
+ parts: 'delar',
+ percentComplete: '{{percent}}% klar',
+ remaining: '{{count}} återstående',
+ },
+ stats: {
+ printJobs: 'Utskriftsjobb',
+ total: 'totalt',
+ failed: '{{count}} misslyckade',
+ partsPrinted: '{{count}} delar utskrivna',
+ printTime: 'Utskriftstid',
+ filamentUsed: 'Filament använt',
+ },
+ cost: {
+ title: 'Kostnadsspårning',
+ filamentCost: 'Filamentkostnad',
+ energy: 'Energi',
+ totalCost: 'Uppskattad kostnad',
+ total: 'Totalt',
+ includesBom: 'inkl. BOM',
+ budget: 'Budget',
+ remaining: 'Återstående',
+ },
+ subProjects: {
+ title: 'Delprojekt ({{count}})',
+ jobs: '{{count}} jobb',
+ },
+ rollup: {
+ title: 'Inkluderar {{count}} delprojekt',
+ progress: 'Total förlopp',
+ percentComplete: '{{percent}}% klar',
+ },
+ notes: {
+ title: 'Anteckningar',
+ noEditPermission: 'Du har inte behörighet att redigera anteckningar',
+ placeholder: 'Lägg till anteckningar om detta projekt...',
+ empty: 'Inga anteckningar än. Klicka på Redigera för att lägga till anteckningar.',
+ },
+ files: {
+ title: 'Filer',
+ linkFolders: 'Länka mappar från Filhanteraren',
+ forQuickAccess: 'till detta projekt för snabb åtkomst.',
+ fileCount: '{{count}} fil(er)',
+ empty: 'Inga mappar länkade. Gå till Filhanteraren och länka en mapp till detta projekt.',
+ printedCount: 'Utskrivet {{count}} gång(er)',
+ progressTooltip: '{{done}} av {{target}} slutförda utskrifter',
+ noFiles: 'Inga filer i denna mapp.',
+ },
+ bom: {
+ title: 'Materialförteckning',
+ acquired: '{{completed}}/{{total}} anskaffade',
+ showAll: 'Visa alla',
+ hideDone: 'Dölj klara',
+ addPart: 'Lägg till del',
+ noAddPermission: 'Du har inte behörighet att lägga till delar',
+ partNamePlaceholder: 'Delnamn (t.ex. M3x8 skruvar)',
+ partName: 'Delnamn',
+ qty: 'Antal',
+ price: 'Pris ({{currency}})',
+ sourcingUrlPlaceholder: 'Inköps-URL (valfritt)',
+ remarksPlaceholder: 'Anteckningar (valfritt)',
+ deletePart: 'Ta bort del',
+ deleteConfirm: 'Är du säker på att du vill ta bort "{{name}}"?',
+ noUpdatePermission: 'Du har inte behörighet att uppdatera delar',
+ noEditPermission: 'Du har inte behörighet att redigera delar',
+ noDeletePermission: 'Du har inte behörighet att ta bort delar',
+ totalCost: 'Total kostnad:',
+ empty: 'Inga delar i materialförteckningen. Lägg till hårdvara, elektronik eller andra komponenter för att spåra vad som behöver anskaffas.',
+ },
+ timeline: {
+ title: 'Aktivitetstidslinje',
+ empty: 'Ingen aktivitet än.',
+ },
+ template: {
+ saveAsTemplate: 'Spara som mall',
+ noCreatePermission: 'Du har inte behörighet att skapa mallar',
+ },
+ queue: {
+ title: 'Kö',
+ viewAll: 'Visa alla',
+ printing: '{{count}} skriver ut',
+ queued: '{{count}} i kö',
+ },
+ prints: {
+ title: 'Utskrifter ({{count}})',
+ },
+ toast: {
+ projectUpdated: 'Projekt uppdaterat',
+ partAdded: 'Del tillagd',
+ partRemoved: 'Del borttagen',
+ exportFailed: 'Kunde inte exportera',
+ projectExported: 'Projekt exporterat',
+ templateCreated: 'Mall skapad',
+ },
+ },
+
+ // System info
+ system: {
+ title: 'Systeminformation',
+ version: 'Version',
+ uptime: 'Driftstid',
+ cpuUsage: 'CPU-användning',
+ memoryUsage: 'Minnesanvändning',
+ diskUsage: 'Diskanvändning',
+ networkInfo: 'Nätverksinformation',
+ logs: 'Loggar',
+ debugMode: 'Felsökningsläge',
+ enableDebug: 'Aktivera felsökningsloggning',
+ disableDebug: 'Inaktivera felsökningsloggning',
+ downloadLogs: 'Ladda ner loggar',
+ clearLogs: 'Rensa loggar',
+ dockerInfo: 'Docker-information',
+ containerName: 'Container-namn',
+ imageName: 'Avbildsnamn',
+ platform: 'Plattform',
+ architecture: 'Arkitektur',
+ },
+
+ // Sponsor surface (System page)
+ sponsors: {
+ sectionTitle: 'Oberoende och community-finansierat',
+ tagline: 'Bambuddy är gratis och förblir det tack vare att människor väljer att stödja det. Inga riskkapitalister, ingen molninlåsning.',
+ viewSupporters: 'Visa supporters',
+ toastPrints: 'Du har slutfört {{count}} utskrifter med Bambuddy. Bambuddy förblir gratis tack vare sina supporters.',
+ toastCost: 'Du har spårat {{total}} i filament med Bambuddy. Se vilka som håller projektet oberoende.',
+ toastArchives: '{{count}} utskrifter arkiverade med Bambuddy. Se vilka som håller det oberoende.',
+ toastAnniversary: 'Ett år med Bambuddy! Se vilka som håller projektet oberoende.',
+ toastVersionUpdate: 'Uppdaterad till v{{version}}. Bambuddy förblir gratis tack vare sina supporters.',
+ toastBusiness: 'Kör du Bambuddy på {{count}} skrivare? Det finns ett supportavtal för team — prioriterade åtgärder, fakturering och direktkontakt med underhållaren.',
+ businessCta: 'Bambuddy för företag',
+ businessTitle: 'Bambuddy för företag',
+ businessTagline: 'Du kör {{count}} skrivare. Prioriterad support, kommersiell licensiering och fakturering är tillgängliga för team och utskriftsfarmar.',
+ },
+
+ // Library (K Profiles)
+ library: {
+ title: 'Filamentbibliotek',
+ addFilament: 'Lägg till filament',
+ editFilament: 'Redigera filament',
+ deleteFilament: 'Ta bort filament',
+ vendor: 'Leverantör',
+ material: 'Material',
+ color: 'Färg',
+ kFactor: 'K-faktor',
+ temperature: 'Temperatur',
+ noFilaments: 'Inga filament i biblioteket',
+ deleteConfirm: 'Är du säker på att du vill ta bort detta filament?',
+ importFromPrinter: 'Importera från skrivare',
+ exportToFile: 'Exportera till fil',
+ // Slicer Pipelines run from the File Manager (#1425 PR B). The Run-with-pipeline
+ // modal is a two-step dialog: pick a pipeline, then either fire (eligibility ok)
+ // or confirm-and-fire (eligibility report shown). Lives in components/RunWithPipelineModal.tsx.
+ runWithPipeline: {
+ actionLabel: 'Kör med pipeline',
+ noPermission: 'Du har inte behörighet att köra pipelines',
+ modalTitle: 'Kör med pipeline',
+ confirmTitle: 'Bekräfta körning',
+ confirmIntro: 'Förkontroll fann problem med denna körning',
+ sourceHint: 'Källa',
+ pipelineHint: 'Pipeline',
+ targetHint: 'Mål',
+ pipelineListAria: 'Tillgängliga pipelines',
+ runAnyway: 'Kör ändå',
+ loading: 'Laddar…',
+ empty: 'Inga pipelines sparade än. Öppna slicing-dialogrutan och klicka på "Spara som pipeline" för att skapa en.',
+ noTarget: 'Ingen målskrivare inställd',
+ noTargetMessage: 'Denna pipeline har ingen målskrivare inställd. Öppna den i Inställningar för att välja en.',
+ // PR C — copies input + class-targeted pipelines.
+ copies: 'Kopierar',
+ copiesHint: 'max {{n}}',
+ classTarget: 'Alla {{model}}',
+ toast: {
+ started: 'Pipeline-körning startad',
+ failed: 'Kunde inte starta körning',
+ },
+ issue: {
+ printerNotSet: 'Ingen målskrivare inställd på denna pipeline.',
+ printerNotFound: 'Målskrivaren finns inte längre.',
+ printerDisabled: 'Målskrivaren är inaktiverad.',
+ printerOffline: 'Målskrivaren är offline.',
+ filamentType: 'Filament-fack {{slot}}: förväntade {{expected}}, AMS har {{actual}}',
+ filamentColor: 'Filament-fack {{slot}}: färg skiljer sig (förväntad {{expected}}, AMS har {{actual}})',
+ amsSlotMissing: 'AMS-fack {{slot}} inte tillgängligt på denna skrivare',
+ filamentUnverified: 'Filament-fack {{slot}} kommer från en moln-/standardförinställning och kunde inte verifieras statiskt.',
+ // PR C — class targeting
+ noClassMatches: 'Inga skrivare i denna installation matchar pipelines målskrivarklass ({{expected}}).',
+ classNotSet: 'Pipeline-målet är inställt på en skrivarklass men ingen modell valdes.',
+ },
+ },
+ },
+
+ // Slice (slicer-API integration via SliceModal)
+ slicerSettings: {
+ presetValuesOutdatedSidecar: "Visar slicer-standardvärden: din slicer-sidecar är äldre än denna funktion och kan inte rapportera en förinställnings värden. Uppdatera sidecar-bilden för att se dem. Allt du inte ändrar använder fortfarande förinställningen.",
+ presetValuesNotConfigured: "Visar slicer-standardvärden: ingen slicer-sidecar är konfigurerad, så en förinställnings värden kan inte läsas. Allt du inte ändrar använder fortfarande förinställningen.",
+ presetValuesSidecarUnavailable: "Visar slicer-standardvärden: slicer-sidecaren svarade inte, så en förinställnings värden kan inte läsas. Allt du inte ändrar använder fortfarande förinställningen.",
+ presetValuesUnavailable: "Visar slicer-standardvärden: den valda förinställningens egna värden kunde inte läsas. Allt du inte ändrar använder fortfarande förinställningen.",
+ filamentDefault: 'Standard',
+ fromFile: 'från fil',
+ fromFileHint: 'Designern ändrade detta i källfilen. Dess värde är {{value}}.',
+ fromFilePrinterCoupled: "designerns skrivare",
+ fromFilePrinterCoupledHint: 'Optimerad för den skrivare denna fil designades för — kan vara fel eller utanför intervallet på din.',
+ fromFileOverridesPreset: 'åsidosätter förinställning',
+ fromFileOverridesPresetHint:
+ 'Filen sätter detta till {{value}} där förinställningen du valde använder {{preset}}. Markera endast om filen ska gälla.',
+ useFromFile: "Använd källfilens värde för {{option}}",
+ otherFromFile: 'Andra inställningar från denna fil',
+ fromFileSummary: 'Designern ändrade {{count}} processinställningar i denna fil. Endast de du markerar används.',
+ fromFileUseAll: "Använd designerns inställningar",
+ fromFileUseAllHint: "Markerar inställningar som gäller över skrivare. De som är optimerade för designerns egen skrivare, och de som definierar förinställningen du valde, förblir avmarkerade.",
+ fromFileClear: 'Rensa {{count}}',
+ loading: 'Laddar slicer-inställningar…',
+ mode: {
+ simple: 'Enkel',
+ advanced: 'Avancerad',
+ expert: 'Expert',
+ },
+ searchPlaceholder: 'Sök inställningar',
+ resetAll: 'Återställ {{count}}',
+ resetOption: 'Återställ till standard',
+ noMatches: 'Inga inställningar matchar denna sökning.',
+ },
+ slice: {
+ filamentSlotUnset: 'inte inställt',
+ processSettingsEmbedded: 'Används inte medan "Använd filens inbyggda inställningar" är på -- filens egna inställningar driver denna slicing.',
+ processSettingsInactive: 'Inaktiv',
+ presetsHidden: '{{count}} dold',
+ showAllPresets: 'Visa alla',
+ showFewerPresets: 'Visa färre',
+ processSettings: 'Processinställningar',
+ processSettingsHint: "Justera den valda förinställningen för denna slicing. Allt du inte rör förblir som förinställningen definierar det.",
+ processSettingsChanged: '{{count}} ändrades',
+ processSettingsUnchanged: 'Standardförinställningar',
+ title: 'Slica modell',
+ action: 'Slica',
+ actionAll: 'Slica alla {{count}} plattor',
+ actionAllTitle: 'Slica varje platta till en enda flerplattsutskrift (ett enda arkiv). Filamentvalet täcker alla fack som projektet definierar.',
+ allPlatesToggle: 'Slica alla {{count}} plattor',
+ slicing: 'Slicar',
+ printer: 'Skrivarprofil',
+ process: 'Processprofil',
+ filament: 'Filamentprofil',
+ filamentSlot: 'Filament {{index}} ({{type}})',
+ filamentColour: 'Filamentfärg',
+ selectPreset: '— Välj en förinställning —',
+ loadingPresets: 'Laddar förinställningar…',
+ analyzingPlateFilaments: 'Analyserar plattfilament…',
+ analyzingPlateFilamentsHint: 'Kör en förhandsgransknings-slicing för att upptäcka vilka AMS-fack denna platta använder. Cachad efteråt — omöjligt att öppna igen.',
+ previewToast: 'Analyserar {{name}} — {{elapsed}}',
+ previewWithProgress: 'Analyserar {{name}} — {{stage}} ({{percent}}%) — {{elapsed}}',
+ notUsedByPlate: '— används inte av denna platta',
+ noPresetsForSlot: 'Inga förinställningar tillgängliga',
+ otherPrinters: 'Andra skrivare',
+ presetsLoadFailed: 'Kunde inte ladda förinställningar. Öppna Inställningar → Profiler för att importera dem först.',
+ refreshPresets: 'Uppdatera',
+ refreshPresetsTitle: 'Uppdatera förinställningar — hämta de senaste moln- och buntade listorna (använd efter att ha tagit bort en förinställning i Bambu Studio eller Bambu Handy)',
+ allPresetsRequired: 'Alla förinställningar måste väljas',
+ useEmbedded: 'Använd filens inbyggda inställningar',
+ useEmbeddedHint: 'Slica den på det sätt designern ställt in den (väggar, fyllning, filament) istället för profilerna ovan. Erbjuds eftersom din skrivare matchar filens.',
+ autoOrient: 'Auto-orientera objekt',
+ autoOrientHint: 'Låt slicern vända varje objekt till dess bästa utskriftssida först. Åsidosätter det sätt modellen lades ner i filen.',
+ autoArrange: 'Auto-placera på plattan',
+ autoArrangeHint: 'Låt slicern positionera objekten så att de inte längre överlappar. Ersätter layouten som filen kom med.',
+ enqueuing: 'Skickar slicing-jobb…',
+ queued: 'Köad…',
+ failed: 'Slicing misslyckades. Kontrollera slicer-sidecar-loggarna.',
+ startedToast: 'Slicar {{name}} i bakgrunden…',
+ queuedToast: 'Köat: {{name}} — {{elapsed}}',
+ runningToast: 'Slicar {{name}} — {{elapsed}}',
+ runningWithProgress: '{{name}} — {{stage}} ({{percent}}%) — {{elapsed}}',
+ runningWithProgressMultiPlate: 'Platta {{plateIndex}} av {{plateCount}} • {{name}} — {{stage}} ({{percent}}%) — {{elapsed}}',
+ completedToast: 'Slicad {{name}}',
+ externalWriteFallbackToast: 'Sparad i det interna biblioteket: den externa mappen kunde inte skrivas till',
+ failedTitle: 'Slicing misslyckades',
+ failedToast: 'Slicing {{name}} misslyckades: {{detail}}',
+ tier: {
+ local: 'Importerad',
+ cloud: 'Bambu Cloud',
+ orcaCloud: 'Orca Cloud',
+ standard: 'Standard',
+ },
+ cloud: {
+ notAuthenticated: 'Logga in på Bambu Cloud (Inställningar → Profiler → Bambu Cloud) för att se dina förinställningar i molnet.',
+ expired: 'Bambu Cloud-session har löpt ut — logga in igen för att uppdatera dina förinställningar i molnet.',
+ unreachable: 'Bambu Cloud är otillgänglig just nu. Lokala och standardförinställningar fungerar fortfarande.',
+ },
+ orcaCloud: {
+ notAuthenticated: 'Logga in på Orca Cloud (Profiler → Orca Cloud) för att se dina Orca-förinställningar.',
+ expired: 'Orca Cloud-session har löpt ut — logga in igen för att uppdatera dina Orca-förinställningar.',
+ unreachable: 'Orca Cloud är otillgänglig just nu. Andra förinställningar fungerar fortfarande.',
+ },
+ bedType: {
+ label: 'Byggplatta',
+ auto: 'Auto (använd processförinställning)',
+ coolPlate: 'Cool Plate',
+ coolPlateSuperTack: 'Cool Plate SuperTack',
+ engineering: 'Engineering Plate',
+ highTemp: 'High Temp Plate',
+ texturedPEI: 'Texturerad PEI-platta',
+ smoothPEI: 'Slät PEI-platta',
+ },
+ // Slicer Pipelines (#1425) — apply a saved bundle or save the current pick.
+ pipelines: {
+ label: 'Pipeline',
+ applyAria: 'Tillämpa pipeline',
+ applyPrompt: 'Tillämpa pipeline…',
+ empty: 'Inga sparade pipelines',
+ saveButton: 'Spara som pipeline',
+ saveTitle: 'Spara det aktuella fyrfacks-valet som en återanvändbar pipeline',
+ namePlaceholder: 'Pipeline-namn',
+ nameAria: 'Nytt pipeline-namn',
+ toast: {
+ applied: 'Tillämpade "{{name}}"',
+ saved: 'Pipeline sparad',
+ saveFailed: 'Kunde inte spara',
+ },
+ },
+ },
+
+ // Spoolman
+ spoolman: {
+ title: 'Spoolman-integration',
+ enabled: 'Spoolman aktiverad',
+ url: 'Spoolman-URL',
+ connected: 'Ansluten',
+ disconnected: 'Inte ansluten',
+ testConnection: 'Testa anslutning',
+ sync: 'Synkronisera',
+ syncing: 'Synkroniserar…',
+ lastSync: 'Senaste synkronisering',
+ linkToSpoolman: 'Länka till Spoolman',
+ openInSpoolman: 'Öppna i Spoolman',
+ unlinkSpool: 'Avlänka rulle',
+ unlinkConfirmTitle: 'Avlänka rulle?',
+ unlinkConfirmMessage: 'Detta kommer att ta bort rullen från detta fack. Rulldatan i sig kommer att förbli oförändrad.',
+ selectSpool: 'Välj rulle',
+ noUnlinkedSpools: 'Inga olänkade rullar tillgängliga',
+ linkSuccess: 'Rulle tilldelad',
+ linkFailed: 'Kunde inte tilldela rulle',
+ unlinkSuccess: 'Rulle avlänkad',
+ unlinkFailed: 'Kunde inte avlänka rulle',
+ linkedSpool: 'Tilldelad rulle',
+ spoolId: 'Rull-ID',
+ fillSourceLabel: '(Spoolman)',
+ weight: 'Vikt',
+ remaining: 'Återstående',
+ disableWeightSync: 'Inaktivera AMS uppskattade vikt-synkronisering',
+ disableWeightSyncDesc: 'Uppdatera inte återstående kapacitet från AMS-uppskattningar. Använd detta om du föredrar Spoolmans användningsspårning framför AMS-procentbaserade uppskattningar. Nya rullar kommer fortfarande att använda AMS-uppskattningen som sin initiala vikt.',
+ reportPartialUsage: 'Rapportera partiell användning för misslyckade utskrifter',
+ reportPartialUsageDesc: 'När en utskrift misslyckas eller avbryts, rapportera den uppskattade filamentanvändningen fram till den punkten baserat på skiktförloppet.',
+ },
+
+ locations: {
+ title: 'Lagringsplatser',
+ subtitle: 'Hantera hyllor, lådor och andra fysiska lagringsplatser för dina rullar',
+ add: 'Lägg till plats',
+ addShort: 'Lägg till',
+ edit: 'Redigera plats',
+ name: 'Namn',
+ spools: 'Rullar',
+ sensors: 'Sensorer',
+ empty: 'Inga lagringsplatser än. Skapa din första hylla eller låda.',
+ manage: 'Platser',
+ createPlaceholder: 't.ex. Hylla A, Låda 1',
+ nameRequired: 'Platsnamn krävs',
+ created: 'Plats skapad',
+ updated: 'Plats uppdaterad',
+ deleted: 'Plats borttagen',
+ saveFailed: 'Kunde inte spara plats',
+ deleteFailed: 'Kunde inte ta bort plats',
+ deleteBlocked: 'Ta bort alla rullar från denna plats innan du tar bort den',
+ confirmDelete: 'Ta bort "{{name}}"?',
+ confirmDeleteMessage: 'Denna plats kommer att tas bort från katalogen. Rullar måste flyttas först.',
+ confirmDeleteMessageWithSensors:
+ 'Denna plats kommer att tas bort från katalogen. Spolar måste flyttas först. Anslutna sensorer kommer också att raderas.',
+ },
+
+ // Inventory
+ inventory: {
+ title: 'Rullinventarium',
+ subtitle: 'Hantera dina rullar',
+ addToInventory: 'Lägg till i inventariet',
+ addToInventoryPending: 'Lägger till…',
+ addToInventorySuccess: 'Rulle tillagd i inventariet',
+ addToInventoryFailed: 'Kunde inte lägga till rulle i inventariet',
+ unknownSpoolTitle: 'Nytt filament upptäckt',
+ unknownSpoolMessage: 'En rulle med en okänd RFID-tagg upptäcktes på {{location}}. Lägg till den i ditt inventarium nu?',
+ unknownSpoolSlot: 'Fack',
+ bulk: {
+ selectAllVisible: 'Markera alla synliga',
+ selectRow: 'Markera rad',
+ selectGroup: 'Markera grupp',
+ selectionCount: '{{count}} valda',
+ edit: 'Redigera',
+ printLabels: 'Skriv ut etiketter',
+ resetUsage: 'Återställ användning',
+ restore: 'Återställ',
+ archive: 'Arkivera',
+ delete: 'Ta bort',
+ clearSelection: 'Rensa val',
+ editTitle: 'Massredigera rullar',
+ editSubtitle: 'Tillämpas på {{count}} valda rullar. Endast de fält du markerar kommer att uppdateras.',
+ editHint: 'Skriv i ett fält för att markera det för uppdatering — endast markerade rader skickas. Lämna ett fält tomt för att behålla rullarna oförändrade (rensning av fält är endast per rulle).',
+ useCustom: 'Använd "{{value}}"',
+ toggleField: 'Växla uppdatering för detta fält',
+ changeCount: '{{count}} fält kommer att uppdateras.',
+ applyPending: 'Tillämpar…',
+ applyButton: 'Tillämpa på {{count}} rullar',
+ deleteTitle: 'Ta bort valda rullar',
+ archiveTitle: 'Arkivera valda rullar',
+ restoreTitle: 'Återställ valda rullar',
+ resetUsageTitle: 'Återställ användning på valda rullar',
+ deleteMessage: 'Ta bort {{count}} rullar permanent? Detta kan inte ångras.',
+ archiveMessage: 'Arkivera {{count}} rullar? De kan återställas senare.',
+ restoreMessage: 'Återställ {{count}} arkiverade rullar?',
+ resetUsageMessage: 'Återställ "Totalt förbrukat"-räknaren på {{count}} rullar? Återstående vikt bevaras.',
+ updateSuccess: '{{count}} rullar uppdaterade',
+ updateFailed: 'Kunde inte uppdatera',
+ updatePartial: '{{ok}} rullar uppdaterade, {{failed}} misslyckades',
+ updateAllFailed: 'Alla {{count}} rulluppdateringar misslyckades — val bevaras så att du kan försöka igen',
+ deleteSuccess: '{{count}} rullar borttagna',
+ deleteFailed: 'Kunde inte ta bort',
+ deletePartial: '{{ok}} rullar borttagna, {{failed}} misslyckades',
+ deleteAllFailed: 'Alla {{count}} rullborttagningar misslyckades — val bevaras så att du kan försöka igen',
+ archiveSuccess: '{{count}} rullar arkiverade',
+ archiveFailed: 'Kunde inte arkivera',
+ archivePartial: '{{ok}} rullar arkiverade, {{failed}} misslyckades',
+ archiveAllFailed: 'Alla {{count}} rullarkiveringar misslyckades — val bevaras så att du kan försöka igen',
+ restoreSuccess: '{{count}} rullar återställda',
+ restoreFailed: 'Kunde inte återställa',
+ restorePartial: '{{ok}} rullar återställda, {{failed}} misslyckades',
+ restoreAllFailed: 'Alla {{count}} rullåterställningar misslyckades — val bevaras så att du kan försöka igen',
+ invalidHex: 'Ange 6 hex-tecken (RRGGBB) eller 8 (RRGGBBAA). Fältet kommer inte att tillämpas annars.',
+ },
+ spoolmanMixedContentTitle: 'Spoolman kan inte laddas över HTTPS — blandat innehåll blockeras av din webbläsare',
+ spoolmanMixedContentBody: 'Bambuddy serveras över HTTPS (via din omvända proxy), men din Spoolman-URL är fortfarande vanlig HTTP. Webbläsare blockerar blandat innehåll av säkerhetsskäl, så det inbäddade Spoolman-gränssnittet kan inte renderas. Spoolman måste vara åtkomligt över HTTPS för att detta ska fungera.',
+ spoolmanMixedContentFixReverseProxy: 'Placera Spoolman bakom samma omvända proxy som Bambuddy (Traefik / Nginx / Caddy) med HTTPS, uppdatera sedan Spoolman-URL:en i Inställningar till den nya HTTPS-adressen.',
+ spoolmanMixedContentFixOpenNewTab: 'För en tillfällig lösning, öppna Spoolman i en ny webbläsarflik över HTTP — regler för blandat innehåll gäller endast för inbäddade ramar, så en fristående flik fungerar fortfarande.',
+ spoolmanOpenInNewTab: 'Öppna Spoolman i en ny flik',
+ labels: {
+ title: 'Skriv ut rulletiketter',
+ selectedCount: '{{count}} valda',
+ pickSpools: 'Välj vilka rullar du vill skriva ut etiketter för:',
+ monochrome: 'Monokrom (svartvit skrivare)',
+ monochromeHint: 'Tar bort färgprovet och breddar texten',
+ startingPosition: 'Startposition för etikett',
+ startingPositionRange: 'Endast arkmallar: L7160 stöder 1–21; 5160 stöder 1–30.',
+ startingPositionInvalid: 'Ange ett heltal från 1 till {{capacity}}.',
+ startingPositionFirst: 'Utskriften börjar vid position 1.',
+ startingPositionSkipped: 'Positionerna 1 till {{lastPosition}} lämnas tomma på det första arket.',
+ startingPositionRangeError: 'Startpositionen måste vara mellan 1 och {{capacity}} för detta ark.',
+ searchPlaceholder: 'Sök namn, märke eller #ID',
+ filterByMaterial: 'Material:',
+ allMaterials: 'Alla',
+ selectVisible: 'Markera alla synliga ({{count}})',
+ deselectVisible: 'Avmarkera synliga',
+ clearAll: 'Rensa alla',
+ noSpoolsToShow: 'Inga rullar att visa. Justera ditt filter och försök igen.',
+ noMatches: 'Inga rullar matchar den aktuella sökningen eller filtren.',
+ printOne: 'Skriv ut etikett för denna rulle',
+ printLabels: 'Skriv ut etiketter…',
+ bulkTitle: 'Välj rullar för att skriva ut etiketter från de {{count}} som visas',
+ noSpoolsTitle: 'Inga rullar att göra etikett för',
+ error: 'Kunde inte generera etiketter: {{msg}}',
+ sortBy: {
+ label: 'Sortera:',
+ id: 'Efter ID',
+ color: 'Efter färg',
+ },
+ templates: {
+ amsHolderSmall: {
+ label: 'AMS-hållare — liten (74 × 33 mm)',
+ hint: 'En etikett per sida; matchar den utskrivbara etiketten från MakerWorld-modell 752566 (AMS Filament Label Holder).',
+ },
+ amsHolderLarge: {
+ label: 'AMS-hållare — stor (75 × 55 mm)',
+ hint: 'En etikett per sida; passar varianten med pappersinsats av AMS Filament Label Holder. Tillräckligt med utrymme för färgprov, märke, material, ID och QR-kod.',
+ },
+ box40x30: {
+ label: 'Lådetikett (40 × 30 mm)',
+ hint: 'En etikett per sida; vanlig DK/Brother-rullstorlek, bra för filamentpåse- och lagringslådetiketter.',
+ },
+ box: {
+ label: 'Lådetikett (62 × 29 mm)',
+ hint: 'En etikett per sida; storlek för Brother PT/QL och Dymo små etiketter.',
+ },
+ averyL7160: {
+ label: 'Avery L7160 — A4-ark (38,1 × 63,5 mm × 21)',
+ hint: 'EU-ark; 21 etiketter per A4-sida.',
+ },
+ avery5160: {
+ label: 'Avery 5160 — US Letter-ark (25,4 × 66,7 mm × 30)',
+ hint: 'US-ark; 30 etiketter per Letter-sida.',
+ },
+ },
+ },
+ csv: {
+ importButton: 'Importera CSV',
+ exportButton: 'Exportera CSV',
+ modalTitle: 'Importera rullar från CSV',
+ selectFile: 'Välj en CSV-fil eller dra den hit',
+ dragHint: 'Rubrik: material (obligatoriskt), brand, subtype, color_name, rgba, …',
+ parsing: 'Läser fil…',
+ previewError: 'Kunde inte läsa CSV-filen',
+ validCount: '{{count}} giltiga',
+ errorCount: '{{count}} fel',
+ skippedCount: '{{count}} hoppade över',
+ colRow: 'Rad',
+ colStatus: 'Status',
+ colColor: 'Färg',
+ colorResolved: 'Färg ifylld från katalog',
+ colorCrossMaterial: 'Färg tagen från ett annat material — ingen exakt matchning i katalogen',
+ duplicateExisting: 'En rulle med detta material, märke och färg finns redan — den kommer fortfarande att importeras som en ny rulle',
+ spoolmanHint: 'I Spoolman-läge, använd Spoolmans inbyggda CSV-import/export.',
+ importValidRows: 'Importera {{count}} giltiga rader',
+ noValidRows: 'Inga giltiga rader',
+ importing: 'Importerar…',
+ importSuccess: '{{count}} rullar importerade',
+ importError: 'Kunde inte importera',
+ exportError: 'Kunde inte exportera',
+ },
+ addSpool: 'Lägg till rulle',
+ editSpool: 'Redigera rulle',
+ copySpool: 'Kopiera rulle',
+ material: 'Material',
+ selectMaterial: 'Välj material...',
+ subtype: 'Subtyp',
+ brand: 'Märke',
+ searchBrand: 'Sök märke...',
+ useCustomBrand: 'Använd "{{brand}}"',
+ useCustomMaterial: 'Använd anpassat material: {{material}}',
+ suggestedOptions: 'Föreslagna',
+ allOptions: 'Alla',
+ colorName: 'Färgnamn',
+ colorNamePlaceholder: 'Jade-vit, Eldröd...',
+ color: 'Färg',
+ hexColor: 'Hex-färg',
+ pickColor: 'Välj anpassad färg',
+ labelWeight: 'Etikettvikt',
+ coreWeight: 'Tom rullvikt',
+ searchSpoolWeight: 'Sök rullvikt...',
+ weightUsed: 'Använt',
+ currentWeight: 'Återstående vikt',
+ measuredWeight: 'Uppmätt vikt',
+ spoolName: 'Rulle',
+ costPerKg: 'Kostnad per kg',
+ storageLocation: 'Lagringsplats',
+ temperature: 'Temperatur',
+ humidity: 'Luftfuktighet',
+ battery: 'Batteri',
+ storageLocationPlaceholder: 't.ex. Hylla A, Låda 1',
+ openInInventory: 'Öppna i inventarium',
+ measuredWeightError: 'Uppmätt vikt måste vara mellan {{min}}g och {{max}}g.',
+ slicerFilament: 'Slicer-filament',
+ slicerFilamentName: 'Slicer-förinställningsnamn',
+ slicerPreset: 'Slicer-förinställning',
+ searchPresets: 'Sök filamentförinställningar...',
+ selectedPreset: 'Vald',
+ noPresetsFound: 'Inga förinställningar hittade',
+ tempOverrides: 'Temperaturåsidosättningar',
+ note: 'Anteckning',
+ notePlaceholder: 'Eventuella ytterligare anteckningar om denna rulle...',
+ // Per-spool category + low-stock threshold override (#729)
+ category: 'Kategori',
+ categoryPlaceholder: 't.ex. Produktion, Prototyp, Kund A',
+ categoryNone: 'Okategoriserad',
+ // #1400: storage-location filter chip — `storageLocation` label is
+ // already defined above for the spool-edit field, reused here for the
+ // dropdown header. `storageLocationNone` is new (the "no location set"
+ // group, mirrors `categoryNone`).
+ storageLocationNone: 'Ingen lagringsplats inställd',
+ lowStockThresholdOverride: 'Lågt lagertröskelvärde (denna rulle)',
+ lowStockThresholdOverrideHelp: 'Lämna tomt för att använda det globala tröskelvärdet ({{global}}%).',
+ // RFID button rename (was "Delete Tag" — confusing because it sounds like a
+ // taxonomy delete; this clears the RFID tag/UUID off the spool record)
+ clearRfid: 'Rensa RFID-tagg',
+ rfidCleared: 'RFID-tagg rensad',
+ archive: 'Arkivera',
+ restore: 'Återställ',
+ noSpools: 'Inga rullar än. Lägg till din första rulle för att komma igång.',
+ noAvailableSpools: 'Inga rullar tillgängliga. Lägg till en rulle i ditt inventarium eller avlänka en från ett annat fack först.',
+ kProfiles: 'K-profiler',
+ addKProfile: 'Lägg till K-profil',
+ assignSpool: 'Tilldela rulle',
+ unassignSpool: 'Avlänka',
+ assignSuccess: 'Rulle tilldelad och AMS-fack konfigurerat',
+ assignPendingInsert: 'Tilldelad. Facket kommer att konfigureras när du sätter in rullen.',
+ assignFailed: 'Kunde inte tilldela rulle',
+ selectSpool: 'Välj en rulle att tilldela detta fack',
+ assigned: 'Tilldelad',
+ assigning: 'Tilldelar…',
+ searchSpools: 'Sök rullar...',
+ showAllSpools: 'Visa alla rullar',
+ spoolmanSpools: 'Spoolman-rullar',
+ allMaterials: 'Alla material',
+ filterByBrand: 'Filtrera efter märke...',
+ showArchived: 'Visa arkiverade',
+ quickAdd: 'Snabb-lägg-till (Lager)',
+ quantity: 'Kvantitet',
+ stock: 'Lager',
+ configured: 'Konfigurerad',
+ spoolsCreated: '{{count}} rullar skapade',
+ spoolsPartiallyCreated: '{{created}} av {{total}} rullar skapade (några misslyckades)',
+ spoolCreated: 'Rulle skapad',
+ spoolUpdated: 'Rulle uppdaterad',
+ spoolDeleted: 'Rulle borttagen',
+ deepLinkSpoolNotFound: 'Rulle hittades ej',
+ deepLinkFetchFailed: 'Kunde inte ladda rulle — försök igen',
+ spoolArchived: 'Rulle arkiverad',
+ spoolRestored: 'Rulle återställd',
+ kProfileSaveFailed: 'K-profilinställningar kunde inte sparas',
+ syncWeightSpoolNotFound: 'Rulle hittades inte — den kan ha tagits bort',
+ syncWeightSpoolmanUnreachable: 'Spoolman är otillgänglig — försök igen senare',
+ syncWeightFailed: 'Kunde inte synkronisera vikt',
+ spoolmanUnreachable: 'Spoolman kan ej nås — försök igen senare',
+ deleteSpoolNotFound: 'Rulle hittades inte — den kan redan ha tagits bort',
+ deleteFailed: 'Kunde inte ta bort rulle',
+ archiveSpoolNotFound: 'Rulle hittades inte — den kan redan ha tagits bort',
+ archiveFailed: 'Kunde inte arkivera rulle',
+ restoreSpoolNotFound: 'Rulle hittades inte — den kan redan ha tagits bort',
+ restoreFailed: 'Kunde inte återställa rulle',
+ saveFailed: 'Kunde inte spara ändringar',
+ tagClearFailed: 'Kunde inte rensa tagg',
+ deleteConfirm: 'Är du säker på att du vill ta bort denna rulle? Detta kan inte ångras.',
+ archiveConfirm: 'Är du säker på att du vill arkivera denna rulle?',
+ advancedSettings: 'Avancerade inställningar',
+ // Tabs
+ filamentInfoTab: 'Filamentinformation',
+ paProfileTab: 'PA-profil',
+ colorAndCostTab: 'Färg & kostnad',
+ printersTab: 'Skrivare',
+ filamentInfo: 'Filament',
+ additional: 'Ytterligare',
+ // Cloud
+ loadingPresets: 'Laddar molnförinställningar…',
+ cloudConnected: 'Moln anslutet',
+ cloudNotConnected: 'Moln ej anslutet (använder standard)',
+ // Colors
+ recentColors: 'Senaste',
+ searchColors: 'Sök färger...',
+ searchResults: 'Sökresultat',
+ allColors: 'Alla färger',
+ commonColors: 'Vanliga färger',
+ showLess: 'Visa färre',
+ showAll: 'Visa alla',
+ noColorsFound: 'Inga färger matchar din sökning',
+ noResults: 'Inga matchningar hittade',
+ // Multi-color gradient + visual effect (#1154)
+ extraColorsLabel: 'Extra färger',
+ extraColorsPlaceholder: 'EC984C,#6CD4BC,A66EB9,D87694',
+ extraColorsHint: 'Klistra in 2 till 8 hex-stoppar, åtskilda av kommatecken. Renderas som en gradient.',
+ extraColorsInvalid: 'Ignorerade ogiltiga hex: {{tokens}}',
+ colorEffectLabel: 'Effekt',
+ colorEffect: {
+ none: 'Ingen',
+ // Surface effects
+ sparkle: 'Gnistrande',
+ wood: 'Trä',
+ marble: 'Marmor',
+ glow: 'Lysande',
+ matte: 'Matt',
+ // Sheen / finish variants
+ silk: 'Silke',
+ galaxy: 'Galax',
+ rainbow: 'Regnbåge',
+ metal: 'Metall',
+ translucent: 'Genomskinlig',
+ // Multi-color structural variants
+ gradient: 'Gradient',
+ dualColor: 'Dubbelfärg',
+ triColor: 'Trippelfärg',
+ multicolor: 'Multifärg',
+ },
+ // PA Profiles
+ selectMaterialFirst: 'Välj ett material först i fliken Filamentinformation.',
+ noPrintersConfigured: 'Inga skrivare konfigurerade. Lägg till skrivare för att använda PA-profiler.',
+ matchingFilter: 'Matchande',
+ anyBrand: 'Alla märken',
+ anyVariant: 'Alla varianter',
+ autoSelect: 'Auto-välj',
+ matches: 'matchningar',
+ match: 'matchning',
+ noMatches: 'Inga matchningar',
+ connected: 'Ansluten',
+ offline: 'Offline',
+ printerOffline: 'Skrivaren är offline. Anslut för att visa kalibreringsprofiler.',
+ noKProfilesMatch: 'Inga K-profiler matchar det valda filamentet.',
+ leftNozzle: 'Vänster munstycke',
+ rightNozzle: 'Höger munstycke',
+ profilesSelected: 'kalibreringsprofil(er) valda',
+ // Printers tab: per-model filament preset + per-hotend K profile
+ filamentPreset: 'Filamentförinställning',
+ presetUseSpoolDefault: 'Använd rullens förinställning',
+ presetInherited: 'ärvd',
+ presetOverride: 'åsidosättning',
+ presetNeedsModel: 'Den här skrivaren har inte rapporterat sin modell än, så den kan inte ha en egen förinställning.',
+ autoMatchPresets: 'Auto-matcha',
+ autoMatchPresetsHint: 'Hitta varianten av den här rullens förinställning som namnger varje modell',
+ kProfilesPerPrinter: 'K-profiler',
+ kProfileNotSet: 'Ej inställt',
+ kProfileFlowMismatch: 'Denna profil mättes på ett {{profile}} munstycke men ett {{fitted}} munstycke är monterat, så det kommer inte att appliceras',
+ nozzle: 'Munstycke',
+ unknownModel: 'Okänd modell',
+ onePrinter: '1 skrivare',
+ nPrinters: '{{n}} skrivare',
+ filamentPresetsLoadFailed: 'Det gick inte att ladda denna rullens per-modell-förinställningar',
+ filamentPresetSaveFailed: 'Det gick inte att spara per modell-förinställningarna',
+ // Stats & enhanced table
+ totalInventory: 'Total inventarium',
+ totalConsumed: 'Totalt förbrukat',
+ byMaterial: 'Efter material',
+ inPrinter: 'I skrivare',
+ lowStock: 'Lågt lager',
+ sinceTracking: 'Sedan räkning startade',
+ resetConsumedCounter: 'Återställ räknare',
+ resetConsumedCounterTooltip: 'Nollställ den förbrukade gramräknaren för denna rulle. Återstående vikt ändras ej.',
+ resetConsumedCounterConfirm: 'Återställ denna rulles förbrukade gramräknare till 0? Framtida utskrifter kommer att räknas från noll igen. Rullen själv, dess återstående viktberäkning och dina inställningar ändras ej.',
+ resetAllConsumedCounters: 'Återställ alla räknare',
+ resetAllConsumedCountersTooltip: 'Nollställ den förbrukade gramräknaren på varje rulle. Återstående vikter ändras inte.',
+ resetAllConsumedCountersConfirm: 'Återställ den förbrukade gramräknaren till 0 på alla {{count}} rullar (inklusive arkiverade)? Detta rensar "Totalt förbrukat"-statistiken så att framtida utskrifter räknas från noll. Rullar och återstående vikter ändras ej.',
+ consumedCounterReset: 'Räknare återställd',
+ allConsumedCountersReset: 'Räknare återställd för {{count}} rulle(r)',
+ resetConsumedCounterFailed: 'Kunde inte återställa räknare',
+ loadedInAms: 'Laddad i AMS/Ext',
+ remaining: 'Återstående',
+ weightCheck: 'Viktkontroll',
+ lastWeighed: 'Senast vägd',
+ neverWeighed: 'Aldrig vägd',
+ search: 'Sök rullar...',
+ showing: 'Visar',
+ to: 'till',
+ of: 'av',
+ show: 'Visa',
+ spools: 'rullar',
+ spool: 'rulle',
+ page: 'Sida',
+ noSpoolsMatch: 'Inga resultat hittade',
+ noSpoolsMatchDesc: 'Försök justera din sökning eller filter för att hitta det du letar efter.',
+ active: 'Aktiv',
+ archived: 'Arkiverad',
+ all: 'Alla',
+ used: 'Använd',
+ new: 'Ny',
+ clearFilters: 'Rensa filter',
+ table: 'Tabell',
+ cards: 'Kort',
+ net: 'Nät',
+ // Grouping
+ groupSimilar: 'Gruppera',
+ groupedSpools: '{{count}} identiska rullar',
+ groupedRows: 'rader',
+ // Column config
+ columns: 'Kolumner',
+ configureColumns: 'Konfigurera kolumner',
+ configureColumnsDesc: 'Dra för att omordna kolumner eller använd pilarna. Växla synlighet med ögonikonen.',
+ visible: 'synlig',
+ reset: 'Återställ',
+ cancel: 'Avbryt',
+ applyChanges: 'Tillämpa ändringar',
+ moveUp: 'Flytta upp',
+ moveDown: 'Flytta ner',
+ hideColumn: 'Dölj kolumn',
+ showColumn: 'Visa kolumn',
+ // Tag linking
+ linkToSpool: 'Länka till rulle',
+ tagLinked: 'Tagg länkad till rulle',
+ tagLinkFailed: 'Kunde inte länka tagg',
+ tagAlreadyLinked: 'Tagg är redan länkad till en annan rulle #{{id}}',
+ unknownTag: 'Okänd RFID-tagg upptäckt',
+ // Usage history
+ usageHistory: 'Användningshistorik',
+ noUsageHistory: 'Ingen användning registrerad än',
+ printName: 'Utskriftsnamn',
+ weightConsumed: 'Vikt förbrukat',
+ clearHistory: 'Rensa',
+ historyCleared: 'Användningshistorik rensad',
+ fillSourceLabel: '(Inv)',
+ lowStockThresholdError: 'Tröskelvärdet måste vara mellan 0,1 och 99,9',
+ assignMismatchTitle: 'Materialfelmatchning',
+ assignMismatchMessage: 'Det valda rullmaterialet "{{spoolMaterial}}" matchar inte fackmaterialet "{{trayMaterial}}" för {{location}}. Tilldela ändå?',
+ assignMismatchConfirm: 'Tilldela ändå',
+ assignPartialMismatchMessage: 'Rullmaterialet "{{spoolMaterial}}" liknar "{{trayMaterial}}" i {{location}} men matchar inte exakt. Vill du fortsätta?',
+ assignProfileMismatchMessage: 'Rullprofilen "{{spoolProfile}}" matchar inte fackprofilen "{{trayProfile}}" i {{location}}. Vill du fortsätta?',
+ assignReconfigureNote: 'AMS-facket kommer att omkonfigureras för att använda rullens profil.',
+ // Spoolman filament catalog picker
+ spoolmanFilamentCatalog: 'Spoolman Filamentkatalog',
+ pickFromSpoolmanCatalog: 'Välj från Spoolman-katalog…',
+ spoolmanFilamentSelected: 'Filament valt från Spoolman-katalog',
+ spoolmanFilamentUnlinked: 'Filamentkataloglänk rensad',
+ noSpoolmanFilaments: 'Inga filament hittade i Spoolman-katalog',
+ spoolmanFilamentColorSwatch: 'Filamentfärg',
+ spoolWeightManagedBySpoolman: 'Tom rullvikt hanteras per filamenttyp i Spoolman',
+ spoolmanCatalogLoadFailed: 'Kunde inte ladda Spoolman filamentkatalog',
+ },
+
+ // Timelapse
+ timelapse: {
+ title: 'Timelapse',
+ create: 'Skapa timelapse',
+ download: 'Ladda ner',
+ delete: 'Ta bort',
+ preview: 'Förhandsvisning',
+ frameRate: 'Bildfrekvens',
+ quality: 'Kvalitet',
+ processing: 'Bearbetar...',
+ noTimelapses: 'Inga timelapse tillgängliga',
+ },
+
+ // AMS
+ ams: {
+ title: 'AMS',
+ slot: 'Fack',
+ empty: 'Tomt',
+ emptySlot: 'Tomt fack',
+ slotEmpty: 'Tomt',
+ slotUnconfigured: '?',
+ emptySlotReset: 'Inget filament tilldelat',
+ unknown: 'Okänd',
+ humidity: 'Luftfuktighet',
+ temperature: 'Temperatur',
+ filamentType: 'Filamenttyp',
+ filamentColor: 'Färg',
+ remaining: 'Återstående',
+ history: 'AMS-historik',
+ noHistory: 'Ingen historik tillgänglig',
+ configureSlot: 'Konfigurera fack',
+ externalSpool: 'Extern rulle',
+ profile: 'Profil',
+ kFactor: 'K-faktor',
+ kFactorShort: 'K',
+ fill: 'Fyll',
+ configure: 'Konfigurera',
+ used: 'använt',
+ remainingUnit: 'återstående',
+ },
+
+ // Print modal
+ printModal: {
+ variants: {
+ editNote: 'Dessa alternativ sattes när jobbet köades. Avbryt och köa om för att ändra dem.',
+ title: 'Skrivaralternativ',
+ help: 'Ett jobb, en köplats. Den första matchande skrivaren som blir ledig kör sin fil.',
+ unknownModel: 'Okänd modell',
+ plateFor: 'Platta för {{filename}}',
+ moveUp: 'Flytta upp',
+ moveDown: 'Flytta ner',
+ queued: 'Köat med {{count}} alternativ',
+ },
+ selectPrinter: 'Välj skrivare',
+ selectPlate: 'Välj platta',
+ filamentMapping: 'Filamentmappning',
+ useArchiveMapping: 'Mappning',
+ useArchiveMappingTooltip: 'Välj alla fack från AMS-mappningen som sparades med detta arkiv (från slicern), istället för att matcha efter typ/färg.',
+ clickToChangeSlot: 'Klicka för att ändra facktilldelning',
+ reRead: 'Läs om',
+ plateN: 'Platta {{n}}',
+ plateFilamentsUnreadable: 'Filamenten för en vald platta kunde inte läsas, så den kan inte mappas. Avmarkera den för att köa de andra.',
+ totalCost: 'Total kostnad:',
+ slotRemainingShort: ' - {{grams}}g kvar',
+ printSettings: 'Utskriftsinställningar',
+ bedLeveling: 'Byggplattsnivåinställning',
+ flowCalibration: 'Flödeskalibrering',
+ vibrationCalibration: 'Vibrationskalibrering',
+ layerInspection: 'Förstaskiktsinspektion',
+ timelapse: 'Timelapse',
+ cancel: 'Avbryt',
+ noPrintersAvailable: 'Inga skrivare tillgängliga',
+ printerBusy: 'Skrivaren är upptagen',
+ printerOffline: 'Skrivaren är offline',
+ sameTypeDifferentColor: 'Samma typ, olika färg',
+ filamentTypeNotLoaded: 'Filamenttyp inte laddad',
+ statusReady: 'Redo',
+ statusColorMismatch: 'Färg matchar ej',
+ statusTypeNotFound: 'Typ hittades ej',
+ requiredTooltip: 'Kräver: {{name}} - {{color}}',
+ manuallySelected: 'Manuellt vald',
+ autoMatched: 'Auto-matchad',
+ selectSlot: 'Välj fack',
+ sameTypeDifferentColorDetail: 'Samma typ, annan färg: behöver {{required}}, facket har {{loaded}}',
+ requiredTypeNotInPrinter: 'Krävd filamenttyp hittades inte i skrivaren.',
+ whenToPrint: 'När utskriften ska starta',
+ asap: 'Så snart som möjligt',
+ queue: 'Kö',
+ schedule: 'Schemalägg',
+ dateTime: 'Datum & tid',
+ invalidDateTime: 'Ange ett giltigt datum och tid',
+ openCalendar: 'Öppna kalender',
+ requireManualStart: 'Kräv manuell start',
+ requirePreviousSuccess: 'Starta endast om föregående utskrift lyckades',
+ autoOffAfter: 'Stäng av skrivaren när den är klar',
+ helpAsap: 'Utskriften kommer att läggas till i toppen av kön och starta så snart en lämplig skrivare är ledig.',
+ helpSchedule: 'Utskriften kommer att starta vid den schemalagda tiden om skrivaren är ledig. Om den är upptagen kommer den att vänta tills skrivaren blir tillgänglig.',
+ helpQueue: 'Utskriften kommer att läggas till i slutet av kön.',
+ leftNozzle: 'V',
+ rightNozzle: 'H',
+ leftNozzleTooltip: 'Vänster munstycke',
+ rightNozzleTooltip: 'Höger munstycke',
+ ftsSameInletHint: 'Alla filament för denna print sitter på filamentspårväljare IN-{{inlet}}. Växlingen mellan filament på samma ingång är långsammare — flytta den ena till en AMS på den andra ingången för att snabba upp växlingen.',
+ rackPosition: 'Hyllposition',
+ rackPositionTooltip: 'Vilket munstycke på hyllan skriver ut detta filament? Positionerna är numrerade som på skrivaren..',
+ rackEmptyPosition: 'Denna hyllposition är tom',
+ rackWrongNozzle: 'Har {{has}} munstycke; detta filament behöver {{needs}}',
+ filamentOverride: 'Åsidosätt filament',
+ filamentOverrideHint: 'Välj att åsidosätta filament för modellbaserad tilldelning. Schemaläggaren kommer att matcha mot dina valda filament istället för de ursprungliga 3MF-värdena.',
+ originalFilament: 'Original',
+ overrideWith: 'Åsidosätt med',
+ resetToOriginal: 'Återställ till original',
+ insufficientFilamentTitle: 'Otillräckligt filament',
+ waitingForAmsStatus: 'Väntar på AMS-status från {{printer}}…',
+ insufficientFilamentMessage: 'Vissa tilldelade rullar har mindre filament kvar än denna utskrift behöver:',
+ insufficientFilamentLine: '{{printer}} - {{slot}}: behöver {{required}}g, återstående {{remaining}}g',
+ insufficientFilamentLinePooled: '{{printer}} - {{slot}}: behöver {{required}}g, {{remaining}}g tillgängligt över matchande rullar (AMS-filamentbackup)',
+ printAnyway: 'Skriv ut ändå',
+ forceColorMatch: 'Tvinga färgmatchning',
+ staggerPrinterStarts: 'Förskjut skrivarstart',
+ staggerGroupSize: 'Gruppstorlek',
+ staggerInterval: 'Intervall (min)',
+ staggerPreview: '{{printers}} skrivare → {{groups}} grupper om {{size}}, startar var {{interval}} minut',
+ staggerLastGroup: 'sista grupp: {{count}}',
+ staggerTotal: 'totalt: {{minutes}} min',
+ staggerToPrinters: 'Förskjut till {{count}} skrivare',
+ gcodeInjection: 'Injicera auto-utskrifts-G-kod',
+ insufficientBudget: 'Otillräcklig budget',
+ unlimitedNoBudget: 'Obgränsad – ingen budgetgräns är inställd.',
+ noPrintableCostCenters: 'Inget aktivt kostnadsställe är tillgängligt för utskrift. Be en administratör att ge dig utskriftsåtkomst.',
+ },
+
+ // Backup
+ backup: {
+ includesEncryptionKey: 'Lokala säkerhetskopior inkluderar MFA-krypteringsnyckelfilen (DATA_DIR/.mfa_encryption_key), så en säkerhetskopie-ZIP är självständig. Behandla ZIP-filen som känslig — vem som helst med filen kan dekryptera OIDC-klienthemligheterna och TOTP-hemligheterna som lagras inuti.',
+ title: 'Säkerhetskopia & Återställning',
+ createBackup: 'Skapa säkerhetskopia',
+ restoreBackup: 'Återställ säkerhetskopia',
+ restoreDescription: 'Ersätt all data från en säkerhetskopia',
+ downloadBackup: 'Ladda ner säkerhetskopia',
+ uploadBackup: 'Ladda upp säkerhetskopia',
+ lastBackup: 'Senaste säkerhetskopia',
+ autoBackup: 'Automatisk säkerhetskopia',
+ backupNow: 'Säkerhetskopiera nu',
+ restoreWarning: 'Varning: Återställning av en säkerhetskopia kommer att skriva över all nuvarande data.',
+ includeArchives: 'Inkludera arkiv',
+ includeSettings: 'Inkludera inställningar',
+ includeProfiles: 'Inkludera profiler',
+ backupSuccess: 'Säkerhetskopia skapad',
+ restoreSuccess: 'Säkerhetskopia återställd',
+ backupFailed: 'Kunde inte skapa säkerhetskopia',
+ restoreFailed: 'Kunde inte återställa säkerhetskopia',
+ restoreNote: 'Virtuell skrivare kommer att stoppas under återställning',
+
+ // GitHub Backup
+ githubBackup: 'Git-säkerhetskopia',
+ enabled: 'Aktiverad',
+ cloudLoginRequired: 'Inloggning till Bambu Cloud krävs. Logga in under Profiler → Molnprofiler för att aktivera GitHub-säkerhetskopia.',
+ cloudLoginRequiredShort: 'Molninloggning krävs',
+ githubDescription: 'Synkronisera dina profiler automatiskt till ett privat GitHub-kodförråd för säkerhetskopiering och versionshistorik.',
+ repoIsPrivate: 'Kodförrådet är privat — säkert att säkerhetskopiera till.',
+ repoIsPublicWarning: 'Kodförrådet är PUBLIKT. Bambuddy-säkerhetskopior inkluderar MQTT-uppgifter, Home Assistant-tokens, Prometheus-tokens, din Bambu Cloud-e-post och skrivaråtkomstkoder via K-profiler. Sparning blockeras tills du gör repositoryt privat i din leverantörs inställningar.',
+ repoVisibilityUnknown: 'Kunde inte fastställa repositoryns synlighet. Bambuddy vägrar att säkerhetskopiera till något som inte bekräftats som privat; sparande kommer att blockeras.',
+ repositoryUrl: 'Kodförråd-URL',
+ repoUrlPlaceholderGitHub: 'https://github.com/användarnamn/repo-namn',
+ repoUrlPlaceholderGitea: 'https://gitea.example.com/användarnamn/repo-namn',
+ repoUrlPlaceholderForgejo: 'https://forgejo.example.com/användarnamn/repo-namn',
+ repoUrlPlaceholderGitLab: 'https://gitlab.com/användarnamn/repo-namn',
+ allowInsecureHttp: 'Tillåt osäker HTTP',
+ allowInsecureHttpHint: 'Aktivera för självhostade instanser på privata nätverk utan TLS',
+ personalAccessToken: 'Personlig åtkomsttoken',
+ tokenSaved: '(sparad)',
+ enterNewToken: 'Ange ny token för att uppdatera',
+ tokenHintGitHub: 'Finkornig token med Contents-läs- och skrivåtkomst, eller en klassisk token med repo-omfång.',
+ tokenHintGitLab: 'Token med api-omfång, eller read_repository och write_repository tillsammans.',
+ tokenHintGitea: 'Token med write:repository-omfång.',
+ tokenHintForgejo: 'Token med write:repository-omfång. En token begränsad till detta enda kodförråd räcker.',
+ branch: 'Gren',
+ provider: 'Git-leverantör',
+ providerGitHub: 'GitHub',
+ providerGitLab: 'GitLab',
+ providerGitea: 'Gitea',
+ providerForgejo: 'Forgejo',
+ manualOnly: 'Endast manuell',
+ hourly: 'Varje timme',
+ daily: 'Dagligen',
+ weekly: 'Veckovis',
+ includeInBackup: 'Inkludera i säkerhetskopia',
+ kProfiles: 'K-profiler',
+ kProfilesDescription: 'Tryckförskottskalibrering från anslutna skrivare',
+ noPrintersConnected: 'Inga skrivare anslutna',
+ printersConnected: '{{connected}}/{{total}} anslutna',
+ cloudProfiles: 'Molnprofiler',
+ cloudProfilesDescription: 'Filament-, skrivar- och processförinställningar från Bambu Cloud och Orca Cloud',
+ cloudProfilesAccounts: 'Anslutna konton — Bambu Cloud: {{bambu}}, Orca Cloud: {{orca}}',
+ appSettings: 'App-inställningar',
+ appSettingsDescription: 'Bambuddy-konfiguration (komplett databas)',
+ spoolInventory: 'Rullinventarium',
+ spoolInventoryDescription: 'Filamentrullar, användningshistorik och kostnadsspårning',
+ printArchives: 'Utskriftsarkiv',
+ printArchivesDescription: 'Utskriftshistorik-metadata (inga gcode/3MF-filer)',
+ lastBackupAt: 'Senaste säkerhetskopia:',
+ noBackupsYet: 'Inga säkerhetskopior än',
+ next: 'Nästa:',
+ startingBackup: 'Startar säkerhetskopia...',
+ test: 'Test',
+ enableBackup: 'Aktivera säkerhetskopia',
+ testConnection: 'Testa anslutning',
+ enterRepoUrl: 'Ange kodförråds-URL',
+ enterRepoAndToken: 'Ange kodförråds-URL och åtkomsttoken',
+ repoRequired: 'Kodförråds-URL krävs',
+ tokenRequired: 'Åtkomsttoken krävs',
+ githubBackupEnabled: 'GitHub-säkerhetskopia aktiverad',
+ tokenUpdated: 'Token uppdaterad',
+ settingsSaved: 'Inställningar sparade',
+ failedToSave: 'Kunde inte spara: {{message}}',
+ backupCompleteFiles: 'Säkerhetskopia slutförd - {{count}} filer uppdaterade',
+ backupSkippedNoChanges: 'Säkerhetskopia hoppade över - inga ändringar',
+ backupFailed2: 'Kunde inte skapa säkerhetskopia: {{message}}',
+ clearedLogs: 'Rensade {{count}} loggar',
+ failedToClearLogs: 'Kunde inte rensa loggar: {{message}}',
+
+ // Restore from Git backup (#2656)
+ restoreFromGit: {
+ button: 'Återställ från Git',
+ title: 'Återställ från Git-säkerhetskopia',
+ subtitle: 'Välj en commit och välj vad som ska återställas',
+ commitLabel: 'Säkerhetskopie-commit',
+ latestCommit: 'Senaste säkerhetskopia (grenens topp)',
+ categoriesLabel: 'Vad som ska återställas',
+ inspecting: 'Läser säkerhetskopieinnehåll...',
+ itemCount: '{{count}} i säkerhetskopian',
+ overwriteLabel: 'Skriv över befintliga poster',
+ overwriteOn: 'Befintliga poster kommer att uppdateras från säkerhetskopian.',
+ overwriteOff: 'Endast saknade poster läggs till; befintliga lämnas orörda.',
+ selectedCount: '{{count}} valda',
+ restoring: 'Återställer...',
+ confirmTitle: 'Återställ från säkerhetskopia?',
+ confirmMessage: 'De valda kategorierna kommer att återställas från denna commit. Saknade poster läggs till; befintliga poster förblir oförändrade.',
+ confirmMessageOverwrite: 'De valda kategorierna kommer att återställas från denna commit, och skriva över poster som redan finns lokalt. Detta kan inte ångras.',
+ kprofilesOverwriteCaveat: 'K-profiler är undantaget: att skriva till ett fack ersätter alltid kalibreringen på skrivaren.',
+ tally: '{{restored}} återställda, {{skipped}} hoppade över, {{failed}} misslyckades',
+ reloadHint: 'Ladda om Bambuddy så att den återställda datan visas överallt.',
+ partialHint: 'Kategorierna som listas ovan är klara och på disken. Eventuella saknade kategorier kördes inte.',
+ failed: 'Kunde inte återställa.',
+ loadFailed: 'Kunde inte läsa säkerhetskopiekodförrådet.',
+ // Preview caveats. The server sends detail_code + detail_params and the
+ // English detail as defaultValue, same contract as backup.pathCheck.
+ details: {
+ notPresent: 'Finns inte i denna säkerhetskopie-commit',
+ unreadableJson: 'Oläsliga JSON: {{paths}}',
+ settingsNoPayload: 'Inga inställningar i nyttolast',
+ settingsCredentialsWillSkip: '{{count}} uppgift(er) av typen lösenord kommer att hoppas över',
+ settingsCompanionWillSkip: '{{count}} uppgift(er) av typen lösenord kommer att hoppas över, och {{companion}} växlar som beror på dem kommer att lämnas av',
+ settingsCompanionOnlyWillSkip: '{{companion}} växlar kommer att lämnas av — den uppgift som var och en behöver kan inte återställas från en säkerhetskopia',
+ spoolsUsageCount: 'inklusive {{count}} användningspost(er)',
+ archivesMetadataOnly: 'Endast metadata — 3MF-filer och minibilder finns inte i en Git-säkerhetskopia',
+ kprofilesPrinterCount: 'över {{count}} skrivare',
+ },
+ // Tally notes, same contract. noData is shared by all four categories:
+ // the category heading renders beside it, so naming the category again
+ // would be redundant.
+ notes: {
+ noData: 'Ingen data av denna typ i denna säkerhetskopia',
+ archivesPrinterMissing: 'Vissa arkiv refererade till skrivare som inte längre finns — länk rensad',
+ archivesProjectMissing: 'Vissa arkiv refererade till projekt som inte längre finns — länk rensad',
+ archivesOwnerCleared: 'Vissa arkiv refererade till användare som inte längre finns — ägare rensad, så de är synliga endast för användare med behörigheten archives:read_all tills en admin tilldelar dem på nytt',
+ archivesOwnerUnmatched: 'Vissa arkiv namnger en ägare som denna instans inte har — ägare rensad snarare än gissad från säkerhetskopians användar-ID, så de är synliga endast för användare med behörigheten archives:read_all tills en admin tilldelar dem på nytt',
+ archivesOwnerUnknown: 'Vissa arkiv återställdes utan en ägare — denna säkerhetskopia registrerar ingen, så de är synliga endast för användare med behörigheten archives:read_all tills en admin tilldelar dem på nytt',
+ archivesUndeleted: 'Arkiv som tagits bort sedan säkerhetskopian är synliga igen — skriv över var aktiverad',
+ archivesMetadataOnly: 'Återställda arkiv innehåller endast metadata — 3MF- och minibildfiler finns inte i en Git-säkerhetskopia',
+ spoolUsageUnresolved: '{{count}} användningspost(er) hoppade över — deras rulle finns inte i denna säkerhetskopias rulllista, så det finns inget att koppla dem till.',
+ spoolUsageUnlinked: '{{count}} användningspost(er) återställda utan sin utskriftshistorik-länk — välj Utskriftsarkiv tillsammans med Rullinventering för att behålla den.',
+ spoolTagKept: '{{count}} rulletagg(ar) lämnade som de är — säkerhetskopian skulle ha rensat en tagg som sedan dess skannats, eller flyttat en till en andra rulle.',
+ settingsCredentialsSkipped: '{{count}} uppgift(er) av typen lösenord hoppade över — ange hemligheter manuellt igen',
+ settingsAuthSkipped: '{{count}} autentiseringsinställning(ar) hoppade över — ändra dem i Inställningar > Autentisering så att uteslutningskontrollerna fortfarande körs',
+ settingsCompanionSkipped: '{{keys}} lämnade avstängda — den uppgift som var och en behöver kan inte återställas från en säkerhetskopia och denna instans har ingen lagrad, så att slå på dem skulle lämna integrationen oautentiserad',
+ settingsMqttRelayFailed: 'MQTT-inställningar återställda, men reläet kunde inte återanslutas — starta om Bambuddy',
+ kprofilesAlwaysOverwrite: 'K-profiler skriver alltid över det matchande facket på skrivaren',
+ kprofilesAckUnreliable: 'En skrivare som inte svarar räknas ändå som återställd — verifiera profilerna på skrivaren',
+ kprofilesPrinterMissing: 'Ingen skrivare med serienummer {{serial}} — hoppade över',
+ kprofilesPrinterOffline: '{{printer}} ({{serial}}) är inte ansluten — hoppade över',
+ kprofilesUnknownNozzle: 'Oväntad munstyckesdiameter {{nozzle}} för {{serial}} — skickad som den är',
+ kprofilesUnmatched: '{{count}} profil(er) för {{nozzle}} hade ingen motsvarighet på {{printer}} — lades till som nya profiler',
+ kprofilesSendFailed: 'Kunde inte skicka {{nozzle}}-profiler till {{printer}} ({{serial}})',
+ kprofilesRefused: '{{printer}} ({{serial}}) vägrade {{nozzle}}-profilerna: {{reason}}',
+ kprofilesStepFailed: 'K-profil-steget kunde inte slutföras — {{reason}}. Allt som återställdes före det är fortfarande sparat.',
+ },
+ },
+
+ // History
+ history: 'Historik',
+ clear: 'Rensa',
+ date: 'Datum',
+ status: 'Status',
+ trigger: 'Typ',
+ triggers: {
+ manual: 'Säkerhetskopia (manuell)',
+ scheduled: 'Säkerhetskopia (schemalagd)',
+ restore: 'Återställning',
+ },
+ commit: 'Commit',
+
+ // Local Backup
+ localBackup: 'Lokal säkerhetskopia',
+ localBackupDescription: 'Skapa en komplett säkerhetskopia av din Bambuddy-data inklusive databas, arkiv, uppladdningar och alla filer.',
+ downloadBackupLabel: 'Ladda ner säkerhetskopia',
+ completeBackupZip: 'Komplett säkerhetskopia: databas + alla filer (ZIP)',
+ download: 'Ladda ner',
+ preparingBackup: 'Förbereder säkerhetskopia...',
+ creatingArchive: 'Skapar säkerhetskopiearkiv... Detta kan ta en stund för stora arkiv.',
+ downloadingFile: 'Laddar ner säkerhetskopia...',
+ backupDownloaded: 'Säkerhetskopia nedladdad',
+ failedToCreateBackup: 'Kunde inte skapa säkerhetskopia: {{message}}',
+ restore: 'Återställ',
+ restoreReplacesAll: 'Återställning ersätter all data.',
+ restoreReplacesAllDetail: 'Din nuvarande databas och filer kommer att ersättas fullständigt. En omstart krävs efter återställning.',
+ restoreConfirmTitle: 'Återställ säkerhetskopia',
+ restoreConfirmMessage: 'Är du säker på att du vill återställa från "{{filename}}"? Detta kommer att ersätta din nuvarande databas och alla filer fullständigt. Applikationen måste startas om efter återställning.',
+ restoreConfirmButton: 'Återställ säkerhetskopia',
+ uploadingFile: 'Laddar upp säkerhetskopia...',
+ backupRestoredRestart: 'Säkerhetskopia återställd. Starta om Bambuddy.',
+ failedToRestore: 'Kunde inte återställa säkerhetskopia. Kontrollera filformatet.',
+ reloadNow: 'Ladda om nu',
+ creatingBackup: 'Skapar säkerhetskopia',
+ restoringBackup: 'Återställer säkerhetskopia',
+ preparing: 'Förbereder...',
+ processing: 'Bearbetar...',
+ doNotClosePage: 'Stäng inte denna sida eller navigera bort. Denna operation kan ta flera minuter för stora säkerhetskopior.',
+
+ // RestoreModal
+ restoring: 'Återställer...',
+ restoreComplete: 'Återställning slutförd',
+ restoreFailed2: 'Kunde inte återställa',
+ importSettings: 'Importera inställningar från en säkerhetskopia',
+ pleaseWaitRestoring: 'Vänligen vänta medan din data återställs',
+ selectBackupFile: 'Klicka för att välja säkerhetskopia (.json eller .zip)',
+ duplicateHandling: 'Hur dubbletter hanteras:',
+ matchPrinters: 'Skrivare',
+ matchPrintersBy: 'matchade efter serienummer',
+ matchSmartPlugs: 'Kontakter',
+ matchSmartPlugsBy: 'matchade efter IP-adress',
+ matchNotificationProviders: 'Notisleverantörer',
+ matchNotificationProvidersBy: 'matchade efter namn',
+ matchFilaments: 'Filament',
+ matchFilamentsBy: 'matchade efter namn + typ + märke',
+ matchArchives: 'Arkiv',
+ matchArchivesBy: 'matchade efter innehållshash (hoppas alltid över)',
+ matchPendingUploads: 'Väntande uppladdningar',
+ matchPendingUploadsBy: 'matchade efter filnamn',
+ matchSettingsTemplates: 'Inställningar & mallar',
+ matchSettingsTemplatesBy: 'skrivs alltid över',
+ replaceExisting: 'Ersätt befintlig data',
+ keepExisting: 'Behåll befintlig data',
+ overwriteDescription: 'Skriv över objekt som redan finns med säkerhetskopiedata',
+ keepDescription: 'Endast återställ objekt som inte redan finns',
+ overwriteCaution: 'Försiktighet:',
+ overwriteWarning: 'Att skriva över kommer att ersätta dina nuvarande konfigurationer med data från säkerhetskopian. Skrivaråtkomstkoder skrivs aldrig över av säkerhetskäl.',
+ cancel: 'Avbryt',
+ processingBackup: 'Bearbetar säkerhetskopia...',
+ itemsRestored: 'Objekt återställda',
+ itemsSkipped: 'Objekt hoppades över',
+ restored: 'Återställd',
+ skippedAlreadyExist: 'Hoppade över (finns redan)',
+ filesCategory: 'Filer (3MF, minibilder, etc.)',
+ andMore: '...och {{count}} till',
+ newApiKeysGenerated: 'Nya API-nycklar genererade',
+ keysShownOnce: 'Dessa nycklar visas endast en gång. Kopiera dem nu!',
+ copy: 'Kopiera',
+ noDataFound: 'Ingen data att återställa hittades i säkerhetskopian.',
+ close: 'Stäng',
+
+ // Scheduled local backups (#884)
+ scheduledBackup: 'Schemalagda säkerhetskopior',
+ scheduledBackupDescription: 'Skapa säkerhetskopieögonblicksbilder automatiskt enligt ett schema. Utdata-katalogen kan monteras till en NAS eller extern lagring.',
+ frequency: 'Frekvens',
+ backupTime: 'Tid',
+ retention: 'Bevarande',
+ retentionDescription: 'Antal säkerhetskopior att behålla',
+ outputPath: 'Utdata-sökväg',
+ outputPathPlaceholder: 'Standard: {{path}}',
+ outputPathDescription: 'Lämna tom för standardplats',
+ runNow: 'Kör nu',
+ backupFiles: 'säkerhetskopior',
+ noScheduledBackups: 'Inga säkerhetskopior än',
+ deleteBackup: 'Ta bort',
+ deleteBackupConfirm: 'Ta bort denna säkerhetskopia?',
+ backupRunning: 'Säkerhetskopiering pågår...',
+ scheduledBackupComplete: 'Säkerhetskopia slutförd',
+ scheduledBackupFailed: 'Kunde inte skapa säkerhetskopia',
+ nextBackup: 'Nästa säkerhetskopia',
+ backupSize: 'Storlek',
+ localTimeHint: 'Lokal tid ({{tz}})',
+ defaultPathLabel: 'Standard:',
+ // Backup output-path probe (#2544)
+ pathCheck: {
+ title: 'Bambuddy kan inte skriva till denna katalog',
+ howToFix: 'Så här åtgärdar du:',
+ sandboxed: 'Bambuddy-tjänsten kan inte skriva till {{path}}. Dess systemd-enhet körs med ProtectSystem=strict, vilket gör varje katalog utanför installations-, data- och loggkatalogerna skrivskyddade för tjänsten — även om du själv kan skriva till den från ditt eget skal.',
+ read_only: '{{path}} är på ett skrivskyddat filsystem.',
+ permission_denied: 'Bambuddy har inte behörighet att skriva till {{path}}. Kontrollera katalogägaren och behörigheterna.',
+ no_space: 'Filsystemet som innehåller {{path}} är fullt.',
+ not_a_directory: '{{path}} finns men är inte en katalog.',
+ missing: '{{path}} finns inte och kunde inte skapas.',
+ error: 'Bambuddy kan inte skriva till {{path}}.',
+ ephemeralTitle: 'Dessa säkerhetskopior kommer inte att överleva en container-omstart',
+ container_ephemeral: '{{path}} är inne i Bambuddy-containern, inte på värden. Säkerhetskopior som skrivs dit försvinner när containern skapas om. Montera katalogen från värden:',
+ },
+
+ // Category labels
+ categories: {
+ settings: 'Inställningar',
+ notification_providers: 'Notisleverantörer',
+ notification_templates: 'Notismallar',
+ smart_plugs: 'Kontakter',
+ printers: 'Skrivare',
+ filaments: 'Filament',
+ maintenance_types: 'Underhållstyper',
+ archives: 'Arkiv',
+ projects: 'Projekt',
+ pending_uploads: 'Väntande uppladdningar',
+ external_links: 'Externa länkar',
+ api_keys: 'API-nycklar',
+ },
+ },
+
+ // Tags
+ tags: {
+ title: 'Taggar',
+ addTag: 'Lägg till tagg',
+ editTag: 'Redigera tagg',
+ deleteTag: 'Ta bort tagg',
+ tagName: 'Taggnamn',
+ tagColor: 'Taggfärg',
+ noTags: 'Inga taggar',
+ deleteConfirm: 'Är du säker på att du vill ta bort denna tagg?',
+ manageTags: 'Hantera taggar',
+ },
+
+ // Upload modal (archives)
+ uploadModal: {
+ title: 'Ladda upp 3MF-filer',
+ dragDrop: 'Dra och släpp .3mf-filer här',
+ or: 'eller',
+ browseFiles: 'Bläddra bland filer',
+ extractionInfo: 'Skrivarmodellen kommer automatiskt att extraheras från 3MF-filens metadata.',
+ uploaded: 'uppladdade',
+ failed: 'misslyckades',
+ uploading: 'Laddar upp...',
+ upload: 'Ladda upp',
+ uploadFailed: 'Kunde inte ladda upp',
+ },
+
+ // Edit archive modal
+ // Edit Archive Modal
+ editArchive: {
+ title: 'Redigera arkiv',
+ name: 'Namn',
+ namePlaceholder: 'Utskriftsnamn',
+ printer: 'Skrivare',
+ noPrinter: 'Ingen skrivare',
+ project: 'Projekt',
+ noProject: 'Ingen projekt',
+ itemsPrinted: 'Antal utskrivna objekt',
+ itemsPrintedHelp: 'Antal objekt som producerats i detta utskriftsjobb',
+ filamentUsed: 'Filament använt (g)',
+ filamentUsedPlaceholder: 't.ex. 46,16',
+ filamentUsedHelp: 'Ställ in detta manuellt när en utskrift arkiveras utan sin 3MF, så att den fortfarande räknas mot filamenttotalen. Om man skannar om ett arkiv som har sin 3MF läser man tillbaka siffran från filen.',
+ notes: 'Anteckningar',
+ notesPlaceholder: 'Lägg till anteckningar om denna utskrift...',
+ externalLink: 'Extern länk',
+ externalLinkPlaceholder: 'https://printables.com/model/...',
+ externalLinkHelp: 'Länk till Printables, Thingiverse eller annan källa',
+ tags: 'Taggar',
+ tagsPlaceholder: 'Lägg till taggar...',
+ addMoreTags: 'Lägg till fler taggar...',
+ matchingTags: 'Matchande "{{query}}"',
+ existingTags: 'Befintliga taggar',
+ clickToAdd: '(klicka för att lägga till)',
+ status: 'Status',
+ failureReason: 'Orsak till fel',
+ selectReason: 'Välj orsak...',
+ photos: 'Bilder av utskriftsresultat',
+ photosHelp: 'Klicka på + för att lägga till bilder av ditt utskriftsresultat',
+ printResult: 'Utskriftsresultat',
+ saving: 'Sparar...',
+ // Failure reasons
+ failureReasons: {
+ adhesionFailure: 'Vidhäftningsfel',
+ spaghettiDetached: 'Spaghetti / lossning',
+ layerShift: 'Skiktförskjutning',
+ cloggedNozzle: 'Igensatt munstycke',
+ filamentRunout: 'Filament tog slut',
+ warping: 'Förskevning',
+ stringing: 'Trådighet',
+ underExtrusion: 'Under-extrudering',
+ powerFailure: 'Strömavbrott',
+ userCancelled: 'Användare avbröt',
+ noStatusUpdate: 'Ingen statusuppdatering mottogs',
+ other: 'Annat',
+ },
+ // Archive statuses
+ statuses: {
+ completed: 'Slutförd',
+ failed: 'Misslyckad',
+ aborted: 'Avbruten',
+ printing: 'Skriver ut',
+ },
+ },
+
+ // K-Profiles
+ kProfiles: {
+ title: 'K-profiler',
+ noPrintersConfigured: 'Inga skrivare konfigurerade',
+ addPrinterInSettings: 'Lägg till en skrivare i Inställningar för att hantera K-profiler',
+ noActivePrinters: 'Inga aktiva skrivare',
+ enablePrinterConnection: 'Aktivera skrivaranslutning för att visa dess K-profiler',
+ loadingProfiles: 'Laddar K-profiler...',
+ printerOffline: 'Skrivare offline',
+ printerOfflineDesc: 'Den valda skrivaren är inte ansluten. Sätt på den för att visa K-profiler.',
+ noMatchingProfiles: 'Inga matchande profiler',
+ noMatchingProfilesDesc: 'Inga profiler matchar dina sökvillkor',
+ noKProfiles: 'Inga K-profiler',
+ noKProfilesDesc: 'Inga tryckförskottsprofiler hittade för {{diameter}} mm munstycke',
+ createFirstProfile: 'Skapa en första profil',
+ // Controls
+ printer: 'Skrivare',
+ nozzle: 'Munstycke',
+ refresh: 'Uppdatera',
+ addProfile: 'Lägg till profil',
+ export: 'Exportera',
+ import: 'Importera',
+ select: 'Välj',
+ selectAll: 'Markera alla',
+ delete: 'Ta bort',
+ // Filters
+ searchPlaceholder: 'Sök efter namn eller filament...',
+ allExtruders: 'Alla extrudrar',
+ leftOnly: 'Endast vänster',
+ rightOnly: 'Endast höger',
+ allFlow: 'Allt flöde',
+ hfOnly: 'Endast HF',
+ sOnly: 'Endast S',
+ sortName: 'Sortera: Namn',
+ sortKValue: 'Sortera: K-värde',
+ sortFilament: 'Sortera: Filament',
+ // Dual extruder labels
+ leftExtruder: 'Vänster extruder',
+ rightExtruder: 'Höger extruder',
+ // Modal
+ modal: {
+ addTitle: 'Lägg till K-profil',
+ editTitle: 'Redigera K-profil',
+ profileName: 'Profilnamn',
+ profileNamePlaceholder: 'Min PLA-profil',
+ kValue: 'K-värde',
+ kValuePlaceholder: '0,020',
+ kValueHelp: 'Typiskt intervall: 0,01 - 0,06 för PLA, 0,02 - 0,10 för PETG',
+ filament: 'Filament',
+ selectFilament: 'Välj filament...',
+ source: {
+ local: 'Importerad',
+ orcaCloud: 'Orca Cloud',
+ bambuCloud: 'Bambu Cloud',
+ builtin: 'Inbyggd',
+ },
+ noFilamentsHelp: 'Inga filament tillgängliga. Logga in på Bambu Cloud, eller importera förinställningar under Profiler → Lokala profiler.',
+ searchFilaments: 'Sök filament...',
+ noFilamentMatches: 'Inga filament matchar den sökningen',
+ flowType: 'Flödestyp',
+ highFlow: 'Högt flöde',
+ standard: 'Standard',
+ nozzleSize: 'Munstyckesstorlek',
+ extruder: 'Extruder',
+ extruders: 'Extrudrar',
+ left: 'Vänster',
+ right: 'Höger',
+ notes: 'Anteckningar (lagras lokalt)',
+ notesPlaceholder: 'Lägg till anteckningar om denna profil...',
+ notesHelp: 'Anteckningar sparas i Bambuddy, inte på skrivaren',
+ syncing: 'Synkroniserar med skrivare...',
+ savingExtruder: 'Sparar till extruder {{current}}/{{total}}...',
+ pleaseWait: 'Vänligen vänta',
+ },
+ // Delete confirmation
+ deleteConfirm: {
+ title: 'Ta bort profil',
+ cannotUndo: 'Denna åtgärd kan inte ångras',
+ message: 'Är du säker på att du vill ta bort "{{name}}" från skrivaren?',
+ },
+ // Bulk delete
+ bulkDelete: {
+ title: 'Ta bort profiler',
+ cannotUndo: 'Denna åtgärd kan inte ångras',
+ message: 'Är du säker på att du vill ta bort {{count}} valda profiler från skrivaren?',
+ },
+ // Toast
+ toast: {
+ profileSaved: 'K-profil sparad',
+ profilesSaved: 'K-profil sparad till {{count}} extrudrar',
+ selectAtLeastOneExtruder: 'Välj minst en extruder',
+ selectFilament: 'Välj ett filament först',
+ filamentNotResolvable: 'Inget Bambu-filament-ID för {{name}} — skrivaren kan inte lagra en profil för den',
+ profileDeleted: 'K-profil borttagen',
+ profilesDeleted: 'Tog bort {{count}} profiler',
+ exportedProfiles: 'Exporterade {{count}} profiler',
+ importedProfiles: 'Importerade {{count}} av {{total}} profiler',
+ noProfilesToExport: 'Inga profiler att exportera',
+ invalidFileFormat: 'Ogiltigt filformat',
+ failedToParseImport: 'Kunde inte tolka importfil',
+ failedToSaveBatch: 'Kunde inte spara K-profiler',
+ noteSaved: 'Anteckning sparad',
+ failedToSaveNote: 'Kunde inte spara anteckning',
+ },
+ // Permissions
+ permission: {
+ noRead: 'Du har inte behörighet att uppdatera profiler',
+ noCreate: 'Du har inte behörighet att lägga till profiler',
+ noUpdate: 'Du har inte behörighet att uppdatera K-profiler',
+ noDelete: 'Du har inte behörighet att ta bort K-profiler',
+ noExport: 'Du har inte behörighet att exportera profiler',
+ noImport: 'Du har inte behörighet att importera profiler',
+ },
+ },
+
+ // Virtual Printer
+ virtualPrinter: {
+ title: 'Virtuell skrivare',
+ running: 'Körs',
+ stopped: 'Stoppad',
+ description: {
+ default: 'Aktivera en virtuell skrivare som visas i Bambu Studio och OrcaSlicer. Filer som skickas till denna skrivare kommer att arkiveras direkt utan utskrift.',
+ proxy: 'Aktivera en proxy som vidarebefordrar slicer-trafik till en riktig skrivare, vilket möjliggör fjärrutskrift över vilket nätverk som helst.',
+ },
+ enable: {
+ title: 'Aktivera virtuell skrivare',
+ visibleInSlicer: 'Synlig som "Bambuddy" i slicer-uppäckt',
+ proxyingTo: 'Vidarebefordrar till {{name}}',
+ notActive: 'Inte aktiv',
+ },
+ model: {
+ title: 'Skrivarmodell',
+ description: 'Välj vilken skrivarmodell som ska emuleras.',
+ restartWarning: 'Att ändra modellen kommer att starta om den virtuella skrivaren',
+ },
+ accessCode: {
+ title: 'Åtkomstkod',
+ isSet: 'Åtkomstkod är inställd',
+ notSet: 'Ingen åtkomstkod inställd — krävs för att aktivera',
+ placeholder: 'Ange 8-teckens kod',
+ placeholderChange: 'Ange ny kod för att ändra',
+ hint: 'Måste vara exakt 8 tecken. Används av slicer för autentisering.',
+ charCount: '({{count}}/8)',
+ inheritedFromTarget: 'Ärvd från mål',
+ derivedFromTargetHint: 'Använder målskrivarens åtkomstkod. Bryggan vidarebefordrar slicer-autentisering till den riktiga skrivaren, så koderna måste matcha — redigera målskrivarens åtkomstkod för att ändra detta värde.',
+ reveal: 'Visa åtkomstkod',
+ hide: 'Dölj åtkomstkod',
+ },
+ targetPrinter: {
+ title: 'Målskrivare',
+ configured: 'Proxy-mål konfigurerat',
+ notConfigured: 'Ingen målskrivare vald — krävs för proxyläge',
+ placeholder: 'Välj en skrivare...',
+ hint: 'Välj den skrivare som slicer-trafiken ska vidarebefordras till. Skrivaren måste vara i LAN-läge.',
+ noPrinters: 'Inga skrivare konfigurerade. Lägg till en skrivare först för att använda proxyläge.',
+ },
+ remoteInterface: {
+ title: 'Åsidosättning för nätverksgränssnitt',
+ configured: 'Åsidosättning för nätverksgränssnitt aktiv',
+ optional: 'Valfritt — använd om auto-upptäckt IP är fel (t.ex. flera nätverkskort, Docker, VPN)',
+ placeholder: 'Auto-upptäckt (standard)...',
+ hint: 'Åsidosätter IP-adressen som annonseras via SSDP och används i TLS-certifikatet. Användbart när Bambuddy har flera nätverksgränssnitt.',
+ },
+ mode: {
+ title: 'Läge',
+ archive: 'Arkiv',
+ archiveDesc: 'Arkivera filer omedelbart',
+ review: 'Granskning',
+ reviewDesc: 'Granska innan arkivering',
+ queue: 'Kö',
+ queueDesc: 'Arkivera och lägg till i kö',
+ proxy: 'Proxy',
+ proxyDesc: 'Vidarebefordra till riktig skrivare',
+ },
+ autoDispatch: {
+ title: 'Auto-utskick',
+ description: 'Starta utskrifter automatiskt när de läggs till i kön. När avstängd väntar utskrifter på manuell utskickning.',
+ },
+ queueForceColorMatch: {
+ title: 'Tvinga färgmatchning',
+ description: 'Vägra att skicka till en skrivare som inte har exakt filamenttyp och färg laddad. Avstängd som standard — utan detta använder kön endast modellmatchning och kan välja en skrivare med fel färg laddad.',
+ },
+ saveAmsMapping: {
+ title: 'Spara AMS-mappning',
+ description: 'Bevara slicerns egna AMS-facksval (från project_file MQTT-kommando) i arkivet, så att en senare omutskrift återanvänder den exakta fysiska rullen istället för att härleda en ny från filens typ/färg. Avstängd som standard.',
+ },
+ gcodeInjection: {
+ title: 'G-kod-injektering',
+ description: 'Tillämpa de modellspecifika G-kodsnuttar som konfigurerats i Inställningar på jobb från denna VP. Avstängd som standard.',
+ },
+ tailscaleDisabled: {
+ title: 'Tailscale-integration',
+ description: 'Aktivera för att markera denna VP som exponerad över Tailscale. Visar värdens Tailscale-adress så att du vet vilken IP du ska klistra in i slicern. CA-import-steget är oförändrat — denna växling har ingen effekt på certifikat.',
+ },
+ setupRequired: {
+ title: 'Inställning krävs',
+ description: 'Funktionen för virtuell skrivare kräver ytterligare systemkonfiguration innan den fungerar. Detta inkluderar portvidarebefordran, brandväggsregler och plattformsspecifika inställningar.',
+ readGuide: 'Läs inställningsguiden innan du aktiverar',
+ },
+ archiveNameSource: {
+ title: 'Källa för arkivnamn',
+ description: 'Välj hur nya arkiv ska namnges när filer anländer via den virtuella skrivaren. "Metadata" använder den inbäddade titeln från slicern från 3MF (standard). "Filnamn" använder filnamnet som Bambu Studio skickade via FTP. Obs: Bambu Studio skriver över det namn du skriver i dialogrutan "Skicka till skrivare" med 3MF:ens Titel-fält när sådant finns, så båda lägen producerar ofta samma sträng.',
+ metadata: 'Metadata',
+ filename: 'Filnamn',
+ },
+ caCert: {
+ title: 'Slicer-certifikat',
+ description: 'Virtuella skrivare använder ett TLS-certifikat signerat av Bambuddy-CA. Importera detta CA-certifikat till din slicers betroda lagring en gång så att den accepterar anslutningen — ingen anledning att kopiera det från kommandoraden.',
+ copy: 'Kopiera',
+ copied: 'Kopierad',
+ download: 'Ladda ner',
+ fingerprint: 'SHA-256',
+ },
+ howItWorks: {
+ title: 'Hur det fungerar',
+ step1: 'På samma subnät visas virtuella skrivare automatiskt i din slicer (Bambu Studio / OrcaSlicer) via upptäckt. Från andra nätverk lägger du till dem manuellt via IP-adress och åtkomstkod.',
+ step2: 'I Arkiv-, Gransknings- och Kölägen, använd knappen "Skicka" i din slicer för att ladda upp 3MF-filer till Bambuddy. Slicern kommer att visa "Utskrift lyckades" — filen lagras, inte skrivs ut.',
+ step3: 'I Proxyläge vidarebefordrar den virtuella skrivaren all trafik till en riktig skrivare — utskrifter startar omedelbart som om den vore ansluten direkt.',
+ },
+ status: {
+ title: 'Statusdetaljer',
+ printerName: 'Skrivarnamn',
+ model: 'Modell',
+ serialNumber: 'Serienummer',
+ mode: 'Läge',
+ pendingFiles: 'Väntande filer',
+ targetPrinter: 'Målskrivare',
+ ftpPort: 'FTP-port',
+ mqttPort: 'MQTT-port',
+ ftpConnections: 'FTP-anslutningar',
+ mqttConnections: 'MQTT-anslutningar',
+ },
+ toast: {
+ updated: 'Inställningar för virtuell skrivare uppdaterade',
+ failedToUpdate: 'Kunde inte uppdatera inställningar',
+ copyFailed: 'Kunde inte kopiera — försök markera texten manuellt',
+ accessCodeRequired: 'Ange en åtkomstkod först',
+ targetPrinterRequired: 'Välj en målskrivare först',
+ bindIpRequired: 'Ange en bind-IP först',
+ accessCodeEmpty: 'Åtkomstkod kan inte vara tom',
+ accessCodeLength: 'Åtkomstkod måste vara exakt 8 tecken',
+ targetCodeChangedRebind: 'Åtkomstkoden matchar nu den nya målskrivaren. Lägg till denna enhet igen i din slicer för att hämta den nya koden.',
+ created: 'Virtuell skrivare skapad',
+ failedToCreate: 'Kunde inte skapa virtuell skrivare',
+ deleted: 'Virtuell skrivare borttagen',
+ failedToDelete: 'Kunde inte ta bort virtuell skrivare',
+ },
+ list: {
+ title: 'Virtuella skrivare',
+ add: 'Lägg till',
+ addFirst: 'Lägg till virtuell skrivare',
+ empty: 'Inga virtuella skrivare konfigurerade. Lägg till en för att komma igång.',
+ },
+ bindIp: {
+ title: 'Bind gränssnitt',
+ placeholder: 'Välj gränssnitt...',
+ hint: 'Nätverksgränssnitt för denna virtuella skrivare att binda till. Måste vara unikt per skrivare.',
+ },
+ proxy: {
+ accessCodeHint: 'I proxyläge, använd din målskrivares åtkomstkod i slicern. Anslutningen vidarebefordras transparant till den riktiga skrivaren.',
+ },
+ addDialog: {
+ title: 'Lägg till virtuell skrivare',
+ name: 'Namn',
+ hint: 'Du kan konfigurera åtkomstkod, målskrivare och andra inställningar efter skapande.',
+ create: 'Skapa',
+ },
+ deleteConfirm: {
+ title: 'Ta bort virtuell skrivare',
+ message: 'Är du säker på att du vill ta bort "{{name}}"? Detta kommer att stoppa alla tjänster för denna skrivare.',
+ },
+ },
+
+ // Model Viewer
+ modelViewer: {
+ openInSlicer: 'Öppna i slicer',
+ openInSlicerWith: 'Öppna i {{slicer}}',
+ moreSlicerOptions: 'Fler slicer-alternativ',
+ openInSlicerFailed: 'Kunde inte öppna i slicer',
+ tabs: {
+ model: '3D-modell',
+ },
+ notAvailable: 'ej tillgänglig',
+ plates: 'Plattor',
+ allPlates: 'Alla plattor',
+ plateNumber: 'Platta {{number}}',
+ plateCount: '{{count}} platta',
+ plateCount_other: '{{count}} plattor',
+ objectCount: '{{count}} objekt',
+ objectCount_other: '{{count}} objekt',
+ filamentCount: '{{count}} filament',
+ filamentCount_other: '{{count}} filament',
+ eta: 'Beräknad tid: {{minutes}} min',
+ noPreview: 'Ingen förhandsvisning tillgänglig för denna fil',
+ pagination: {
+ pageOf: 'Sida {{current}} av {{total}}',
+ prev: 'Föregående',
+ next: 'Nästa',
+ },
+ errors: {
+ failedToLoad: 'Kunde inte ladda fil',
+ noMeshes: 'Inga nät hittade i 3MF-fil',
+ unsupportedFormat: 'Filformatet stöds inte',
+ },
+ },
+
+ // Maintenance type descriptions (built-in)
+ maintenanceDescriptions: {
+ lubricateCarbonRods: 'Applicera smörjmedel på kolstänger för jämn rörelse',
+ lubricateRails: 'Applicera smörjmedel på linjärskenor för jämn rörelse',
+ cleanNozzle: 'Rengör varmblock och munstycke för att förhindra igensättning',
+ checkBelts: 'Kontrollera remspänning för noggranna utskrifter',
+ cleanBuildPlate: 'Rengör byggplatta för bättre vidhäftning',
+ checkExtruder: 'Kontrollera extruderkugghjul för slitage',
+ checkCooling: 'Se till att kylfläktar fungerar korrekt',
+ generalInspection: 'Allmän skrivarkontroll',
+ cleanCarbonRods: 'Rengör kolstänger för att minska friktion',
+ lubricateSteelRods: 'Applicera smörjmedel på stålstänger för jämn rörelse',
+ cleanSteelRods: 'Rengör stålstänger för att minska friktion',
+ cleanLinearRails: 'Torka av linjärskenor för att ta bort damm och smuts',
+ checkPtfeTube: 'Kontrollera PTFE-rör för slitage eller skador',
+ replaceHepaFilter: 'Byt HEPA-filter för bättre luftkvalitet',
+ replaceCarbonFilter: 'Byt aktivt kolfilter',
+ lubricateLeftNozzleRail: 'Smörj vänster munstyckesskena (H2-serien)',
+ },
+
+ // Smart Plugs
+ haSensors: {
+ label: 'Sensorer',
+ unavailable: 'Ej tillgänglig',
+ blocksPrints: '{{entity}} — håller på utskrifter medan larmet är aktivt',
+ states: {
+ open: 'Öppen',
+ closed: 'Stängd',
+ unlocked: 'Olåst',
+ locked: 'Låst',
+ detected: 'Upptäckt',
+ clear: 'Tom',
+ wet: 'Blöt',
+ dry: 'Torr',
+ problem: 'Problem',
+ ok: 'OK',
+ running: 'Körs',
+ stopped: 'Stoppad',
+ on: 'På',
+ off: 'Av',
+ },
+ sectionTitle: 'Home Assistant-sensorer',
+ add: 'Lägg till sensor',
+ addTitle: 'Lägg till Home Assistant-sensor',
+ editTitle: 'Redigera Home Assistant-sensor',
+ empty: 'Inga sensorer än. Binda en dörrkontakt eller termometer från Home Assistant för att visa den på ett skrivarkort.',
+ unknownPrinter: 'Okänd skrivare',
+ badgeBlocks: 'Håller på utskrifter',
+ badgeNotifies: 'Notiserar',
+ badgeHidden: 'Dold på kort',
+ printer: 'Skrivare',
+ entity: 'Entitet',
+ searchPlaceholder: 'Sök entiteter...',
+ noEntities: 'Inga matchande entiteter',
+ name: 'Visningsnamn',
+ alertWhen: 'Larma när',
+ alertNever: 'Aldrig — visa endast',
+ alertAbove: 'Ovanför',
+ alertBelow: 'Under',
+ alertHint: 'Larmtillståndet markerar sensorn på skrivarkortet och aktiverar alternativen nedan.',
+ showOnCard: 'Visa på skrivarkort',
+ notifyOnAlert: 'Skicka en notis när larmet startar',
+ blockPrint: 'Håll köade utskrifter medan larmet är aktivt',
+ blockPrintHint: 'Jobb förblir köade och startar automatiskt när sensorn återgår till normalt. Ignoreras medan Home Assistant är otillgänglig.',
+ toast: {
+ created: 'Sensor tillagd',
+ updated: 'Sensor sparad',
+ deleted: 'Sensor borttagen',
+ },
+ error: {
+ pickEntity: 'Välj en Home Assistant-entitet',
+ nameRequired: 'Ange ett visningsnamn',
+ printerRequired: 'Välj en skrivare',
+ alertRequired: 'Sätt ett larmvillkor först',
+ },
+ },
+ locationHaSensors: {
+ sectionTitle: 'Home Assistant-sensorer (lagringsplatser)',
+ sectionDescription:
+ 'Bind Home Assistant-temperatur-, luftfuktighets- eller batterisensorer till dina lagringsplatser för att visa liveavläsningar på filamentkortet och i tabellen. Endast en sensor per kategori är tillåten per plats.',
+ add: 'Lägg till sensor',
+ addTitle: 'Lägg till Home Assistant-sensor',
+ editTitle: 'Redigera Home Assistant-sensor',
+ empty: 'Inga sensorer än. Bind en temperatur- eller luftfuktighetssensor från Home Assistant för att visa den på filamentkortet.',
+ unknownLocation: 'Okänd plats',
+ location: 'Lagringsplats',
+ showOnCard: 'Visa på filamentkort',
+ overwriteConfirm: {
+ title: 'Ersätt befintlig sensor?',
+ messageTemperature: '"{{location}}" har redan en temperatursensor kopplad: {{name}}. Om du fortsätter kommer den att ersättas.',
+ messageHumidity: '"{{location}}" har redan en luftfuktighetssensor kopplad: {{name}}. Om du fortsätter kommer den att ersättas.',
+ messageBattery: '"{{location}}" har redan en batterisensor kopplad: {{name}}. Om du fortsätter kommer den att ersättas.',
+ },
+ autoAdd: {
+ confirmTitle: 'Lägg till de andra sensorerna också?',
+ confirmMessage: 'Bambuddy hittade också andra sensorer för denna plats. Välj vilka du också vill lägga till.',
+ added: 'Lade också till: {{names}}',
+ noneFound: 'Inga matchande temperatur-, luftfuktighets- eller batterisensorer hittade för denna plats.',
+ onlyThisOne: 'Endast denna',
+ },
+ options: {
+ buttonLabel: 'Sensoralternativ',
+ title: 'Platssensoralternativ',
+ description: 'Dessa värden används när en temperatur-, luftfuktighets- eller batterisensor binds automatiskt tillsammans med den första sensorn för en plats.',
+ generalSettings: 'Allmänna inställningar',
+ pollInterval: 'Uppdateringsintervall (sekunder)',
+ pollIntervalHint: 'Hur ofta Bambuddy avfrågar Home Assistant och uppdaterar sensorvärden på skärmen. Minimum 60 sekunder.',
+ colorizeValues: 'Färga sensorvärden enligt deras larmtröskelvärden',
+ aboveColor: 'Färg för överstiget tröskelvärde',
+ belowColor: 'Färg för understiget tröskelvärde',
+ optimalColor: 'Färg för optimalt värde',
+ colors: {
+ red: 'Röd',
+ orange: 'Orange',
+ yellow: 'Gul',
+ green: 'Grön',
+ blue: 'Blå',
+ purple: 'Lila',
+ pink: 'Rosa',
+ },
+ saved: 'Alternativ sparade',
+ saveFailed: 'Kunde inte spara alternativ',
+ reset: 'Återställ',
+ resetConfirm: {
+ title: 'Återställ alla befintliga platssensorer?',
+ message:
+ 'Detta skriver över larmtröskelvärden, notiser och kortsynlighet för alla befintliga lagringsplats-temperatur-, luftfuktighets- och batterisensorer med de värden som konfigurerats ovan, och återställer varje sensors namn till dess Home Assistant-vänliga namn — skrivarsensorer påverkas inte. Detta kan inte ångras. Beroende på hur många sensorer du har, kan detta ta en stund.',
+ },
+ resetDone: 'Tillämpat på {{count}} plats-sensorer',
+ resetFailed: 'Kunde inte återställa plats-sensorer',
+ },
+ deleteAllConfirm: {
+ title: 'Ta bort alla sensorer?',
+ message: 'Detta tar bort alla {{count}} sensorer bundna till "{{location}}". Detta kan inte ångras.',
+ },
+ currentlyBound: 'För närvarande kopplad: {{entity}}',
+ error: {
+ locationRequired: 'Välj en lagringsplats',
+ },
+ toast: {
+ created: 'Sensor tillagd',
+ updated: 'Sensor sparad',
+ deleted: 'Sensor borttagen',
+ },
+ },
+ smartPlugs: {
+ offline: 'Offline',
+ admin: 'Admin',
+ openPlugAdminPage: 'Öppna kontakt-adminsida',
+ deleteSmartPlug: 'Ta bort kontakt',
+ turnOnSmartPlug: 'Sätt på kontakt',
+ turnOffSmartPlug: 'Stäng av kontakt',
+ turnOn: 'Sätt på',
+ turnOff: 'Stäng av',
+ addSmartPlug: {
+ scanningNetwork: 'Söker i subnät...',
+ chooseEntity: 'Välj en entitet...',
+ connectionFailed: 'Kunde inte ansluta',
+ searchEntities: 'Sök entiteter...',
+ searchPowerSensors: 'Sök effektsensorer...',
+ searchEnergySensors: 'Sök energisensorer...',
+ placeholders: {
+ plugName: 'Kontakt i vardagsrummet',
+ mqttStateOnValue: 'PÅ, true, 1',
+ mqttSameAsPower: 'Samma som effekttopik, eller annan',
+ },
+ },
+ // SmartPlugCard
+ linkedTo: 'Länkad till:',
+ monitorOnly: 'Endast övervakning',
+ alerts: 'Larm',
+ scheduleOn: 'På {{time}}',
+ scheduleOff: 'Av {{time}}',
+ on: 'På',
+ off: 'Av',
+ power: 'Effekt',
+ kwhToday: 'kWh idag',
+ settings: 'Inställningar',
+ automationSettings: 'Automatiseringsinställningar',
+ showInSwitchbar: 'Visa i kontaktpanel',
+ quickAccessSidebar: 'Snabbåtkomst från sidofält',
+ enabled: 'Aktiverad',
+ enableAutomation: 'Aktivera automatisering för denna kontakt',
+ autoOn: 'Auto på',
+ autoOnDescription: 'Sätt på när utskrift startar',
+ autoOff: 'Auto av',
+ autoOffDescription: 'Stäng av när utskrift slutförs (engångs)',
+ autoOffPersistent: 'Håll aktiverad',
+ autoOffPersistentDescription: 'Förblir aktiverad mellan utskrifter istället för engångs',
+ controlsPrinterPower: 'Kontrollerar skrivarens ström',
+ controlsPrinterPowerDescription: 'Stäng av om denna kontakt endast förser en tillbehörsenhet (filterfläkt, belysning). Annars markeras skrivaren som offline när den stängs av.',
+ autoOffAfterDrying: 'Auto av efter torkning',
+ autoOffAfterDryingDescription: 'Stäng av när AMS-torkning slutförs',
+ delayAfterDryingMinutes: 'Torkningsfördröjning (minuter)',
+ turnOffDelayMode: 'Avstängningsfördröjningsläge',
+ time: 'Tid',
+ temp: 'Temp',
+ delayMinutes: 'Fördröjning (minuter)',
+ tempThreshold: 'Temperaturtröskelvärde (°C)',
+ tempThresholdDescription: 'Stäng av när munstycket svalnar under detta tröskelvärde',
+ edit: 'Redigera',
+ deleteConfirm: 'Är du säker på att du vill ta bort "{{name}}"? Detta kan inte ångras.',
+ turnOnConfirm: 'Är du säker på att du vill sätta på "{{name}}"?',
+ turnOffConfirm: 'Är du säker på att du vill stänga av "{{name}}"? Detta kommer att bryta strömmen till den anslutna enheten.',
+ failedToTurn: 'Kunde inte {{action}} "{{name}}"',
+ unknown: 'Okänd',
+ // AddSmartPlugModal
+ addTitle: 'Lägg till kontakt',
+ editTitle: 'Redigera kontakt',
+ stopScanning: 'Stoppa sökning',
+ discoverTasmota: 'Upptäck Tasmota-enheter',
+ foundDevices: 'Hittade {{count}} enhet(er) — klicka för att välja:',
+ noDevicesFound: 'Inga Tasmota-enheter hittade i ditt subnät',
+ haNotConfigured: 'Home Assistant är inte konfigurerad. Ställ in det i',
+ haSettingsPath: 'Inställningar → Nätverk → Home Assistant',
+ selectEntity: 'Välj entitet *',
+ ipAddress: 'IP-adress *',
+ nameLabel: 'Namn *',
+ username: 'Användarnamn',
+ password: 'Lösenord',
+ authHint: 'Lämna tomt om din Tasmota-enhet inte kräver autentisering',
+ linkToPrinter: 'Länka till skrivare',
+ noPrinter: 'Ingen skrivare (endast manuell kontroll)',
+ linkingDescription: 'Länkning aktiverar automatisk på/av när utskrifter startar/slutförs',
+ powerAlerts: 'Effektlarm',
+ alertAbove: 'Larma om över (W)',
+ alertBelow: 'Larma om under (W)',
+ alertDescription: 'Få en notis när effektförbrukningen överskrider dessa tröskelvärden. Lämna tomt för att inaktivera det villkoret.',
+ dailySchedule: 'Dagligt schema',
+ turnOnAt: 'Sätt på klockan',
+ turnOffAt: 'Stäng av klockan',
+ scheduleDescription: 'Sätt automatiskt på/av kontakten vid dessa tider dagligen. Lämna tomt för att hoppa över den åtgärden.',
+ showOnPrinterCard: 'Visa på skrivarkort',
+ displayOnPrinterCard: 'Visa knapp på skrivarkort',
+ connectedResult: 'Ansluten!',
+ deviceLabel: 'Enhet: {{name}} - ',
+ stateLabel: 'Status: {{state}}',
+ test: 'Test',
+ delete: 'Ta bort',
+ save: 'Spara',
+ add: 'Lägg till',
+ cancel: 'Avbryt',
+ failedToStartScan: 'Kunde inte starta sökning',
+ nameRequired: 'Namn krävs',
+ entityRequired: 'Entitet krävs för Home Assistant-kontakter',
+ mqttTopicRequired: 'Minst ett MQTT-ämne måste konfigureras för effekt, energi eller statusövervakning',
+ loadingEntities: 'Laddar entiteter...',
+ loading: 'Laddar...',
+ failedToLoadEntities: 'Kunde inte ladda entiteter: {{error}}',
+ noEntitiesMatching: 'Inga entiteter hittade som matchar "{{search}}"',
+ noEntitiesAvailable: 'Inga entiteter tillgängliga',
+ searchingEntities: 'Söker i alla entiteter ({{count}} hittade)',
+ showingEntities: 'Visar switch, light, input_boolean ({{count}} tillgängliga)',
+ energyMonitoringOptional: 'Energiövervakning (valfritt)',
+ energyMonitoringHint: 'Sök och välj sensorer som tillhandahåller effekt-/energidata.',
+ powerSensorW: 'Effektsensor (W)',
+ energyTodayKwh: 'Energi idag (kWh)',
+ totalEnergyKwh: 'Total energi (kWh)',
+ noMatchingSensors: 'Inga matchande sensorer',
+ none: 'Ingen',
+ mqttNotConfigured: 'MQTT-mäklare inte konfigurerad. Ställ in mäklaradress i',
+ mqttSettingsPath: 'Inställningar → Nätverk → MQTT-publicering',
+ mqttNotConfiguredSuffix: '(du behöver inte aktivera publicering, fyll bara i mäklaruppgifterna).',
+ mqttMonitorOnlyDescription: 'MQTT-kontakter tar emot effekt-/energidata via MQTT-prenumeration. På/av-kontroll är inte tillgänglig — använd din MQTT-mäklare eller hemautomatiseringssystem.',
+ powerMonitoring: 'Effektövervakning',
+ energyMonitoring: 'Energiövervakning',
+ stateMonitoring: 'Statusövervakning',
+ optional: 'valfritt',
+ topic: 'Topik',
+ jsonPath: 'JSON-sökväg',
+ multiplier: 'Multiplikator',
+ onValue: 'PÅ-värde',
+ mqttPowerHint: 'JSON-sökväg extraherar värde från JSON-nyttolast (t.ex. "power_l1"). Lämna tomt om ämnet publicerar råa numeriska värden.\nAnvänd multiplikator 0,001 för mW→W, 1000 för kW→W.',
+ mqttEnergyHint: 'JSON-sökväg extraherar värde från JSON-nyttolast. Lämna tomt för råa värden.\nAnvänd multiplikator 0,001 för Wh→kWh, 1000 för MWh→kWh.',
+ mqttStateHint: 'JSON-sökväg extraherar värde från JSON-nyttolast. Lämna tomt för råa värden.\nPÅ-värde: den exakta strängen som betyder "PÅ". Lämna tomt för auto-upptäckt (PÅ, true, 1).',
+ // REST kontakt
+ restControl: 'Kontroll',
+ restOnUrl: 'Sätt PÅ-URL',
+ restOffUrl: 'Sätt AV-URL',
+ restOnBody: 'PÅ-förfrågningsinnehåll',
+ restOffBody: 'AV-förfrågningsinnehåll',
+ restMethod: 'HTTP-metod',
+ restHeaders: 'Anpassade huvuden (JSON)',
+ restStatusUrl: 'Status-URL',
+ restStatusPath: 'Status JSON-sökväg',
+ restStatusOnValue: 'PÅ-värde',
+ restPowerUrl: 'Effekt-URL',
+ restPowerPath: 'Effekt JSON-sökväg',
+ restPowerMultiplier: 'Effektmultiplikator',
+ restEnergyUrl: 'Energi-URL',
+ restEnergyPath: 'Energi JSON-sökväg (idag)',
+ restEnergyTotalPath: 'Energi JSON-sökväg (livstid)',
+ restEnergyTotalMultiplier: 'Livstidsmultiplikator',
+ restEnergyTotalPathHint: 't.ex. aenergy.total',
+ restEnergyTotalHint:
+ 'Många kontakter — varje Shelly bland dem — rapporterar endast en livstidsräknare som aldrig nollställs. Den hör hit, inte i fältet ovan: läs som dagens användning skulle den aldrig nollställas vid midnatt, och Igår och Totalt skulle förbli tomma. Bambuddy beräknar Idag och Igår utifrån den, vilket tar en eller två dagars avläsningar att fylla i. En Shelly rapporterar wattimmar, så använd en multiplikator på 0,001.',
+ restEnergyMultiplier: 'Energimultiplikator',
+ restUrlRequired: 'Minst en URL (PÅ eller AV) krävs för REST-kontakter',
+ restHeadersHint: 't.ex. {"Authorization": "Bearer din-token"}',
+ restBodyHint: 't.ex. PÅ, {"state": "on"}',
+ restStatusHint: 'URL för att hämta aktuell status',
+ restPathHint: 't.ex. state eller data.power.status',
+ restPowerUrlHint: 'Separat URL för effektdata (använder Status-URL om tom)',
+ restEnergyUrlHint: 'Separat URL för energidata (använder Status-URL om tom)',
+ restEnergyHint: 'Varje värde kan använda sin egen URL eller falla tillbaka till Status-URL. Använd multiplikatorer för enhetsomvandling (t.ex. 0,001 för att konvertera Wh till kWh).',
+ testConnection: 'Testa anslutning',
+ connectionSuccess: 'Anslutning lyckades',
+ noSwitchesInSwitchbar: 'Inga växlar i kontaktpanelen',
+ enableSwitchbarHint: 'Aktivera "Visa i kontaktpanel" i Inställningar > Kontakter',
+ },
+
+ // Notifications
+ notifications: {
+ // Provider types
+ providerTypes: {
+ callmebot: 'CallMeBot/WhatsApp',
+ ntfy: 'ntfy',
+ pushover: 'Pushover',
+ telegram: 'Telegram',
+ email: 'E-post',
+ discord: 'Discord',
+ webhook: 'Webhook',
+ homeassistant: 'Home Assistant',
+ bark: 'Bark',
+ },
+ // Provider descriptions
+ providerDescriptions: {
+ email: 'SMTP e-postnotiser',
+ telegram: 'Notiser via Telegram-bot',
+ discord: 'Skicka till Discord-kanal via webhook',
+ ntfy: 'Gratis, självhostade pushnotiser',
+ pushover: 'Enkla, pålitliga pushnotiser',
+ callmebot: 'Gratis WhatsApp-notiser via CallMeBot',
+ webhook: 'Generisk HTTP POST till valfri URL',
+ homeassistant: 'Permanenta notiser i Home Assistant-instrumentpanel',
+ bark: 'iOS push-notiser via Bark (självhostbar)',
+ },
+ // NotificationProviderCard
+ lastSuccess: 'Senast: {{date}}',
+ error: 'Fel',
+ printer: 'Skrivare:',
+ allPrinters: 'Alla skrivare',
+ sendTestNotification: 'Skicka testnotis',
+ eventSettings: 'Händelseinställningar',
+ enabled: 'Aktiverad',
+ sendFromProvider: 'Skicka notiser från denna leverantör',
+ // Event categories
+ printEvents: 'Utskriftshändelser',
+ printerStatus: 'Skrivarstatus',
+ amsAlarms: 'AMS-larm',
+ amsHtAlarms: 'AMS-HT-larm',
+ printQueue: 'Utskriftskö',
+ // Event tags (badges)
+ start: 'Start',
+ plateCheck: 'Plattkontroll',
+ complete: 'Slutförd',
+ failed: 'Misslyckad',
+ stopped: 'Stoppad',
+ progress: 'Förlopp',
+ offline: 'Offline',
+ lowFilament: 'Lågt filament',
+ maintenance: 'Underhåll',
+ amsHumidity: 'AMS-luftfuktighet',
+ amsTemp: 'AMS-temperatur',
+ amsDryingSuspended: 'Torkning pausad',
+ amsHtHumidity: 'AMS-HT-luftfuktighet',
+ amsHtTemp: 'AMS-HT-temperatur',
+ bedCooled: 'Byggplatta kyld',
+ firstLayer: 'Första skikt',
+ quiet: 'Tyst',
+ digest: 'Sammanfattning {{time}}',
+ // Event labels (expanded settings)
+ printStarted: 'Utskrift startad',
+ plateNotEmpty: 'Platta inte tom',
+ plateNotEmptyDescription: 'Objekt upptäckta före utskrift',
+ plateClearRequired: 'Platt-rensning krävs',
+ plateClearRequiredDescription: 'Utskrift slutförd, kön väntar på plattbekräftelse',
+ plateClear: 'Platta rensad',
+ printCompleted: 'Utskrift slutförd',
+ bedCooledLabel: 'Byggplatta kyld',
+ bedCooledDescription: 'Byggplattan kyld under tröskelvärdet efter utskrift',
+ firstLayerCompleteLabel: 'Första skikt klart',
+ firstLayerCompleteDescription: 'Notis med ögonblicksbild när första skiktet är klart',
+ missingSpoolAssignmentLabel: 'Rulltilldelning saknas',
+ billingChargeFailedLabel: 'Faktureringsavgift misslyckades',
+ billingChargeFailedDescription: 'Notis när utskriftskostnader inte kunde registreras',
+ missingSpoolAssignmentDescription: 'Notis när utskrift startar och nödvändiga fack saknar tilldelad rulle',
+ printFailed: 'Utskrift misslyckades',
+ printStopped: 'Utskrift stoppad',
+ progressMilestones: 'Förloppsmilstolpar',
+ progressMilestonesDescription: 'Notis vid 25%, 50%, 75%',
+ printerOffline: 'Skrivare offline',
+ printerError: 'Skrivarfel',
+ aiFailureDetection: 'AI-felsdetektering',
+ aiFailureDetectionDescription: 'Notis när Obico AI upptäcker ett möjligt utskriftsfel',
+ lowFilamentLabel: 'Lågt filament',
+ maintenanceDue: 'Underhåll förfallit',
+ maintenanceDueDescription: 'Notis när underhåll behövs',
+ amsHumidityHigh: 'Hög AMS-luftfuktighet',
+ amsHumidityHighDescription: 'AMS-luftfuktighet överskrider tröskelvärdet',
+ amsTemperatureHigh: 'Hög AMS-temperatur',
+ amsTemperatureHighDescription: 'AMS-temperatur överskrider tröskelvärdet',
+ amsDryingSuspendedTitle: 'Auto-torkning pausad',
+ amsDryingSuspendedDescription: 'Automatisk torkning avbröts för en AMS-enhet eftersom luftfuktigheten aldrig sjönk under tröskelvärdet',
+ amsHtHumidityHigh: 'Hög AMS-HT-luftfuktighet',
+ amsHtHumidityHighDescription: 'AMS-HT-luftfuktighet överskrider tröskelvärdet',
+ amsHtTemperatureHigh: 'Hög AMS-HT-temperatur',
+ amsHtTemperatureHighDescription: 'AMS-HT-temperatur överskrider tröskelvärdet',
+ // Inventory stock alert events
+ inventoryAlerts: 'Lagerlarm',
+ stockReorderAlert: 'Påfyllningslarm',
+ stockReorderAlertDescription: 'Artikel har nått sin påfyllningsnivå',
+ stockBreakAlert: 'Lagerslutslarm',
+ stockBreakAlertDescription: 'Lagret kommer att ta slut innan påfyllning ankommer',
+ // Queue events
+ jobAdded: 'Jobb tillagt',
+ jobAddedDescription: 'Jobb tillagt i kön',
+ jobAssigned: 'Jobb tilldelat',
+ jobAssignedDescription: 'Modellbaserat jobb tilldelat till skrivare',
+ jobStarted: 'Jobb startat',
+ jobStartedDescription: 'Köjobb startade utskrift',
+ jobWaiting: 'Jobb väntar',
+ jobWaitingDescription: 'Jobb väntar på filament eller skrivare',
+ jobSkipped: 'Jobb hoppades över',
+ jobSkippedDescription: 'Jobb hoppades över (föregående misslyckades)',
+ jobFailed: 'Jobb misslyckades',
+ jobFailedDescription: 'Jobb kunde inte starta',
+ queueComplete: 'Kö slutförd',
+ queueCompleteDescription: 'Alla köjobb slutförda',
+ // Quiet hours
+ quietHours: 'Tysta timmar',
+ noNotificationsDuring: 'Inga notiser under dessa timmar',
+ editProviderToChangeQuietHours: 'Redigera leverantör för att ändra tysta timmar',
+ // Daily digest
+ dailyDigest: 'Daglig sammanfattning',
+ batchNotifications: 'Samla notiser till en daglig sammanfattning',
+ sendAt: 'Skicka klockan {{time}}',
+ editProviderToChangeDigestTime: 'Redigera leverantör för att ändra sammanfattningstid',
+ // Actions
+ edit: 'Redigera',
+ deleteProvider: 'Ta bort notisleverantör',
+ deleteConfirm: 'Är du säker på att du vill ta bort "{{name}}"? Detta kan inte ångras.',
+ delete: 'Ta bort',
+ // AddNotificationModal
+ addTitle: 'Lägg till notisleverantör',
+ editTitle: 'Redigera notisleverantör',
+ nameLabel: 'Namn *',
+ namePlaceholder: 'Mina notiser',
+ providerTypeLabel: 'Leverantörstyp *',
+ configuration: 'Konfiguration',
+ testConfiguration: 'Testa konfiguration',
+ printerFilter: 'Skrivarfilter',
+ onlyFromPrinter: 'Skicka notiser endast för händelser från denna skrivare',
+ quietHoursDnd: 'Tysta timmar (Stör ej)',
+ quietStart: 'Start',
+ quietEnd: 'Slut',
+ dailyDigestLabel: 'Daglig sammanfattning',
+ sendDigestAt: 'Skicka sammanfattning klockan',
+ digestCollected: 'Händelser kommer att samlas och skickas som en sammanfattning vid denna tid',
+ notificationEvents: 'Notishändelser',
+ progressPercent: '(25%, 50%, 75%)',
+ bedCooledAfterPrint: '(efter utskrift slutförs)',
+ haSensorAlert: 'Sensorlarm',
+ haSensorAlertDescription: '(en kopplad Home Assistant-sensor behöver uppmärksamhet)',
+ locationHaSensorAlert: 'Sensorlarm för lagringsplats',
+ locationHaSensorAlertDescription: '(en Home Assistant-sensor som är kopplad till en lagringsplats behöver uppmärksamhet)',
+ // Per-event ntfy priority (#990)
+ eventPriority: {
+ sectionTitle: 'ntfy-prioritet',
+ helpNtfy: 'Välj en prioritet för varje aktiverad händelse. ntfy använder dessa för att eskalera larm (ljud, synlighet, push-beteende). Nivåer som inte sätts här använder ntfy-serverns standard.',
+ min: 'Min',
+ low: 'Låg',
+ default: 'Standard',
+ high: 'Hög',
+ urgent: 'Brådskande',
+ },
+ cancel: 'Avbryt',
+ save: 'Spara',
+ add: 'Lägg till',
+ nameRequired: 'Namn krävs',
+ fieldRequired: '{{field}} krävs',
+ haDataInvalid: 'Data-fältet måste vara ett giltigt JSON-objekt, t.ex. {"priority": "high", "ttl": 0}',
+ // Config field labels
+ phoneNumber: 'Telefonnummer',
+ apiKey: 'API-nyckel',
+ serverUrl: 'Server-URL',
+ topic: 'Ämne',
+ authToken: 'Autentiseringstoken',
+ userKey: 'Användarnyckel',
+ appToken: 'App-nyckel',
+ priority: 'Prioritet',
+ pushoverRetry: 'Nödåterförsök (s)',
+ pushoverExpire: 'Nödutgång (s)',
+ botToken: 'Bot-token',
+ chatId: 'Chat-ID',
+ telegramThreadId: 'Forumämnes-ID',
+ telegramThreadIdHelp: 'Valfritt. Skickar till ett enda ämne i en forumgrupp — det sista numret i ämneslänken (t.me/c/.../25). Lämna tomt för det allmänna ämnet.',
+ telegramThreadIdInvalid: 'Forumämnes-ID måste vara ett nummer.',
+ smtpServer: 'SMTP-server',
+ smtpPort: 'SMTP-port',
+ security: 'Säkerhet',
+ authentication: 'Autentisering',
+ username: 'Användarnamn',
+ password: 'Lösenord',
+ fromEmail: 'Från e-post',
+ toEmail: 'Till e-post',
+ webhookUrl: 'Webhook-URL',
+ payloadFormat: 'Nyttolastsformat',
+ authorization: 'Autorisation',
+ titleFieldName: 'Titel-fältnamn',
+ messageFieldName: 'Meddelande-fältnamn',
+ // NotificationTemplateEditor
+ editTemplate: 'Redigera mall: {{name}}',
+ titleLabel: 'Titel',
+ bodyLabel: 'Brödtext',
+ titlePlaceholder: 'Notistitel...',
+ bodyPlaceholder: 'Notisbrödtext...',
+ availableVariables: 'Tillgängliga variabler',
+ clickToInsert: 'Klicka för att infoga vid markörpositionen i brödtext',
+ livePreview: 'Live-förhandsvisning',
+ hide: 'Dölj',
+ show: 'Visa',
+ loadingPreview: 'Laddar förhandsvisning...',
+ enterTemplateContent: 'Ange mallinnehåll för att se förhandsvisning',
+ titlePreview: 'Titel:',
+ bodyPreview: 'Brödtext:',
+ resetToDefault: 'Återställ till standard',
+ titleRequired: 'Titel krävs',
+ bodyRequired: 'Brödtext krävs',
+ // NotificationLogViewer
+ notificationLog: 'Notislogg',
+ showFailedOnly: 'Visa endast misslyckade',
+ last24Hours: 'Senaste 24 timmarna',
+ last7Days: 'Senaste 7 dagarna',
+ last30Days: 'Senaste 30 dagarna',
+ last90Days: 'Senaste 90 dagarna',
+ justNow: 'Precis nu',
+ noFailedNotifications: 'Inga misslyckade notiser',
+ noNotificationsLogged: 'Inga notiser loggade',
+ unknownProvider: 'Okänd leverantör',
+ logTitle: 'Titel',
+ logMessage: 'Meddelande',
+ logError: 'Fel',
+ logProvider: 'Leverantör: {{type}}',
+ logTime: 'Tid: {{time}}',
+ refresh: 'Uppdatera',
+ clearOld: 'Rensa gamla',
+ statsSummary: 'Senaste {{days}} dagarna:',
+ statsNotifications: 'notiser',
+ statsSent: '{{count}} skickade',
+ statsFailed: '{{count}} misslyckade',
+ // Event type labels (for log viewer)
+ eventTypes: {
+ print_start: 'Utskrift startad',
+ print_complete: 'Utskrift slutförd',
+ print_failed: 'Utskrift misslyckades',
+ print_stopped: 'Utskrift stoppad',
+ print_progress: 'Förlopp',
+ printer_offline: 'Skrivare offline',
+ printer_error: 'Skrivarfel',
+ filament_low: 'Lågt filament',
+ maintenance_due: 'Underhåll förfallit',
+ test: 'Test',
+ },
+ // User email notification preferences
+ userEmail: {
+ title: 'Notiser',
+ emailNotifications: 'E-postnotiser',
+ emailNotificationsDesc: 'Ta emot e-postnotiser för dina egna utskriftsjobb. E-postmeddelanden skickas med hjälp av systemets SMTP-inställningar som konfigurerats i avancerad autentisering.',
+ sendingTo: 'Notiser kommer att skickas till',
+ noEmailWarning: 'Ditt konto har ingen e-postadress. Kontakta en administratör för att lägga till en.',
+ printJobNotifications: 'Utskriftsjobbnotiser',
+ printJobNotificationsDesc: 'Välj vilka händelser som utlöser e-postnotiser för utskriftsjobb som du skickar in.',
+ printJobStarts: 'Utskriftsjobb startar',
+ printJobStartsDesc: 'Få en notis när ditt utskriftsjobb börjar.',
+ printJobFinishes: 'Utskriftsjobb slutförs',
+ printJobFinishesDesc: 'Få en notis när ditt utskriftsjobb slutförs.',
+ printErrors: 'Utskriftsfel',
+ printErrorsDesc: 'Få en notis när ditt utskriftsjobb misslyckas eller stöter på ett fel.',
+ printJobStops: 'Utskriftsjobb stoppas',
+ printJobStopsDesc: 'Få en notis när ditt utskriftsjobb avbryts eller stoppas.',
+ saveSuccess: 'Notisinställningar sparade.',
+ saveError: 'Kunde inte spara notisinställningar.',
+ },
+ },
+
+ // Rich Text Editor
+ richTextEditor: {
+ bold: 'Fet',
+ italic: 'Kursiv',
+ underline: 'Understruken',
+ bulletList: 'Punktlista',
+ numberedList: 'Numrerad lista',
+ alignLeft: 'Vänsterjustera',
+ alignCenter: 'Centrera',
+ alignRight: 'Högerjustera',
+ addLink: 'Lägg till länk',
+ removeLink: 'Ta bort länk',
+ },
+
+ // External Links
+ externalLinks: {
+ title: 'Sidofältslänkar',
+ sidebarLayout: 'Sidofält',
+ sidebarLayoutDescription: 'Visa eller dölj inbyggda sidor, lägg till externa länkar och dra objekt för att omordna sidofältsnavigeringen.',
+ systemPages: 'Bambuddy-sidor',
+ externalLinks: 'Externa länkar',
+ visibleInSidebar: 'Synlig i sidofält',
+ hiddenFromSidebar: 'Dold från sidofält',
+ requiredInSidebar: 'Obligatorisk i sidofält',
+ hidePage: 'Dölj sida',
+ showPage: 'Visa sida',
+ settingsCannotBeHidden: 'Inställningar kan ej döljas',
+ noLinksConfigured: 'Inga externa länkar konfigurerade',
+ deleteLink: 'Ta bort länk',
+ removeCustomIcon: 'Ta bort anpassad ikon',
+ openInNewTab: 'Öppna i ny flik',
+ placeholders: {
+ linkName: 'Min länk',
+ },
+ },
+
+ // Keyboard Shortcuts Modal
+ keyboardShortcuts: {
+ title: 'Tangentbordsgenvägar',
+ navigation: 'Navigering',
+ archivesSection: 'Arkiv',
+ kProfilesSection: 'K-profiler',
+ generalSection: 'Allmänt',
+ shortcuts: {
+ goToPrinters: 'Gå till Skrivare',
+ goToArchives: 'Gå till Arkiv',
+ goToQueue: 'Gå till Kö',
+ goToStats: 'Gå till Statistik',
+ goToProfiles: 'Gå till Molnprofiler',
+ goToSettings: 'Gå till Inställningar',
+ focusSearch: 'Fokusera sökning',
+ openUploadModal: 'Öppna uppladdningsmodal',
+ clearSelection: 'Rensa val / ta bort fokus från inmatning',
+ contextMenu: 'Kontextmeny på kort',
+ refreshProfiles: 'Uppdatera profiler',
+ newProfile: 'Ny profil',
+ exitSelectionMode: 'Avsluta urvalsläge',
+ showHelp: 'Visa denna hjälp',
+ },
+ footer: 'Tryck på Esc eller klicka utanför för att stänga',
+ },
+
+ // Notification Log
+ notificationLog: {
+ title: 'Notislogg',
+ events: {
+ printStarted: 'Utskrift startad',
+ printComplete: 'Utskrift slutförd',
+ printFailed: 'Utskrift misslyckades',
+ printStopped: 'Utskrift stoppad',
+ progress: 'Förlopp',
+ printerOffline: 'Skrivare offline',
+ printerError: 'Skrivarfel',
+ lowFilament: 'Lågt filament',
+ maintenanceDue: 'Underhåll utebliven',
+ test: 'Test',
+ },
+ timeAgo: {
+ justNow: 'Precis nu',
+ minutesAgo: '{{minutes}}m sedan',
+ hoursAgo: '{{hours}}t sedan',
+ },
+ },
+
+ // Restore/Backup Modal
+ restoreBackup: {
+ title: 'Återställ säkerhetskopia',
+ restoring: 'Återställer...',
+ restoreComplete: 'Återställning slutförd',
+ restoreFailed: 'Kunde inte återställa',
+ importSettings: 'Importera inställningar från en säkerhetskopia',
+ pleaseWait: 'Vänligen vänta medan din data återställs',
+ clickToSelect: 'Klicka för att välja säkerhetskopia (.json eller .zip)',
+ howDuplicateHandling: 'Hur dubbletter hanteras:',
+ categories: {
+ printers: 'Skrivare',
+ smartPlugs: 'Kontakter',
+ notificationProviders: 'Notisleverantörer',
+ filaments: 'Filament',
+ archives: 'Arkiv',
+ pendingUploads: 'Väntande uppladdningar',
+ settingsTemplates: 'Inställningar & mallar',
+ },
+ matchingInfo: {
+ printers: 'matchade efter serienummer',
+ smartPlugs: 'matchade efter IP-adress',
+ notificationProviders: 'matchade efter namn',
+ filaments: 'matchade efter namn + typ + märke',
+ archives: 'matchade efter innehållshash',
+ pendingUploads: 'matchade efter filnamn',
+ settingsTemplates: 'skrivs alltid över',
+ },
+ replaceExisting: 'Ersätt befintlig data',
+ keepExisting: 'Behåll befintlig data',
+ replaceDescription: 'Skriv över objekt som redan finns med säkerhetskopia',
+ keepDescription: 'Återställ endast objekt som inte redan finns',
+ caution: 'Varning:',
+ cautionText: 'Att skriva över kommer att ersätta dina nuvarande konfigurationer med data från säkerhetskopian. Skrivaråtkomstkoder skrivs aldrig över av säkerhetsskäl.',
+ itemsRestored: 'Objekt återställda',
+ itemsSkipped: 'Objekt hoppade över',
+ restored: 'Återställd',
+ skipped: 'Hoppades över (finns redan)',
+ filesLabel: 'Filer (3MF, minibilder, etc.)',
+ newApiKeysGenerated: 'Nya API-nycklar genererade',
+ newApiKeysWarning: 'Dessa nycklar visas endast en gång. Kopiera dem nu!',
+ processingBackup: 'Bearbetar säkerhetskopia...',
+ noDataFound: 'Ingen data hittades att återställa i säkerhetskopian.',
+ failedToRestore: 'Kunde inte återställa säkerhetskopia. Kontrollera filformatet.',
+ },
+
+ // Backup Export Modal
+ backupExport: {
+ title: 'Exportera säkerhetskopia',
+ selectData: 'Välj data att inkludera',
+ selectAll: 'Markera alla',
+ selectNone: 'Avmarkera alla',
+ categoryDescriptions: {
+ settings: 'Språk, tema, uppdateringsinställningar',
+ notifications: 'ntfy, Pushover, Discord, etc.',
+ templates: 'Anpassade meddelandemallar',
+ smartPlugs: 'Tasmota-kontaktkonfigurationer',
+ externalLinks: 'Sidofältslänkar till externa tjänster',
+ printers: 'Skrivarinformation (åtkomstkoder exkluderade)',
+ plateDetection: 'Referensbilder för tomma plattor',
+ filaments: 'Filamenttyper och kostnader',
+ maintenance: 'Anpassade underhållsscheman',
+ archives: 'All utskriftsdata + filer (3MF, minibilder, foton)',
+ projects: 'Projekt, BOM-objekt och bilagor',
+ pendingUploads: 'Uppladdningar från virtuell skrivare som väntar på granskning',
+ apiKeys: 'Webhook API-nycklar (nya nycklar genereras vid import)',
+ },
+ requiresPrinters: 'Kräver att Skrivare är valda',
+ zipFileWarning: 'En ZIP-fil kommer att skapas.',
+ zipFileDescription: 'Inkluderar alla 3MF-filer, minibilder, timelapse och foton. Detta kan ta en stund och resultera i en stor fil.',
+ includeAccessCodes: 'Inkludera åtkomstkoder',
+ includeAccessCodesDescription: 'För överföring till en annan maskin',
+ includeAccessCodesWarning: 'Åtkomstkoder kommer att inkluderas i klartext. Håll denna säkerhetskopia säker!',
+ categoriesSelected: '{{selectedCount}} kategorier valda',
+ },
+
+ // Pending Uploads Panel
+ pendingUploads: {
+ placeholders: {
+ notes: 'Lägg till anteckningar om denna utskrift...',
+ },
+ discardUpload: 'Kassera uppladdning',
+ archiveAllUploads: 'Arkivera alla uppladdningar',
+ discardAllUploads: 'Kassera alla uppladdningar',
+ archive: 'Arkivera',
+ timeAgo: {
+ justNow: 'Precis nu',
+ minutesAgo: '{{minutes}}m sedan',
+ hoursAgo: '{{hours}}t sedan',
+ daysAgo: '{{days}}d sedan',
+ },
+ },
+
+ // API Browser
+ apiBrowser: {
+ placeholders: {
+ requestBody: 'JSON-förfrågningsinnehåll...',
+ searchEndpoints: 'Sök ändpunkter...',
+ },
+ },
+
+ // Configure AMS Slot Modal
+ configureAmsSlot: {
+ title: 'Konfigurera AMS-fack',
+ slotConfigured: 'Fack konfigurerat!',
+ configuringSlot: 'Konfigurerar fack:',
+ slotLabel: '{{ams}} fack {{slot}}',
+ searchPresets: 'Sök förinställningar...',
+ colorPlaceholder: 'Färgnamn eller hex (t.ex. brun, FF8800)',
+ clearCustomColor: 'Rensa anpassad färg',
+ noCloudPresets: 'Inga molnförinställningar. Logga in på Bambu Cloud för att synkronisera.',
+ noPresetsAvailable: 'Inga förinställningar tillgängliga. Logga in på Bambu Cloud eller importera lokala profiler.',
+ noMatchingPresets: 'Inga matchande förinställningar hittade.',
+ custom: 'Anpassad',
+ builtin: 'Inbyggd',
+ orcaCloud: 'Orca Cloud',
+ bambuCloud: 'Bambu Cloud',
+ settingsSentToPrinter: 'Inställningar skickade till skrivare',
+ filamentProfile: 'Filamentprofil',
+ kProfileLabel: 'K-profil (Tryckförskott)',
+ filteringFor: 'Filtrerar för: {{material}}',
+ noKProfile: 'Ingen K-profil (använd standard 0,020)',
+ noMatchingKProfiles: 'Inga matchande K-profiler hittade. Standard K=0,020 kommer att användas.',
+ otherKProfiles: 'Andra K-profiler på denna skrivare',
+ selectFilamentFirst: 'Välj en filamentprofil först',
+ kFromCalibration: 'K={{value}} från skrivarkalibrering',
+ customColorLabel: 'Anpassad färg (valfritt)',
+ presetColors: '{{name}}-färger:',
+ showLessColors: 'Visa färre färger',
+ showMoreColors: 'Visa fler färger',
+ clear: 'Rensa',
+ hexLabel: 'Hex: #{{hex}}',
+ resetting: 'Återställer...',
+ resetSlot: 'Återställ fack',
+ cancel: 'Avbryt',
+ configuring: 'Konfigurerar...',
+ configureSlot: 'Konfigurera fack',
+ },
+
+ // Git Backup Settings
+ githubBackup: {
+ title: 'Git-säkerhetskopia',
+ history: 'Historik',
+ downloadBackup: 'Ladda ner säkerhetskopia',
+ restoreBackup: 'Återställ säkerhetskopia',
+ noBackupsYet: 'Inga säkerhetskopior än',
+ },
+
+ // Email Settings
+ emailSettings: {
+ placeholders: {
+ fromName: 'Bambuddy',
+ },
+ },
+
+ // Tag Management Modal
+ tagManagement: {
+ searchTags: 'Sök taggar...',
+ renameTag: 'Döp om tagg',
+ deleteTag: 'Ta bort tagg',
+ },
+
+ // Notification Template Editor
+ notificationTemplates: {
+ placeholders: {
+ title: 'Notistitel...',
+ body: 'Notisbrödtext...',
+ },
+ },
+
+ // Batch Tag Modal
+ batchTag: {
+ placeholders: {
+ newTag: 'Ange ny tagg...',
+ },
+ },
+
+ // Photo Gallery Modal
+ photoGallery: {
+ deletePhoto: 'Ta bort foto',
+ },
+
+ // Filament Hover Card
+ filamentHoverCard: {
+ copySpoolUuid: 'Kopiera rullens UUID',
+ },
+
+ // K Profiles View
+ kProfilesView: {
+ hasNote: 'Har anteckning',
+ copyProfile: 'Kopiera profil',
+ },
+
+ // Layout/Navigation
+ layout: {
+ openMenu: 'Öppna meny',
+ noPermissionSystemInfo: 'Du har inte behörighet att visa systeminformation',
+ },
+
+ // Dashboard
+ dashboard: {
+ dragToReorder: 'Dra för att omordna',
+ hideWidget: 'Dölj widget',
+ },
+
+ // Notification Provider Card
+ notificationProviderCard: {
+ deleteNotificationProvider: 'Ta bort notisleverantör',
+ },
+
+ // File Manager Modal
+ fileManagerModal: {
+ closeFileManager: 'Stäng filhanterare',
+ sortFiles: 'Sortera filer',
+ goToParentFolder: 'Gå till överordnad mapp',
+ threeView: '3D-vy',
+ },
+
+ // Embedded Camera Viewer
+ embeddedCameraViewer: {
+ refreshStream: 'Uppdatera ström',
+ close: 'Stäng',
+ zoomOut: 'Zooma ut',
+ resetZoom: 'Återställ zoom',
+ zoomIn: 'Zooma in',
+ dragToResize: 'Dra för att ändra storlek',
+ },
+
+ // Timelapse Viewer
+ timelapseViewer: {
+ skipBack5s: 'Hoppa bakåt 5s',
+ skipForward5s: 'Hoppa framåt 5s',
+ },
+
+ // Notification Providers
+ notificationProviders: {
+ descriptions: {
+ email: 'SMTP e-postnotiser',
+ telegram: 'Notiser via Telegram-bot',
+ discord: 'Skicka till Discord-kanal via webhook',
+ ntfy: 'Gratis, självhostade push-notiser',
+ pushover: 'Enkla, pålitliga push-notiser',
+ callmebot: 'Gratis WhatsApp-notiser via CallMeBot',
+ webhook: 'Generisk HTTP POST till valfri URL',
+ },
+ },
+
+ // Log Viewer
+ logViewer: {
+ searchPlaceholder: 'Sök meddelande eller loggnamn...',
+ noLogEntries: 'Inga loggposter hittade',
+ },
+
+ // Switchbar Popover
+ switchbarPopover: {
+ noSwitchesInSwitchbar: 'Inga växlar i kontaktpanelen',
+ },
+
+ // Project Page Modal
+ projectPageModal: {
+ placeholders: {
+ title: 'Titel',
+ designer: 'Designer',
+ license: 'Licens',
+ description: 'Ange beskrivning...',
+ profileTitle: 'Profilnamn',
+ profileDescription: 'Profilbeskrivning...',
+ },
+ },
+
+ // Spoolman Settings
+ spoolmanSettings: {},
+
+ // Time
+ time: {
+ unknown: '-',
+ waiting: 'Väntar',
+ justNow: 'Precis nu',
+ now: 'Nu',
+ minsAgo: '{{count}}m sedan',
+ inMins: 'om {{count}}m',
+ hoursAgo: '{{count}}t sedan',
+ inHours: 'om {{count}}t',
+ daysAgo: '{{count}}d sedan',
+ inDays: 'om {{count}}d',
+ },
+
+ // SpoolBuddy Kiosk
+ spoolbuddy: {
+ nav: {
+ dashboard: 'Instrumentpanel',
+ ams: 'AMS',
+ inventory: 'Inventarium',
+ writeTag: 'Skriv tagg',
+ settings: 'Inställningar',
+ },
+ status: {
+ nfcReady: 'NFC redo',
+ nfcOff: 'NFC av',
+ offline: 'Offline',
+ online: 'Online',
+ noPrinters: 'Inga skrivare',
+ deviceOffline: 'Enhet offline',
+ waitingConnection: 'Väntar på enhetsanslutning...',
+ systemReady: 'System redo',
+ status: 'Status',
+ },
+ dashboard: {
+ readyToScan: 'Redo att skanna',
+ idleMessage: 'Placera en rulle på vågen för att identifiera den',
+ nfcHint: 'NFC-tagg kommer att läsas automatiskt',
+ device: 'Enhet',
+ syncWeight: 'Synkronisera vikt',
+ weightSynced: 'Synkroniserad!',
+ unknownTag: 'Okänd tagg',
+ newTag: 'Ny tagg upptäckt',
+ onScale: 'på våg',
+ linkSpool: 'Länka till rulle',
+ linkTagTitle: 'Länka tagg till rulle',
+ linkTag: 'Länka tagg',
+ selectSpool: 'Välj en rulle att länka denna tagg till:',
+ noUntagged: 'Inga rullar utan taggar hittade',
+ tagDetected: 'Tagg upptäckt',
+ noTag: 'Ingen tagg',
+ tagId: 'Tagg',
+ grossWeight: 'Bruttovikt',
+ spoolSize: 'Rullstorlek',
+ close: 'Stäng',
+ currentSpool: 'Aktuell rulle',
+ plateReady: 'Platta redo: {{name}}',
+ plateReadyLabel: 'Plattor redo att rensa',
+ plateClearAction: 'Rensa',
+ plateClearedToast: 'Platta markerad som rensad',
+ plateClearFailed: 'Kunde inte markera platta som rensad',
+ },
+ modal: {
+ spoolDetected: 'Rulle upptäckt',
+ assignToAms: 'Tilldela till AMS',
+ syncWeight: 'Synkronisera vikt',
+ weightSynced: 'Synkroniserad!',
+ syncing: 'Synkroniserar...',
+ newTagDetected: 'Ny tagg upptäckt',
+ addToInventory: 'Lägg till i inventarium',
+ assignToAmsTitle: 'Tilldela till AMS',
+ selectSlot: 'Välj ett fack',
+ assign: 'Tilldela',
+ assigning: 'Tilldelar...',
+ assignSuccess: 'Tilldelad!',
+ assignPendingInsert: 'Tilldelad. Facket kommer att konfigureras när du sätter in rullen.',
+ assignError: 'Kunde inte tilldela rulle. Försök igen.',
+ noPrinterSelected: 'Välj en skrivare...',
+ noAmsDetected: 'Ingen AMS upptäckt på denna skrivare',
+ slot: 'Fack',
+ },
+ weight: {
+ noReading: 'Ingen avläsning',
+ stable: 'Stabil',
+ measuring: 'Mäter...',
+ tare: 'Tara',
+ calibrate: 'Kalibrera',
+ },
+ spool: {
+ remaining: 'Återstående',
+ material: 'Material',
+ brand: 'Märke',
+ color: 'Färg',
+ coreWeight: 'Kärna',
+ labelWeight: 'Etikett',
+ scaleWeight: 'Våg',
+ netWeight: 'Nettovikt',
+ lastUsed: 'Senast använd',
+ unknownColor: 'Okänd färg',
+ },
+ ams: {
+ noData: 'Ingen AMS upptäckt',
+ connectAms: 'Anslut en AMS för att se filamentfack',
+ noPrinter: 'Ingen skrivare vald',
+ selectPrinter: 'Välj en skrivare från den övre menyn',
+ printerDisconnected: 'Skrivare frånkopplad',
+ humidity: 'Luftfuktighet',
+ level: 'Nivå',
+ active: 'Aktiv',
+ slot: 'Fack',
+ empty: 'Tomt',
+ },
+ inventory: {
+ search: 'Sök rullar...',
+ empty: 'Inga rullar i inventariet',
+ noResults: 'Inga matchande rullar',
+ spools: 'rullar',
+ addSpool: 'Lägg till rulle',
+ },
+ settings: {
+ // Tabs
+ tabDevice: 'Enhet',
+ tabDisplay: 'Skärm',
+ tabScale: 'Våg',
+ tabUpdates: 'Uppdateringar',
+ // Device tab
+ nfcReader: 'NFC-läsare',
+ type: 'Typ',
+ connection: 'Anslutning',
+ notConnected: 'Ej ansluten',
+ deviceInfo: 'Enhetsinformation',
+ hostname: 'Värdnamn',
+ uptime: 'Driftstid',
+ systemConfig: 'Backend & Autentisering',
+ backendUrl: 'Bambuddy Backend-URL',
+ apiToken: 'API-token',
+ apiTokenPlaceholder: 'Ange API-token',
+ saveConfig: 'Spara konfiguration',
+ systemQueued: 'Konfiguration köad.',
+ nfcDiagnostic: 'NFC-diagnostik',
+ scaleDiagnostic: 'Vågdiagnostik',
+ readTagDiagnostic: 'Läs tagg-diagnostik',
+ testNfc: 'Testa läsare',
+ testScale: 'Testa noggrannhet',
+ testReadTag: 'Läs tagg',
+ systemFieldsRequired: 'Backend-URL krävs.',
+ // Display tab
+ brightness: 'Ljusstyrka',
+ saved: 'Sparad',
+ noBacklight: 'Ingen DSI-bakgrundsbelysning upptäckt. Ljusstyrkekontroll kräver en DSI-display.',
+ screenBlank: 'Skärm avstängningstimeout',
+ screenBlankDesc: 'Skärmen stängs av efter inaktivitet. Tryck för att väcka.',
+ displayNote: 'Ljusstyrka tillämpas som ett mjukvarufilter.',
+ // Scale tab
+ scaleCalibration: 'Vågkalibrering',
+ currentWeight: 'Aktuell vikt',
+ tareOffset: 'Tara',
+ calFactor: 'Faktor',
+ knownWeight: 'Känd vikt',
+ calStep1: 'Ta bort alla föremål från vågen och tryck på Sätt noll.',
+ calStep2: 'Placera känd vikt på vågen.',
+ setZero: 'Sätt noll',
+ calibrateNow: 'Kalibrera',
+ calibrated: 'Kalibrerad',
+ tareSet: 'Tara-kommando skickad. Väntar på enhet...',
+ tareComplete: 'Tara slutförd!',
+ tareTimedOut: 'Tara timeout — körs SpoolBuddy-demon?',
+ tareFailed: 'Kunde inte skicka tarakommando',
+ zeroSet: 'Nollpunkt inställd. Placera känd vikt på vågen.',
+ calibrationDone: 'Kalibrering slutförd!',
+ calibrationFailed: 'Kalibrering misslyckades',
+ lastCalibrated: 'Senast kalibrerad',
+ stable: 'Stabil',
+ settling: 'Stabiliserar...',
+ firmware: 'Firmware',
+ scale: 'Våg',
+ noDevice: 'Ingen SpoolBuddy-enhet hittad',
+ // Updates tab
+ daemonVersion: 'Demonversion',
+ currentVersion: 'Aktuell',
+ versionPending: 'Väntar på demon...',
+ checking: 'Kontrollerar...',
+ checkUpdates: 'Sök efter uppdateringar',
+ updateAvailable: 'Uppdatering tillgänglig',
+ updateInstructions: 'Uppdatera via SSH: kör SpoolBuddy-installationsskriptet för att uppgradera.',
+ upToDate: 'Uppdaterad',
+ includeBeta: 'Inkludera betaversioner',
+ },
+ writeTag: {
+ tabExisting: 'Befintlig rulle',
+ tabNew: 'Ny rulle',
+ tabReplace: 'Ersätt tagg',
+ searchPlaceholder: 'Sök efter material, färg, märke...',
+ noUntaggedSpools: 'Inga rullar utan taggar',
+ noTaggedSpools: 'Inga rullar med taggar',
+ selectSpool: 'Välj en rulle, placera sedan en tom NTAG på läsaren',
+ placeTag: 'Placera en NTAG på läsaren',
+ tagReady: 'Tagg upptäckt — redo att skriva',
+ writeTag: 'Skriv tagg',
+ replaceTag: 'Ersätt tagg',
+ writing: 'Skriver tagg...',
+ waiting: 'Väntar på SpoolBuddy...',
+ writeSuccess: 'Tagg skriven!',
+ writeFailed: 'Kunde inte skriva tagg',
+ queueFailed: 'Kunde inte köa skrivkommando',
+ tryAgain: 'Försök igen',
+ cancel: 'Avbryt',
+ replaceWarning: 'Gamla taggen kommer att avlänkas. Ny tagg kommer att ersätta den.',
+ deviceOffline: 'SpoolBuddy är offline',
+ material: 'Material',
+ colorName: 'Färgnamn',
+ color: 'Färg',
+ brand: 'Märke',
+ weight: 'Vikt (g)',
+ createSpool: 'Skapa rulle',
+ creating: 'Skapar...',
+ spoolCreated: 'Rulle skapad! Redo att skriva.',
+ createFailed: 'Kunde inte skapa rulle',
+ incompleteDataWarning: 'Tagg skriven med ofullständig Spoolman-data',
+ },
+ quickMenu: {
+ printerPower: 'Skrivarström',
+ systemControls: 'System',
+ restartDaemon: 'Starta om demon',
+ restartBrowser: 'Starta om webbläsare',
+ reboot: 'Starta om',
+ shutdown: 'Stäng av',
+ swipeToClose: 'Svep ner för att stänga',
+ confirmTitle: 'Bekräfta',
+ confirmShutdown: 'Är du säker på att du vill stänga av SpoolBuddy? Du kommer att behöva fysisk åtkomst för att sätta på den igen.',
+ confirmReboot: 'Är du säker på att du vill starta om SpoolBuddy?',
+ confirmRestartDaemon: 'Starta om SpoolBuddy-demon? NFC och våg kommer att vara tillfälligt otillgängliga.',
+ confirmRestartBrowser: 'Starta om kiosk-webbläsaren? Skärmen kommer kort att bli tom.',
+ confirm: 'Bekräfta',
+ confirmPlugOn: 'Sätt på {{name}}?',
+ confirmPlugOff: 'Stäng av {{name}}?',
+ turnOn: 'Sätt på',
+ turnOff: 'Stäng av',
+ },
+ },
+
+ diagnostic: {
+ modalTitle: 'Anslutningsdiagnostik — {{name}}',
+ running: 'Kör diagnostik...',
+ runningElapsed: 'Kör diagnostik... ({{elapsed}}s)',
+ waitingForReportHint: 'Lyssnar efter att skrivaren ska publicera en statusrapport — detta kan ta upp till {{max}} sekunder.',
+ runFailed: 'Kunde inte köra diagnostik: {{error}}',
+ retry: 'Kör igen',
+ runButton: 'Kör diagnostik',
+ sectionTitle: 'Anslutningsdiagnostik',
+ sectionDescription: 'Kontrollera varför en skrivare inte ansluter eller inte skriver ut — portåtkomlighet, LAN-utvecklarläge, Docker-nätverksläge och inloggningsuppgifter.',
+ noPrinters: 'Inga skrivare konfigurerade.',
+ overall: {
+ ok: 'Inga problem hittade — skrivaranslutningen ser frisk ut.',
+ warnings: 'Skrivaren bör fungera, men vissa saker behöver uppmärksamhet.',
+ problems: 'Hittade problem som förklarar varför skrivaren inte ansluter eller skriver ut.',
+ },
+ check: {
+ port_mqtt: {
+ title: 'Kontrollport (MQTT 8883)',
+ pass: 'Åtkomlig — skrivaren accepterar kontrollanslutningar.',
+ fail: 'Port 8883 är otillgänglig. Skrivaren är avstängd, har en annan IP-adress, eller en brandvägg blockerar den. Verifiera skrivarens IP och att inget blockerar port 8883.',
+ },
+ port_ftps: {
+ title: 'Filöverföringsport (FTPS 990)',
+ pass: 'Åtkomlig — skickande av utskriftsfiler kommer att fungera.',
+ warn: 'Port 990 är otillgänglig. Övervakning kan fortfarande fungera, men skickande av utskrifter till skrivaren kommer att misslyckas. Se till att port 990 inte blockeras.',
+ warn_no_tls: 'Port 990 är öppen men skrivarens filservice slutför inte ett TLS-handskak. Utskriftsfiler, omslag och timelapse kan inte hämtas, så arkiv förblir tomma. Starta om skrivaren — att låsa upp porten kommer inte att hjälpa.',
+ },
+ external_storage: {
+ title: 'Lagra skickade filer på extern lagring (installationssteg 4)',
+ pass: 'Skrivaren rapporterar att detta alternativ är på — skickade filer kommer att lagras på SD-kortet och arkiv kommer att ha minibilder och slicer-inställningar.',
+ fail: 'Skrivaren rapporterar att detta alternativ är av. Aktivera "Lagra skickade filer på extern lagring" — på nyare firmware (P2S 01.02 / Bambu Studio 2.6+) finns växeln på skrivarens Utskriftsinställningar; på äldre versioner finns den i Bambu Studio / OrcaSlicers Enhetsflik. Utan det saknar varje arkiverad utskrift sin minibild och slicer-inställningar.',
+ skip: 'Kontrollerades inte — kräver en aktiv MQTT-anslutning. På äldre slicers där denna inställning endast finns i slicern rapporterar skrivaren aldrig det, så denna kontroll kommer att passera även när alternativet är av — verifiera installationssteg 4 manuellt.',
+ skip_unsupported_model: 'Denna modell har ett SD-kortfack men inget sätt att slå på alternativet — aktuell P1-serie-firmware exponerar inte växeln i Bambu Studio och skrivaren har ingen skärm. Inget att åtgärda här; arkiverade utskrifter kan sakna minibilder och slicer-inställningar tills Bambu Lab lägger till firmware-stöd.',
+ fail_no_media: 'Alternativet är aktiverat, men skrivaren rapporterar att det inte finns något kort eller sticka i sin kortplats, så det finns ingenstans för skickade filer att ta vägen. Sätt i ett och skriv ut igen – fram till dess kommer alla arkiverade utskrifter att sakna sina minibilder och slicing-metadata.',
+ warn_internal_storage: 'Alternativet är aktiverat och lagring finns, men den sista utskriften gick ändå till skrivarens interna lagring, som Bambuddy inte kan läsa. På H2-serien och P2S skickar Bambu Studios Skriv ut-knapp alltid dit det alternativ det är inställt på. Arkivet skrivs ut med namn och tidpunkt, men utan minibild eller metadata för slicern. För kompletta arkiv, starta utskrifter från Bambuddy eller slica i OrcaSlicer — eller i Bambu Studio, använd Skicka med Externt vald, och starta sedan utskriften.',
+ warn_internal_history: 'Alternativet är aktiverat och lagring finns, men den senaste utskriften kördes från en fil som redan fanns på skrivaren – en ny utskrift från dess skärm, en start från Handy eller en fil som skickades tidigare. Det biblioteket finns på den interna lagringen som Bambuddy inte kan läsa, och ingen inställning ändrar det, eftersom ingenting skickades för den utskriften. Den arkiverar fortfarande med sitt namn och tidpunkt, men utan en minibild eller slicer-metadata. För fullständiga arkiv, starta utskrifter från Bambuddy eller skicka dem från slicern.',
+ },
+ port_rtsps: {
+ title: 'Kameraport ({{protocol}} {{port}})',
+ pass: 'Åtkomlig — kameraströmmen kommer att fungera.',
+ warn: 'Port {{port}} är otillgänglig. Live-kameravyn kommer inte att fungera. Detta påverkar inte utskriften.',
+ },
+ macos_local_network: {
+ title: 'macOS lokala nätverksbehörighet',
+ pass: 'macOS tillåter Bambuddy att nå det lokala nätverket.',
+ warn_unsigned: 'Den Python som kör Bambuddy har ingen kodsignatur, så macOS har inget att koppla en lokal nätverksbehörighet till och släpper tyst varje anslutning till skrivaren - inget fel och ingen prompt. Kör Bambuddy-uppdateringen (install/update_macos.sh), som signerar den, starta sedan om Bambuddy. Tolk: {{executable}}',
+ warn_permission: 'Om skrivaren är påslagen och på den här adressen, öppna Systeminställningar > Sekretess & säkerhet > Lokalt nätverk och se till att den Python som kör Bambuddy är aktiverad. macOS släpper lokala anslutningar tyst när det inte är det, och uppdatering av Python kan lämna den gamla behörigheten bakom sig.',
+ },
+ network_mode: {
+ title: 'Docker-nätverksläge',
+ genericRuntime: 'en container',
+ pass: 'Körs i {{runtime}} med värdnätverk.',
+ warn: 'Bambuddy körs i {{runtime}} med bryggnätverk. Skrivaruppäckt och den virtuella skrivaren kräver värdnätverksläge — skapa containern igen i värdnätverksläge ("network_mode: host" i docker-compose, "--network=host" för Podman).',
+ skip: 'Körs inte i Docker — inte tillämpligt.',
+ skip_unknown: 'Bambuddy körs i {{runtime}}, men dess nätverksläge kunde inte fastställas. Om skrivarupptäckt eller den virtuella skrivaren inte fungerar, återskapa containern med värdnätverk.',
+ skip_system_container: 'Bambuddy körs i en {{runtime}} systemcontainer, som sitter på LAN som en virtuell maskin — inte tillämpligt.',
+ },
+ subnet: {
+ title: 'Nätverkssubnät',
+ pass: 'Skrivaren och Bambuddy är på samma subnät.',
+ warn: 'Skrivaren ({{printer_ip}}) och Bambuddy ({{host_ip}}) är på olika subnät. De kan inte nå varandra om inte routning mellan subnäten är konfigurerad.',
+ skip: 'Subnät kunde inte bestämmas — hoppade över.',
+ },
+ mqtt_auth: {
+ title: 'Skrivarens inloggningsuppgifter',
+ pass: 'Skrivaren accepterade anslutningen.',
+ fail: 'Skrivaren är åtkomlig men Bambuddy är inte ansluten till den. Åtkomstkoden eller serienumret är troligen fel — åtkomstkoden ändras varje gång LAN Only eller Utvecklarläge växlas, så kopiera den från skrivarens skärm igen. En skrivare som startar om, eller redan har nått sin gräns för samtidiga anslutningar, kan se ut på samma sätt.',
+ fail_auth_rejected: 'Skrivaren avvisade Bambuddys inloggningsuppgifter. Åtkomstkoden eller serienumret är fel — åtkomstkoden ändras varje gång LAN Only eller Utvecklarläge växlas, så kopiera den från skrivarens skärm och spara den i skrivarinställningarna.',
+ skip: 'Kontrollerades inte — skrivaren kunde inte nås.',
+ },
+ developer_mode: {
+ title: 'LAN-utvecklarläge',
+ pass: 'Utvecklarläge är aktiverat.',
+ fail: 'Utvecklarläge är AV på skrivaren. Aktivera det i skrivarens LAN-inställningar — och bekräfta med OK. Utan det kommer utskrifter inte att starta.',
+ skip: 'Kunde inte kontrolleras — kräver en aktiv anslutning till skrivaren.',
+ },
+ printer_publishing: {
+ title: 'Skrivaren publicerar status',
+ pass: 'Skrivaren publicerar statusuppdateringar — AMS, filament och K-profiler kommer att speglas korrekt till slicern.',
+ fail: 'MQTT-mäklaren accepterade anslutningen men skrivaren har inte publicerat några statusrapporter. Detta är nästan alltid ett felaktigt serienummer eller ett serienummer med felaktigt skiftläge — device//report är skiftlägeskänsligt. Dubbelkolla serienumret i skrivarinställningarna mot skärmen på skrivaren.',
+ skip: 'Kunde inte kontrolleras — kräver en aktiv anslutning till skrivaren.',
+ },
+ },
+ },
+
+ systemHealth: {
+ sectionTitle: 'Systemhälsa',
+ sectionDescription: 'Söker i senaste loggar efter kända problem som du vanligtvis kan åtgärda själv, innan de blir till ett supportärende.',
+ rescan: 'Sök igen',
+ clean: 'Inga kända problem hittade i de senaste {{times}} loggposterna.',
+ logUnavailable: 'Filloggning är inaktiverad, så loggar kan inte sökas. Aktivera filloggning för att använda denna kontroll.',
+ learnMore: 'Hur du åtgärdar',
+ fixLabel: 'Åtgärd:',
+ occurrences: 'Set {{times}}× — senast {{lastSeen}}',
+ category: {
+ layer8: 'Du kan åtgärda detta',
+ environment: 'Miljö',
+ bug: 'Rapportera detta',
+ },
+ signature: {
+ 'ftp-auth-rejected': {
+ name: 'Skrivaren avvisade åtkomstkoden',
+ cause: 'Skrivaren vägrade filöverföringsinloggningen. Åtkomstkoden är fel, eller den ändrades efter att Utvecklarläge växlats.',
+ fix: 'Kopiera åtkomstkoden från skrivarens skärm (LAN-inställningar) och uppdatera den i skrivarens inställningar i Bambuddy.',
+ },
+ 'ftp-connection-timeout': {
+ name: 'Filöverföringsanslutning timeout',
+ cause: 'Bambuddy kunde inte nå skrivarens filöverföringsport (FTPS 990). Porten är blockerad, eller skrivaren är av eller på ett annat subnät.',
+ fix: 'Se till att inget blockerar port 990 mellan Bambuddy och skrivaren, och att båda är på samma nätverk.',
+ },
+ 'ftp-ssl-error': {
+ name: 'Säker filöverförings-handskak misslyckades',
+ cause: 'Skrivarens filservice svarade på port 990 utan TLS. Dess filserver har fastnat — ett fel på skrivarsidan, inte ett brandväggs- eller firmware-problem.',
+ fix: 'Starta om skrivaren. Tills dess kan utskriftsfiler, omslag och timelapse inte hämtas; själva utskriften påverkas inte.',
+ },
+ 'bambu-cloud-captcha': {
+ name: 'Bambu Cloud begär en CAPTCHA',
+ cause: 'Bambus anti-missbrukslager utmanar detta nätverk, så ingen Bambu Cloud-inloggning kan slutföras. Det är kopplat till den offentliga IP-adressen, inte till ditt konto eller denna installation.',
+ fix: 'Vänta — det löser sig normalt inom några timmar, och upprepade inloggningsförsök förlänger det. För att ansluta under tiden, logga in med en åtkomsttoken som tas från en webbläsarsession.',
+ },
+ 'mqtt-connection-flapping': {
+ name: 'Skrivaranslutningen kopplas bort upprepade gånger',
+ cause: 'Kontrollanslutningen (MQTT 8883) kopplas bort och ansluter upprepade gånger — vanligtvis en svag nätväg eller en delvis blockerad port.',
+ fix: 'Kontrollera Wi-Fi-signalen vid skrivaren, föredra en trådbunden anslutning och se till att port 8883 är pålitligt åtkomlig.',
+ },
+ 'camera-connection-refused': {
+ name: 'Kameraström otillgänglig',
+ cause: 'Live-kameran kunde inte nås på port RTSPS 322. Porten är blockerad, eller kameran eller LAN-livevy är av på skrivaren.',
+ fix: 'Aktivera kameran och LAN-livevy på skrivaren, och se till att port 322 inte blockeras. Detta påverkar inte utskriften.',
+ },
+ 'database-locked': {
+ name: 'Databas-skrivkonflikt',
+ cause: 'SQLite-databasen får "databas är låst"-fel under belastning — vanligt när flera skrivare körs samtidigt.',
+ fix: 'Växla Bambuddy till en extern PostgreSQL-databas. Se PostgreSQL-guiden i dokumentationen.',
+ },
+ },
+ },
+
+ vpDiagnostic: {
+ title: 'Inställningskontroll — {{name}}',
+ runButton: 'Kör inställningskontroll',
+ running: 'Kör inställningskontroll...',
+ runFailed: 'Kunde inte köra inställningskontrollen: {{error}}',
+ retry: 'Kör igen',
+ overall: {
+ ok: 'Alla kontroller godkända — denna virtuella skrivare är korrekt konfigurerad.',
+ warnings: 'Den virtuella skrivaren bör fungera, men vissa saker behöver uppmärksamhet.',
+ problems: 'Hittade problem som förklarar varför slicern inte kan se eller använda denna virtuella skrivare.',
+ },
+ check: {
+ enabled: {
+ title: 'Virtuell skrivare aktiverad',
+ fail: 'Denna virtuella skrivare är avstängd. Växla på den för att göra den upptäckbar.',
+ },
+ running: {
+ title: 'Tjänster körs',
+ fail: 'Den virtuella skrivaren är aktiverad men dess tjänster körs inte. Kontrollera Bambuddy-loggen — en bind-IP-konflikt eller ett behörighetsfel stoppar vanligtvis dem.',
+ },
+ bind_interface: {
+ title: 'Bind nätverksgränssnitt',
+ fail: 'Bind-gränssnittet är inte inställt, eller existerar inte längre på denna värd. Välj ett aktuellt gränssnitt i rullisten Bind-gränssnitt.',
+ },
+ access_code: {
+ title: 'Åtkomstkod inställd',
+ fail: 'Ingen åtkomstkod är inställd. Slicern måste ges samma 8-teckens åtkomstkod som du ställt in här.',
+ },
+ target_printer: {
+ title: 'Målskrivare',
+ fail: 'Ingen målskrivare är vald. Proxyläge behöver en riktig skrivare att vidarebefordra till.',
+ warn: 'Målskrivaren är offline just nu — vidarebefordring kommer att återupptas när den ansluter igen.',
+ },
+ port_ftps: {
+ title: 'Filuppladdningstjänst (port {{port}})',
+ fail: 'Inget lyssnar på port {{port}} för bind-IP, så slicern kan inte ladda upp filer. En portkonflikt på detta gränssnitt är den vanliga orsaken.',
+ },
+ port_mqtt: {
+ title: 'Kontrolltjänst (port {{port}})',
+ fail: 'Inget lyssnar på port {{port}} för bind-IP, så slicern kan inte ansluta eller visa status.',
+ },
+ port_bind: {
+ title: 'Söktjänst (port {{port}})',
+ fail: 'Inget lyssnar på port {{port}} för bind-IP, så slicerns upptäcknings-handskak misslyckas.',
+ },
+ privileged_ports: {
+ title: 'Privilegerad portbindning',
+ fail: 'Port {{port}} är under 1024, och denna tjänst har inte behörighet att binda den — varför inget lyssnar ovan. Lägg till AmbientCapabilities=CAP_NET_BIND_SERVICE till /etc/systemd/system/bambuddy.service och starta om, eller kör "sudo setcap cap_net_bind_service=+ep $(readlink -f $(which python3))". För Docker, lägg till cap_add: [NET_BIND_SERVICE].',
+ },
+ certificate: {
+ title: 'TLS-certifikat',
+ pass: 'Certifikat redo. Se till att Bambuddy-CA-certifikatet (ovan) importeras till din slicerns betroddelagring.',
+ fail: 'TLS-certifikatet för denna virtuella skrivare saknas. Kontrollera att Bambuddy-datakatalogen är skrivbar.',
+ },
+ },
+ },
+
+ bugReport: {
+ title: 'Rapportera ett fel',
+ description: 'Beskrivning',
+ descriptionPlaceholder: 'Vad gick fel? Beskriv problemet...',
+ email: 'E-post (valfritt)',
+ emailPlaceholder: 'din@e-post.com',
+ emailPrivacy: 'Om den anges kommer din e-post att inkluderas i en ihopfälld sektion av GitHub-ärendet så att underhållaren kan följa upp.',
+ screenshot: 'Skärmdump',
+ uploadOrPaste: 'Ladda upp, klistra in eller dra en bild',
+ dataCollectedSummary: 'Vilken data inkluderas i rapporten?',
+ dataIncluded: 'Inkluderad:',
+ dataIncludedList: 'App-version, OS, arkitektur, Python-version, databasstatistik (endast räknare), skrivarmodeller, munstyckesantal, firmwareversioner, anslutningsstatus, integrationsstatus (Spoolman, MQTT, HA), icke-känsliga inställningar, nätverksgränssnittsantal, Docker-detaljer, beroendeversioner.',
+ dataNeverIncluded: 'Inkluderas aldrig:',
+ dataNeverIncludedList: 'Skrivarnamn, serienummer, åtkomstkoder, lösenord, IP-adresser, e-postadresser, API-nycklar, tokens, webhook-URL:er, värdnamn eller användarnamn.',
+ submit: 'Skicka in',
+ startLogging: 'Starta felsökningsloggning',
+ stepEnableLogging: 'Felsökningsloggning aktiverad',
+ stepReproduce: 'Återskapa problemet nu',
+ stepStopLogging: 'Stoppa och skicka in rapport',
+ stopAndSubmit: 'Stoppa och skicka in',
+ maxDuration: 'Stoppar automatiskt efter {{minutes}} min',
+ stoppingLogs: 'Samlar in loggar och skickar in...',
+ submitting: 'Skickar in felrapport...',
+ submittingStepConnection: 'Kör skrivaranslutningskontroller',
+ submittingStepVirtualPrinters: 'Kör inställningskontroller för virtuella skrivare',
+ submittingStepLogScan: 'Söker i senaste loggar efter kända problem',
+ submittingStepSubmit: 'Skickar in rapport till GitHub',
+ submitSuccess: 'Felrapport skickad in!',
+ submitFailed: 'Kunde inte skicka in felrapport',
+ diagnosticChecking: 'Kontrollerar skrivaranslutningar...',
+ diagnosticHealthy: 'Anslutningskontroll godkänd — inga problem hittade på dina skrivare.',
+ diagnosticSummary: '{{problems}} av {{total}} skrivare har anslutningsproblem',
+ diagnosticIntro: 'En eller flera skrivare har ett anslutningsproblem som kan orsaka ditt problem. Fäll ut en skrivare nedan för att se åtgärden — att lösa det kan lösa problemet utan en felrapport. Du kan fortfarande skicka in en rapport nedan.',
+ logHealthSummary: 'Kända problem hittade i dina loggar',
+ logHealthIntro: 'Senaste loggar matchar kända problem. Kontrollera åtgärderna nedan — att lösa dem kan lösa ditt problem utan en felrapport. Du kan fortfarande skicka in en rapport nedan.',
+ thankYou: 'Tack!',
+ submitted: 'Din felrapport har skickats in.',
+ viewIssue: 'Visa ärende',
+ closeKeepsRecording: 'Du kan stänga den här panelen medan du återskapar problemet – inspelningen fortsätter och om du öppnar den igen kommer du tillbaka hit.',
+ resumeRecording: 'Buggrapport spelar in — {{elapsed}}. Klicka för att avsluta.',
+ resumeReport: 'Återuppta rapportering',
+ unexpectedError: 'Ett oväntat fel uppstod',
+ },
+ failureDetection: {
+ title: 'AI-feldetektering',
+ description: 'Övervaka utskrifter med ett självhostat Obico ML-API och agera automatiskt på upptäckta fel.',
+ mlUrl: 'Obico ML-API-URL',
+ mlUrlHint: 'Bas-URL för din självhostade Obico ml_api-container (t.ex. http://192.168.1.10:3333).',
+ mlToken: 'ML-API-token (valfritt)',
+ mlTokenPlaceholder: 'Lämna tomt om servern körs utan ML_API_TOKEN',
+ mlTokenHint: 'Måste matcha ML_API_TOKEN-miljövariabeln för din Obico ml_api-container. Lämna tomt när containern körs utan en.',
+ test: 'Test',
+ testSuccess: 'ML-API åtkomlig och frisk.',
+ testSuccessTokenUnknown: 'ML-API åtkomlig och frisk. Token kunde inte kontrolleras.',
+ testFailed: 'Kunde inte nå ML-API:et.',
+ sensitivity: 'Känslighet',
+ sensitivityLow: 'Låg (färre falska larm)',
+ sensitivityMedium: 'Medium (balanserad)',
+ sensitivityHigh: 'Hög (upptäck tidigt, fler falska larm)',
+ sensitivityHint: 'Justerar konfidens-trösklarna som utlöser varningar och fel.',
+ action: 'Åtgärd vid upptäckt fel',
+ actionNotify: 'Notisera endast',
+ actionPause: 'Pausa utskrift',
+ actionPauseOff: 'Pausa och bryt ström',
+ pollInterval: 'Avläsningsintervall (sekunder)',
+ pollIntervalHint: 'Hur ofta varje skrivare ska kontrollera medan den skriver ut. Minimum 5s, maximum 120s.',
+ externalUrlMissing: 'Extern URL är inte inställd.',
+ externalUrlHint: 'ML-API:et hämtar kamerans ögonblicksbild via URL. Sätt den externa URL:en i Allmänna inställningar så att ML-API-containern kan nå Bambuddy.',
+ perPrinterTitle: 'Övervakade skrivare',
+ perPrinterHint: 'Välj vilka skrivare som övervakningstjänsten ska bevaka.',
+ monitorAll: 'Övervaka alla anslutna skrivare',
+ statusTitle: 'Status',
+ serviceRunning: 'Tjänst körs',
+ thresholds: 'Låga / Hög tröskelvärden',
+ activePrinters: 'Aktiva utskrifter',
+ noActivePrints: 'Inga utskrifter körs för närvarande.',
+ historyTitle: 'Senaste upptäckter',
+ noHistory: 'Inga upptäckter än.',
+ },
+
+ makerworld: {
+ title: 'MakerWorld',
+ description: 'Klistra in en MakerWorld-modell-URL för att importera och skriva ut den direkt från Bambuddy — utan att lämna för Bambu Handy-app.',
+ pasteUrlHeader: 'Importera från MakerWorld',
+ pasteUrlPlaceholder: 'https://makerworld.com/en/models/… eller klistra in valfri MakerWorld-länk',
+ resolveButton: 'Lös',
+ signInRequiredTitle: 'Bambu Cloud-inloggning krävs för nedladdning',
+ signInRequiredBody: 'Du kan bläddra bland modelldetaljer anonymt, men MakerWorld kräver ett Bambu Cloud-konto för att ladda ner 3MF-filer.',
+ signInExpiredTitle: 'Bambu Cloud-inloggning har löpt ut',
+ signInExpiredBody: 'Du är fortfarande inloggad på Bambuddy, men Bambu Lab har slutat acceptera den lagrade token, så nedladdningar kommer att misslyckas. Logga in på Bambu Cloud igen.',
+ openCloudSettings: 'Öppna molninställningar',
+ untitledModel: 'Namnlös modell',
+ byCreator: 'av {{name}}',
+ downloadsCount: '{{count}} nedladdningar',
+ licensePrefix: 'Licens',
+ alreadyImported: 'Redan importerad',
+ openOnMakerworld: 'Öppna på MakerWorld',
+ alreadyInLibrary: 'Denna modell finns redan i ditt bibliotek — hitta den i Filhanterare → MakerWorld',
+ importSuccess: 'Importerade {{filename}} — sparad i Filhanterare → MakerWorld',
+ platesHeader: 'Plattor ({{count}})',
+ plateDefaultName: 'Platta {{n}}',
+ materialCount: '{{count}} filament',
+ amsRequired: 'AMS krävs',
+ slicedFor: 'Slicad för {{printer}}',
+ alsoCompatible: 'Markerad som kompatibel med: {{printers}}',
+ importToLibrary: 'Spara',
+ sliceIn: 'Spara och slica i {{slicer}}',
+ disclaimer: 'MakerWorld-integration använder community-dokumenterade API-ändpunkter. Bambuddy är inte ansluten till eller godkänd av MakerWorld eller Bambu Lab.',
+ lastImportSuccess: 'Importerad till ditt bibliotek',
+ lastImportAlreadyInLibrary: 'Finns redan i ditt bibliotek',
+ viewInLibrary: 'Visa i Filhanterare',
+ openInBambuStudio: 'Öppna i Bambu Studio',
+ openInOrcaSlicer: 'Öppna i OrcaSlicer',
+ importTo: 'Importera till filhanterare',
+ recentImportsHeader: 'Senaste importen',
+ phaseResolving: 'Löser',
+ phaseDownloading: 'Laddar ner',
+ folderAuto: 'MakerWorld (standard)',
+ importAll: 'Importera alla',
+ importAllProgress: 'Importerar {{current}}/{{total}}',
+ openGallery: 'Öppna bildgalleri',
+ galleryPrev: 'Föregående bild',
+ galleryNext: 'Nästa bild',
+ deleteImport: 'Ta bort från bibliotek',
+ importDeleting: 'Tar bort...',
+ importDeleted: 'Borttagen från bibliotek',
+ confirmDelete: 'Ta bort {{filename}} från biblioteket? Detta tar bort den lokala filen men plattan kan importeras om från MakerWorld.',
+ errors: {
+ resolveFailed: 'Kunde inte lösa den MakerWorld-URL:en.',
+ downloadFailed: 'Kunde inte ladda ner. Försök igen.',
+ deleteFailed: 'Kunde inte ta bort filen från biblioteket.',
+ },
+ },
+ gcodeViewer: {
+ filamentSlot: 'Filament {{n}}',
+ loading: 'Läser verktygssökväg…',
+ loadFailed: 'Kunde inte ladda G-koden för denna fil.',
+ showTravel: 'Rörelse',
+ topLayer: 'Topplager',
+ bottomLayer: 'Bottenlager',
+ noSource: 'Ingen fil gavs för förhandsgranskning.',
+ view: {
+ filament: 'Filament',
+ feature: 'Särdrag',
+ height: 'Höjd',
+ width: 'Bredd',
+ },
+ feature: {
+ wall: 'Väggar',
+ sparseInfill: 'Gles utfyllnad',
+ solidInfill: 'Solid utfyllnad',
+ bridge: 'Brygga / överhäng',
+ support: 'Stöd',
+ skirt: 'Kant / brätte',
+ gapFill: 'Mellanrumsifyllnad',
+ ironing: 'Strykning',
+ primeTower: 'Primtorn',
+ },
+ back: 'Tillbaka',
+ backToArchives: 'Tillbaka till utskriftsarkiv',
+ backToFiles: 'Tillbaka till Filhanterare',
+ plates: 'Plattor',
+ plateN: 'Platta {{n}}',
+ },
+ libraryTrash: {
+ title: 'Papperskorgen',
+ headerButton: 'Papperskorgen',
+ headerTooltip: 'Visa filer som flyttats till papperskorgen',
+ backToFiles: 'Tillbaka till Filhanterare',
+ subtitleAdmin: 'Borttagna filer stannar här i {{days}} dagar, sedan raderas de automatiskt. Denna vy visar filer i papperskorgen för alla användare.',
+ subtitleUser: 'Borttagna filer stannar här i {{days}} dagar, sedan raderas de automatiskt.',
+ loading: 'Laddar papperskorgen…',
+ loadError: 'Kunde inte ladda papperskorgen.',
+ empty: 'Papperskorgen är tom.',
+ summary: '{{count}} filer · {{size}}',
+ emptyTrash: 'Töm papperskorgen',
+ restore: 'Återställ',
+ purgeNow: 'Ta bort nu',
+ autoPurgeIn: 'Raderas automatiskt om {{when}}',
+ days: 'dagar',
+ retentionLabel: 'Automatisk radering efter',
+ selectAll: 'Markera alla',
+ selectOne: 'Markera {{filename}}',
+ selectionCount: '{{count}} markerade',
+ bulkRestore: 'Återställ markerade',
+ bulkPurge: 'Ta bort markerade',
+ col: {
+ filename: 'Fil',
+ folder: 'Mapp',
+ size: 'Storlek',
+ deleted: 'Flyttad till papperskorgen',
+ autoPurge: 'Automatisk radering',
+ owner: 'Ägare',
+ actions: 'Åtgärder',
+ },
+ confirm: {
+ purgeTitle: 'Ta bort permanent?',
+ purgeBody: '{{filename}} kommer att tas bort från disken och kan inte återställas.',
+ emptyTitle: 'Töm papperskorgen?',
+ emptyBody: 'Alla {{count}} filer kommer att tas bort från disken. Detta kan inte ångras.',
+ bulkPurgeTitle: 'Ta bort markerade filer permanent?',
+ bulkPurgeBody: 'De {{count}} markerade filerna kommer att tas bort från disken och kan inte återställas.',
+ cta: 'Ta bort permanent',
+ },
+ toast: {
+ restored: 'Fil återställd.',
+ restoreFailed: 'Kunde inte återställa filen.',
+ purged: 'Fil borttagen permanent.',
+ purgeFailed: 'Kunde inte ta bort filen.',
+ emptied: 'Tog bort {{count}} fil(er) från papperskorgen.',
+ emptyFailed: 'Kunde inte tömma papperskorgen.',
+ retentionSaved: 'Automatisk radering inställd på {{days}} dagar.',
+ retentionFailed: 'Kunde inte spara inställningen för automatisk radering.',
+ bulkRestored: 'Återställde {{count}} fil(er).',
+ bulkPurged: 'Tog bort {{count}} fil(er).',
+ },
+ },
+ libraryPurge: {
+ title: 'Rensa gamla filer',
+ headerButton: 'Rensa gamla',
+ headerTooltip: 'Massflytta gamla filer till papperskorgen',
+ description: 'Rensa ut gamla filer från ditt bibliotek i ett svep. Filer med utskriftshistorik åldras efter deras senaste utskriftsdatum; filer som aldrig skrivits ut åldras efter deras uppladdningsdatum.',
+ ageLabel: 'Flytta filer äldre än',
+ days: 'dagar',
+ includeNeverPrinted: 'Inkludera filer som aldrig skrivits ut',
+ effectsTitle: 'Vad händer när du klickar på Rensa',
+ effect1: 'Matchande filer flyttas till Papperskorgen — de raderas inte från disken än.',
+ effect2: 'Du kan återställa dem från Papperskorgen när som helst tills behållningsfönstret löper ut.',
+ effect3: 'Efter behållningstiden rensar papperskorgen dem permanent från disken.',
+ effect4: 'Filer i externa (länkade) mappar hoppas över — Bambuddy raderar aldrig byte som den inte äger.',
+ previewLoading: 'Kontrollerar hur många filer som matchar…',
+ previewFailed: 'Kunde inte förhandsgranska rensningen.',
+ previewSummary: '{{count}} filer · {{size}} kommer att flyttas till papperskorgen',
+ andMore: '…och {{count}} till',
+ warning: 'Filer i papperskorgen räknas fortfarande mot lagringen tills behållningsfönstret löper ut. Töm Papperskorgen därefter för att frigöra diskutrymme omedelbart.',
+ confirmCta: 'Flytta {{count}} till papperskorgen',
+ purging: 'Flyttar till papperskorgen…',
+ toast: {
+ success: 'Flyttade {{count}} fil(er) till papperskorgen.',
+ failed: 'Kunde inte rensa filer.',
+ },
+ },
+ libraryAutoPurge: {
+ enableLabel: 'Auto-rensa gamla filer',
+ enableDescription: 'Kör den administrativa rensningen en gång per dag. Filer går till Papperskorgen först — de raderas inte omedelbart.',
+ ageLabel: 'Auto-rensa filer äldre än',
+ ageDescription: 'Minimum 7 dagar, maximum 10 år. Använder samma åldersregel som den manuella Rensa-knappen.',
+ days: 'dagar',
+ includeNeverPrinted: 'Inkludera filer som aldrig skrivits ut',
+ saveFailed: 'Kunde inte spara inställningar för auto-rensning.',
+ },
+ archivePurge: {
+ headerButton: 'Rensa gamla',
+ headerTooltip: 'Massradera gamla arkiv',
+ title: 'Rensa gamla arkiv',
+ description: 'Rensa ut gammal utskriftshistorik. Varje arkiv åldras efter dess senaste utskrifts slutförande — omutskrift av ett arkiv uppdaterar dess ålder, så aktivt arbete raderas aldrig.',
+ ageLabel: 'Radera arkiv som inte skrivits ut på',
+ days: 'dagar',
+ effectsTitle: 'Vad händer när du klickar på Rensa',
+ effect1: 'Varje matchande arkiv döljs från listorna och dess filer tas bort från disken (3MF, minibild, timelapse, käll-3MF, F3D-designfil, foton).',
+ effect2: 'Arkivraden förblir i databasen så att Snabbstatistik behåller filament-, tid-, kostnads- och energibidraget — samma som standard för radering av enskilt arkiv.',
+ effect3: 'Kryssa i "Ta också bort från statistik" nedan för att ta bort Snabbstatistik-bidraget också (matchar alternativet för radering av enskilt arkiv). Den vägen är oåterkallelig.',
+ effect4: 'Omutskrift av ett arkiv uppdaterar dess åldersklocka, så arkiv som du fortfarande använder är säkra.',
+ purgeStatsLabel: 'Ta också bort från statistik',
+ purgeStatsHint: 'Tar bort de matchande arkiven från Snabbstatistik (filament, tid, kostnad, energi). Utan detta behåller Snabbstatistik varje bidrag och endast filerna lämnar disken.',
+ previewLoading: 'Kontrollerar hur många arkiv som matchar…',
+ previewFailed: 'Kunde inte förhandsgranska rensningen.',
+ previewSummary: '{{count}} arkiv · {{size}} kommer att tas bort',
+ andMore: '…och {{count}} till',
+ warning: 'Filer tas bort från disken och kan inte återställas. Ladda ner eller markera som favorit allt du vill behålla innan du fortsätter.',
+ confirmCta: 'Ta bort {{count}} arkiv',
+ purging: 'Tar bort…',
+ toast: {
+ success: 'Tog bort {{count}} arkiv.',
+ failed: 'Kunde inte rensa arkiv.',
+ },
+ },
+ archiveAutoPurge: {
+ enableLabel: 'Auto-rensa gamla arkiv',
+ enableDescription: 'En gång per dag döljs arkiv från listorna och deras filer tas bort från disken när de inte har skrivits ut inom tröskelvärdet. Omutskrift av ett arkiv återställer klockan.',
+ ageLabel: 'Auto-radera arkiv som inte skrivits ut på',
+ ageDescription: 'Minimum 7 dagar, maximum 10 år. Baserat på det senaste utskrifts slutförandet — omutskrift av ett arkiv uppdaterar dess ålder. Tar bort 3MF, minibild, timelapse, käll-3MF, F3D och foton.',
+ days: 'dagar',
+ purgeStatsLabel: 'Ta också bort från statistik',
+ purgeStatsDescription: 'När aktiverat tar den dagliga rensaren också bort varje rensat arkiv från Snabbstatistik (filament, tid, kostnad, energi). Standard av — Snabbstatistik behåller bidraget, endast filerna lämnar disken.',
+ runNow: 'Rensa arkiv nu',
+ saveFailed: 'Kunde inte spara inställningar för auto-rensning.',
+ },
+ cameraTokens: {
+ scope: {
+ camera_stream: 'Kameraström',
+ camwall: 'Kameravägg',
+ overlay: 'Strömningsöverlägg',
+ },
+ title: 'Kamera-API-tokens',
+ navTitle: 'Kamera-API-tokens',
+ description:
+ 'Långlivade tokens för att bädda in kameraströmmen i Home Assistant, Frigate, kiosker eller något annat verktyg som behöver en stabil URL. Välj omfång när du skapar en; en token kan återkallas när som helst.',
+ loading: 'Laddar…',
+ confirmRevoke: {
+ title: 'Återkalla denna token?',
+ body: 'All enheter som använder "{{name}}" kommer att förlora åtkomsten omedelbart. Detta kan inte ångras.',
+ cancel: 'Avbryt',
+ confirm: 'Återkalla',
+ },
+ create: {
+ scopeLabel: 'Omfång',
+ hintCameraStream:
+ 'En kameraström-token kan endast hämta kameraströmmar och ögonblicksbilder. Använd den för Home Assistant, Frigate eller något som bäddar in en enda kamera.',
+ hintCamWall:
+ 'En Kameravägg-token öppnar /camwall på en skärm utan inloggning. Den kan se alla skrivares namn och status, och deras kameraströmmar. Den kan inte se filnamn, adresser eller åtkomstkoder.',
+ hintOverlay:
+ 'En Strömningsöverlägg-token öppnar /overlay/{printerId} på en skärm utan inloggning — för OBS eller någon livestream. Den kan se en skrivares kameraström plus dess live utskriftsstatus, inklusive filnamnet som visas på skärmen. Den kan inte se adresser eller åtkomstkoder.',
+ title: 'Skapa ny token',
+ nameLabel: 'Token-namn',
+ namePlaceholder: 't.ex. Home Assistant',
+ daysLabel: 'Dagar till utgång',
+ submit: 'Skapa',
+ hint:
+ 'Maximal livstid är 365 dagar. Token-värdet visas endast en gång vid skapande — kopiera det nu.',
+ },
+ created: {
+ camWallUrlTitle: 'Kameravägg-URL för denna display',
+ camWallUrlHint:
+ 'Öppna detta på skärmen. Vem som helst som kan läsa URL:en kan titta på väggen, så behandla den som en nyckel — återkalla token för att koppla bort display.',
+ overlayUrlTitle: 'Överläggs-URL för OBS',
+ overlayUrlHint:
+ 'Lägg till detta som en Browser Source i OBS. Ändra /overlay/1 numret till din skrivares nummer (från dess URL på Skrivarsidan). Vem som helst som kan läsa URL:en kan titta på strömmen, så behandla den som en nyckel — återkalla token för att koppla bort den.',
+ title: 'Token skapad — kopiera den nu',
+ warning:
+ 'Detta är det enda tillfället denna token kommer att visas. När du stänger denna dialogruta kan du aldrig visa den igen.',
+ copy: 'Kopiera',
+ dismiss: 'Jag har sparat den',
+ },
+ list: {
+ scope: 'Omfång',
+ myTitle: 'Mina tokens',
+ allTitle: 'Alla användare (admin-vy)',
+ empty: 'Inga tokens än.',
+ name: 'Namn',
+ owner: 'Ägare',
+ prefix: 'Prefix',
+ created: 'Skapad',
+ expires: 'Utgår',
+ lastUsed: 'Senast använd',
+ revoke: 'Återkalla',
+ expired: 'Utgången',
+ },
+ toast: {
+ created: 'Token skapad',
+ createFailed: 'Kunde inte skapa token',
+ revoked: 'Token återkallad',
+ revokeFailed: 'Kunde inte återkalla token',
+ loadFailed: 'Kunde inte ladda tokens',
+ copied: 'Kopierad till urklipp',
+ copyFailed: 'Kunde inte kopiera — markera och kopiera manuellt',
+ },
+ },
+ // Forecast & Inventory Intelligence
+ forecast: {
+ title: 'Prognos',
+ noSpools: 'Inga aktiva rullar hittade. Lägg till rullar till din inventering för att se prognosdata.',
+ noUsageData: 'Inga användningsdata tillgängliga — kan inte projicera lagertidslinje.',
+ // Table headers
+ sku: 'SKU',
+ material: 'Material',
+ stock: 'Lager',
+ dailyRate: 'Förbrukning',
+ daysLeft: 'Dagar kvar',
+ emptyBy: 'Tomt den',
+ reorderBy: 'Beställ senast',
+ actions: 'Åtgärder',
+ // Rate tier badges
+ trend: 'Trend',
+ estimated: 'Uppskattad',
+ noData: 'Ingen data',
+ // Timeframe
+ timeframe: 'Tidsram',
+ // Chart
+ chartTitle: 'Projicerat lager — Topp 5 material',
+ dashedLinesROP: 'Streckade linjer = beställningspunkter',
+ stockLevel: 'Lagernivå',
+ reorderPoint: 'Beställningspunkt',
+ safetyMargin: 'Säkerhetsmarginal',
+ // Legend labels
+ trendLegend: 'Trend (historikbaserad, 95% servicenivå)',
+ estimatedLegend: 'Uppskattad (viktskillnad)',
+ noDataLegend: 'Ingen data',
+ ropLabel: 'ROP',
+ ssLabel: 'SS',
+ safetyStockLegend: 'Reservlager',
+ stockArrivalLegend: 'Lagerankomst',
+ stockoutLegend: 'Lagerslut',
+ // Alerts toolbar
+ alertCount_one: '{{count}} varning',
+ alertCount_other: '{{count}} varningar',
+ order: 'Beställ',
+ // Settings
+ save: 'Spara',
+ cancel: 'Avbryt',
+ settingsSaved: 'Inställningar sparade',
+ failedSaveSettings: 'Kunde inte spara inställningar',
+ globalLeadTimeSaved: 'Global ledtid sparad',
+ globalLeadTime: 'Global ledtid',
+ globalLeadTimeHint: 'Global ledtidsbas — används i beräkning av beställningspunkt för alla SKU:er',
+ skuLeadTimeOverride: 'Åsidosätt ledtid',
+ skuLeadTimeHint: '0 = använd global ledtid. Sätt >0 för att åsidosätta för denna SKU.',
+ safetyMarginLabel: 'Säkerhetsmarginal',
+ effectiveLeadTime: 'Effektiv ledtid',
+ effectiveLeadTimeHint: 'max(global {{global}}d, SKU {{sku}}d)',
+ reorderPointHint: 'd̄ × LT + säkerhetsmarginal — beställ när lagret når denna nivå',
+ safetyMarginHint: 'Statistisk säkerhetslager (z=1,65 × σ × √LT) + användardefinierad buffert',
+ safetyMarginHintDays: 'Buffert som läggs till på den statistiska säkerhetslagret.{{approx}}',
+ safetyMarginHintDaysApprox: ' ≈ {{g}}g vid aktuell förbrukning.',
+ safetyMarginHintG: 'Fast viktbuffert som läggs till på den statistiska säkerhetslagret.{{approx}}',
+ safetyMarginHintGApprox: ' ≈ {{days}}d vid aktuell förbrukning.',
+ individualSpools: 'Enskilda rullar',
+ labelWeight: 'Etikett',
+ spoolCount_one: '{{count}} rulle',
+ spoolCount_other: '{{count}} rullar',
+ // Alerts
+ stockBreakRisk: 'Risk för lagerslut',
+ stockBreakDetail: '{{days}} dagar kvar, ledtid {{lt}} dagar.',
+ stockBreakBefore: 'Lagerslut före påfyllning',
+ reorderNow: 'Beställ nu',
+ reorderTriggerPassed: 'Utlösningsdatum {{date}} har passerat.',
+ // Shopping list
+ shoppingList: 'Inköpslista',
+ shoppingListItems_one: '({{count}} artikel)',
+ shoppingListItems_other: '({{count}} artiklar)',
+ shoppingListEmpty: 'Inköpslistan är tom. Klicka på varukorgsikonen på valfri rad för att lägga till artiklar.',
+ addToCart: 'Lägg till i inköpslista',
+ alertsSnoozed: 'Tysta varningar för denna SKU',
+ alertsEnabled: 'Aktivera varningar för denna SKU',
+ addedToCart: 'Lagt till i inköpslista',
+ failedAddItem: 'Kunde inte lägga till artikel',
+ listView: 'Lista',
+ logisticsView: 'Logistik',
+ qty: 'Antal',
+ weight: 'Vikt',
+ leadTime: 'Ledtid',
+ expectedRestock: 'Förväntad påfyllning',
+ status: 'Status',
+ note: 'Anteckning',
+ pending: 'Väntande',
+ purchased: 'Inköpt',
+ received: 'Mottagen',
+ markPurchased: 'Markera som inköpt',
+ markReceived: 'Markera som mottagen — lägger till rullar i Lagerinventering',
+ resetToPending: 'Återställ till väntande',
+ remove: 'Ta bort',
+ clearAll: 'Rensa alla',
+ downloadCsv: 'CSV',
+ // Add to cart modal
+ addToCartTitle: 'Lägg till i inköpslista',
+ byQuantity: 'Efter kvantitet',
+ byDuration: 'Efter varaktighet',
+ numberOfSpools: 'Antal rullar',
+ lastHowManyDays: 'Bör räcka hur många dagar?',
+ noUsageQty: 'Ingen användningsdata — kvantitet inställd på 1.',
+ noteOptional: 'Anteckning (valfritt)',
+ notePlaceholder: 't.ex. för projekt X, brådskande…',
+ addNSpools_one: 'Lägg till {{count}} rulle',
+ addNSpools_other: 'Lägg till {{count}} rullar',
+ // Cart logistics
+ onArrival: 'Vid ankomst',
+ stockBreakIn: 'Lagerslut om {{days}} dagar.',
+ stockRunsOutBefore: 'Lagret tar slut innan ledtiden {{lt}} dagar har passerat.',
+ atRate: 'Vid {{rate}}g/dag behöver du',
+ moreSpools_one: '{{count}} rulle till',
+ moreSpools_other: '{{count}} rullar till',
+ bridgeGap: 'för att överbrygga gapet.',
+ // Permissions
+ noReadAccess: 'Du har inte behörighet att visa lagerprognoser.',
+ noWriteAccess: 'Du har inte behörighet att modifiera prognosinställningar.',
+ },
+};
diff --git a/frontend/src/i18n/locales/tr.ts b/frontend/src/i18n/locales/tr.ts
index e9440a028..adc133a82 100644
--- a/frontend/src/i18n/locales/tr.ts
+++ b/frontend/src/i18n/locales/tr.ts
@@ -534,6 +534,8 @@ export default {
step: 'Adım (mm)',
up: 'Plakayı yukarı hareket ettir',
down: 'Plakayı aşağı hareket ettir',
+ toolheadUp: 'Baskı kafasını yukarı hareket ettir',
+ toolheadDown: 'Baskı kafasını aşağı hareket ettir',
disabledWhilePrinting: 'Baskı sırasında devre dışı',
notHomedTitle: 'Yazıcı sıfırlanmamış',
notHomedMessage: 'Yazıcı son baskıdan bu yana sıfırlanmadı. Güvenli konumlandırma için önce otomatik sıfırlamayı çalıştırın (kafayı park eder, ardından X, Y ve Z sıfırlanır) veya yine de hareket ettirin — yazılım son durakları atlanır.',
@@ -901,11 +903,15 @@ export default {
docsLink: 'Kurulum adımı 4\'ü görüntüle',
docsLinkInternalStorage: 'Bu neden oluyor',
titleInternalStorage: 'Bazı son baskılar yazıcının dahili depolamasında kaldı',
- bodyInternalStorage: 'Bambu Studio dilimlenmiş dosyayı karta değil yazıcının dahili depolamasına yazdı, bu yüzden Bambuddy\'nin FTP üzerinden okuyacağı bir şey yoktu. H2 serisi ve P2S\'de «Yazdır» düğmesi bunu her zaman yapar; seçim yalnızca «Gönder» ile mümkündür ve orada da varsayılan «Önbellek»tir. Bu baskılar adları ve süreleriyle yine arşivlenir, yalnızca küçük resim ve dilimleyici meta verileri olmadan. Eksiksiz arşivler için baskıyı Bambuddy üzerinden başlatın ya da OrcaSlicer ile dilimleyin; Bambu Studio kullanacaksanız «Gönder» ile «Harici» seçip baskıyı sonrasında başlatın. Hepsi yazıcıda kart ya da bellek gerektirir.',
+ bodyInternalStorage: 'Bambu Studio dilimlenmiş dosyayı karta değil yazıcının dahili depolamasına yazdı, bu yüzden Bambuddy\'nin FTP üzerinden okuyacağı bir şey yoktu. H2 serisi, P2S ve X2D\'de «Yazdır» düğmesi bunu her zaman yapar; seçim yalnızca «Gönder» ile mümkündür ve orada da varsayılan «Önbellek»tir. Bu baskılar adları ve süreleriyle yine arşivlenir, yalnızca küçük resim ve dilimleyici meta verileri olmadan. Eksiksiz arşivler için baskıyı Bambuddy üzerinden başlatın ya da OrcaSlicer ile dilimleyin; Bambu Studio kullanacaksanız «Gönder» ile «Harici» seçip baskıyı sonrasında başlatın. Hepsi yazıcıda kart ya da bellek gerektirir.',
titleNoExternalStorage: 'Bazı son baskılar arşivlenemedi — yazıcıda depolama yok',
bodyNoExternalStorage: 'Yazıcı yuvasında kart veya bellek bildirmiyor, bu yüzden dilimlenmiş dosyanın ineceği bir yer ve Bambuddy\'nin okuyacağı bir şey yoktu. Bir tane takın, sonraki baskı eksiksiz arşivlenecek.',
titleInternalHistory: 'Bazı son baskılar yazıcıda zaten bulunan bir dosyadan başlatıldı',
bodyInternalHistory: 'Bu baskılar yazıcının kendi kitaplığından çalıştı — ekranından yeniden baskı, Handy üzerinden başlatma ya da daha önce gönderilip sonra basılan bir dosya. Bambuddy baskı dosyalarını FTP üzerinden okur, FTP ise yalnızca kartı veya belleği sunar; yazıcı bu kitaplığı FTP\'nin ulaşamadığı bir alanda tutar, dolayısıyla okunacak bir 3MF yoktu. Hiçbir dilimleyici ayarı bunu değiştirmez, çünkü bu baskılar için hiçbir şey gönderilmedi. Adları ve süreleriyle arşivlenmeye devam ederler ve "Arşivi Düzenle" ile kullanılan filamenti elle girebilirsiniz. Eksiksiz bir arşiv için baskıyı Bambuddy\'den ya da dilimleyicinizden başlatın.',
+ titleFtpsCooloff: 'Bazı son baskılar arşivlenemedi — yazıcı dosya bağlantısını reddetti',
+ bodyFtpsCooloff: 'Bambuddy yazıcının dosya aktarım portunu (FTPS 990) açtı ve yazıcı TLS olmayan bir şeyle yanıt verdi, bu yüzden ondan hiçbir şey okunamadı. O baskılar adları ve süreleriyle yine arşivlenir, yalnızca küçük resim ve dilimleyici meta verileri olmadan. Bu bir dilimleyici ayarı değil ve sizin değiştirdiğiniz bir şey de değil — aynı modeller ve aynı ürün yazılımları başka kurulumlarda sorunsuz çalışıyor ve etkilenen bir yazıcı genellikle bir süre sonra kendiliğinden yeniden çalışıyor. Böyle bir hatadan sonra Bambuddy o yazıcıya yapılan aktarımları beş dakika duraklatır ve duraklama bitince dosyayı yeniden almaya gelir; kısa bir kesinti böylece kendiliğinden düzelir, hâlâ boş duran bir kart ise reddin yeniden denemeden daha uzun sürdüğü anlamına gelir. Buna neyin yol açtığı henüz bilinmiyor. Tekrarlıyorsa Sistem sayfasından hata ayıklama günlüğünü açın ve bildiriminize bir destek paketi ekleyin.',
+ titleFtpTransferFailed: 'Bazı son baskılar arşivlenemedi — dosya aktarımı süreye sığmadı',
+ bodyFtpTransferFailed: 'Dilimlenmiş dosya yazıcının kartındaydı ve yazıcı dosyayı gerçekten sunuyordu, ancak aktarım izin verilen sürede tamamlanmadı. Baskı başlarken yazıcı aynı anda kamerasıyla, durum mesajlarıyla ve iş yüklemesiyle de ilgilenir; büyük bir 3MF bunların arasından her zaman geçemez. Bu baskılar adı ve süreleriyle hâlâ arşivde, yalnızca küçük resim ve dilimleyici verileri eksik. Bu bir dilimleyici ayarı değil ve sizin değiştirdiğiniz bir şeyden kaynaklanmıyor. Bambuddy sonraki on dakika içinde dosyayı üç kez daha almaya gider, bu yüzden çoğu kendiliğinden tamamlanır; hâlâ boş duran bir kart, o denemelerin de süreye sığmadığı anlamına gelir. Sık sık oluyorsa Ayarlar > Ağ > FTP Yeniden Deneme altındaki Bağlantı Zaman Aşımı değerini artırın.',
dismissLabel: 'Bu bildirimi kapat',
},
searchPlaceholder: 'Arşivlerde ara...',
@@ -1487,7 +1493,7 @@ export default {
// Zaman
time: {
etaIfStartedNow: 'Bu iş şimdi başlatılırsa tamamlanma saati',
- asap: 'ASAP',
+ whenFree: 'Bir yazıcı boşaldığında',
overdue: 'Gecikmiş',
now: 'Şimdi',
lessThanMinute: 'Bir dakikadan az',
@@ -1511,6 +1517,7 @@ export default {
staged: 'Hazırlandı (manuel başlatma)',
autoPowerOff: 'Baskı sonrası otomatik kapanma',
requirePrevious: 'Önceki başarı gerekli',
+ gcodeInjection: 'G-kod enjekte et',
printOptions: 'Baskı Seçenekleri',
bedLevelling: 'Tabla seviyelendirme',
flowCalibration: 'Akış kalibrasyonu',
@@ -2334,7 +2341,7 @@ export default {
preheatEnabled: 'Ön ısıtma & soak\'u etkinleştir',
preheatEnabledDesc: 'Kapalıyken kuyruktaki baskılar hemen başlar. Her kuyruk öğesi baskı bazında geçersiz kılabilir.',
preheatFilamentTargetsLabel: 'Filament başına oda hedefi (°C)',
- preheatFilamentTargetsHint: 'Bambuddy yüklü AMS yuvaları arasındaki en yüksek hedefi seçer; yalnız PLA baskıları 0 türeterek oda aşamasını otomatik atlar.',
+ preheatFilamentTargetsHint: 'Bambuddy yüklü AMS yuvaları arasındaki en yüksek hedefi seçer. Tüm yuvaları 0 türeten baskılar — PLA, PETG, TPU, PVA — ön ısıtmayı tamamen atlar ve hemen başlar.',
preheatFilamentTargetsReset: 'Varsayılanlara sıfırla',
preheatFilamentTargetsDefaultRow: 'Diğer / eşlenmemiş',
preheatMaxWait: 'Maks. bekleme (saniye)',
@@ -2351,6 +2358,7 @@ export default {
calibrationMode_on: 'Açık',
calibrationMode_auto: 'Otomatik',
preheatTargetOverride: 'Oda hedefini geçersiz kıl (°C, boş = filament varsayılanı)',
+ preheatTargetOverrideHelp: '0, oda olmadan tablayı ısıtır ve bekleme süresini uygular. Boş bırakılırsa oda gerektirmeyen bir baskı ön ısıtmayı tamamen atlar.',
plateClear: 'Plaka Temizleme Onayı',
requirePlateClear: 'Plaka temizleme onayı gerektir',
requirePlateClearDescription: 'Etkinleştirildiğinde, planlayıcı bitmiş işleri olan yazıcılarda kuyruktaki baskıları başlatmadan önce yazıcı başına plaka temizleme onayını bekler. Bunu devre dışı bırakmak ayrıca plaka durum rozetini ve yazıcı kartlarındaki "Plakayı temizlendi olarak işaretle" düğmesini gizler.',
@@ -4894,7 +4902,7 @@ export default {
linkToSpool: 'Makaraya Bağla',
tagLinked: 'Etiket makaraya bağlandı',
tagLinkFailed: 'Etiket bağlanamadı',
- tagAlreadyLinked: 'Etiket zaten başka bir makaraya bağlı',
+ tagAlreadyLinked: 'Etiket zaten #{{id}} numaralı makaraya bağlı',
unknownTag: 'Bilinmeyen RFID etiketi algılandı',
usageHistory: 'Kullanım Geçmişi',
noUsageHistory: 'Henüz kullanım kaydedilmedi',
@@ -5375,7 +5383,7 @@ export default {
project: 'Proje',
noProject: 'Proje yok',
itemsPrinted: 'Yazdırılan Öğeler',
- itemsPrintedHelp: 'Bu baskı işinde üretilen öğe sayısı',
+ itemsPrintedHelp: 'Bu baskı işinde üretilen öğe sayısı. Tüm tabla bozulduysa 0 girin.',
filamentUsed: 'Kullanılan filament (g)',
filamentUsedPlaceholder: 'ör. 46.16',
filamentUsedHelp: 'Bir baskı 3MF dosyası olmadan arşivlendiyse elle girin; böylece filament toplamlarına yine de dahil olur. 3MF dosyası olan bir arşiv yeniden tarandığında bu değer dosyadan yeniden okunur.',
@@ -6738,6 +6746,7 @@ export default {
scaleWeight: 'Tartı',
netWeight: 'Net',
lastUsed: 'Son kullanım',
+ unknownColor: 'Bilinmeyen renk',
},
ams: {
noData: 'AMS algılanmadı',
@@ -6910,7 +6919,7 @@ export default {
skip: 'Kontrol edilmedi — etkin bir MQTT bağlantısı gerekli. Bu ayarın yalnızca dilimleyicide bulunduğu eski dilimleyicilerde yazıcı bunu bildirmez, bu nedenle seçenek kapalı olsa bile bu kontrol geçer — kurulum adımı 4\'ü manuel olarak doğrulayın.',
skip_unsupported_model: 'Bu modelde SD yuvası var ancak seçeneği açmanın bir yolu yok — mevcut P1 serisi bellenim, Bambu Studio\'da bu anahtarı göstermiyor ve yazıcının ekranı yok. Burada düzeltilecek bir şey yok; Bambu Lab bellenim desteği ekleyene kadar arşivlenen baskılarda küçük resimler ve dilimleyici meta verileri eksik olabilir.',
fail_no_media: 'Seçenek açık, ancak yazıcı yuvasında kart veya bellek bildirmiyor, dolayısıyla gönderilen dosyaların gideceği bir yer yok. Bir tane takıp yeniden yazdırın — o zamana kadar arşivlenen her baskıda küçük resim ve dilimleyici meta verileri eksik olacak.',
- warn_internal_storage: 'Seçenek açık ve depolama takılı, ancak son baskı yine de Bambuddy\'nin okuyamadığı dahili depolamaya gitti. H2 serisi ve P2S\'de Bambu Studio\'nun «Yazdır» düğmesi bu ayardan bağımsız olarak her zaman oraya gönderir. Baskılar adları ve süreleriyle arşivlenir, ancak küçük resim ve dilimleyici meta verileri olmadan. Eksiksiz arşivler için baskıları Bambuddy üzerinden başlatın, OrcaSlicer ile dilimleyin ya da Bambu Studio\'da «Gönder» ile «Harici» seçip baskıyı sonrasında başlatın.',
+ warn_internal_storage: 'Seçenek açık ve depolama takılı, ancak son baskı yine de Bambuddy\'nin okuyamadığı dahili depolamaya gitti. H2 serisi, P2S ve X2D\'de Bambu Studio\'nun «Yazdır» düğmesi bu ayardan bağımsız olarak her zaman oraya gönderir. Baskılar adları ve süreleriyle arşivlenir, ancak küçük resim ve dilimleyici meta verileri olmadan. Eksiksiz arşivler için baskıları Bambuddy üzerinden başlatın, OrcaSlicer ile dilimleyin ya da Bambu Studio\'da «Gönder» ile «Harici» seçip baskıyı sonrasında başlatın.',
warn_internal_history: 'Seçenek açık ve depolama takılı, ancak son baskı yazıcıda zaten bulunan bir dosyadan çalıştı — ekranından yeniden baskı, Handy üzerinden başlatma ya da daha önce gönderilmiş bir dosya. Bu kitaplık, Bambuddy\'nin okuyamadığı dahili depolamada durur ve o baskı için hiçbir şey gönderilmediğinden hiçbir ayar bunu değiştirmez. Adı ve süreleriyle yine arşivlenir, ama küçük resim ve dilimleyici meta verileri olmadan. Eksiksiz arşivler için baskıları Bambuddy\'den başlatın ya da dilimleyiciden gönderin.',
},
port_rtsps: {
@@ -6918,11 +6927,20 @@ export default {
pass: 'Erişilebilir — kamera akışı çalışacak.',
warn: 'Port {{port}} erişilemez. Canlı kamera görünümü çalışmayacak. Bu, baskıyı etkilemez.',
},
+ macos_local_network: {
+ title: 'macOS Yerel Ağ izni',
+ pass: 'macOS, Bambuddy’nin yerel ağa erişmesine izin veriyor.',
+ warn_unsigned: 'Bambuddy’yi çalıştıran Python’un kod imzası yok; bu yüzden macOS Yerel Ağ iznini bağlayacak bir kimlik bulamıyor ve yazıcıya giden tüm bağlantıları hata vermeden, sormadan sessizce düşürüyor. İmzalamayı yapan Bambuddy güncelleme betiğini (install/update_macos.sh) çalıştırın, ardından Bambuddy’yi yeniden başlatın. Yorumlayıcı: {{executable}}',
+ warn_permission: 'Yazıcı açıksa ve bu adresten erişilebiliyorsa Sistem Ayarları > Gizlilik ve Güvenlik > Yerel Ağ bölümünü açıp Bambuddy’nin Python’unun etkin olduğundan emin olun. Etkin değilse macOS yerel bağlantıları sessizce düşürür; ayrıca Python güncellemesi eski izni geride bırakabilir.',
+ },
network_mode: {
- title: 'Docker ağ modu',
- pass: 'Ana bilgisayar ağ modunda çalışıyor.',
- warn: 'Bambuddy, Docker köprü ağı kullanılarak çalışıyor. Yazıcı keşfi ve Sanal Yazıcı, ana bilgisayar ağ modu gerektirir — konteyneri "network_mode: host" ile yeniden oluşturun.',
- skip: 'Docker\'da çalışmıyor — uygulanamaz.',
+ title: 'Konteyner ağ modu',
+ genericRuntime: 'bir konteyner',
+ pass: '{{runtime}} içinde ana bilgisayar ağıyla çalışıyor.',
+ warn: 'Bambuddy, {{runtime}} içinde köprü ağıyla çalışıyor. Yazıcı keşfi ve Sanal Yazıcı ana bilgisayar ağı gerektirir — konteyneri ana bilgisayar ağıyla yeniden oluşturun (docker-compose için "network_mode: host", Podman için "--network=host").',
+ skip: 'Bir konteyner içinde çalışmıyor — uygulanamaz.',
+ skip_unknown: 'Bambuddy {{runtime}} içinde çalışıyor ancak ağ modu belirlenemedi. Yazıcı keşfi veya Sanal Yazıcı çalışmıyorsa konteyneri ana bilgisayar ağıyla yeniden oluşturun.',
+ skip_system_container: 'Bambuddy, sanal makine gibi doğrudan yerel ağda bulunan bir {{runtime}} sistem konteyneri içinde çalışıyor — uygulanamaz.',
},
subnet: {
title: 'Ağ alt ağı',
diff --git a/frontend/src/i18n/locales/uk.ts b/frontend/src/i18n/locales/uk.ts
index 891eae729..fe3c2f70c 100644
--- a/frontend/src/i18n/locales/uk.ts
+++ b/frontend/src/i18n/locales/uk.ts
@@ -537,6 +537,8 @@ export default {
step: "Крок (мм)",
up: "Перемістити стіл угору",
down: "Перемістити стіл униз",
+ toolheadUp: "Перемістити друкувальну голову вгору",
+ toolheadDown: "Перемістити друкувальну голову вниз",
disabledWhilePrinting: "Вимкнено під час друку",
notHomedTitle: "Принтер не відкалібровано за початковою позицією",
notHomedMessage: "Після останнього друку принтер не виконував пошук початкової позиції. Для безпечного позиціювання спочатку запустіть автопозиціювання: принтер припаркує друкувальну головку та визначить початкові координати X, Y і Z. Або продовжте переміщення без нього — програмні кінцеві обмежувачі буде обійдено.",
@@ -905,11 +907,15 @@ export default {
docsLink: "Переглянути крок 4 встановлення",
docsLinkInternalStorage: 'Чому так стається',
titleInternalStorage: 'Деякі нещодавні друки залишилися у внутрішній пам\'яті принтера',
- bodyInternalStorage: 'Bambu Studio зберіг нарізаний файл у внутрішній пам\'яті принтера, а не на картці, тож Bambuddy не мав чого читати через FTP. На серії H2 та P2S кнопка «Друк» завжди робить саме так: вибір є лише в «Надіслати», і там за замовчуванням теж «Кеш». Ці друки й далі архівуються з назвою та часом, лише без мініатюри та метаданих слайсера. Щоб архіви були повними, запускайте друк із Bambuddy або нарізайте в OrcaSlicer, або в Bambu Studio скористайтеся «Надіслати» із «Зовнішнім носієм», а друк запускайте потім. Усі варіанти потребують картки або флешки в принтері.',
+ bodyInternalStorage: 'Bambu Studio зберіг нарізаний файл у внутрішній пам\'яті принтера, а не на картці, тож Bambuddy не мав чого читати через FTP. На серії H2, P2S та X2D кнопка «Друк» завжди робить саме так: вибір є лише в «Надіслати», і там за замовчуванням теж «Кеш». Ці друки й далі архівуються з назвою та часом, лише без мініатюри та метаданих слайсера. Щоб архіви були повними, запускайте друк із Bambuddy або нарізайте в OrcaSlicer, або в Bambu Studio скористайтеся «Надіслати» із «Зовнішнім носієм», а друк запускайте потім. Усі варіанти потребують картки або флешки в принтері.',
titleNoExternalStorage: 'Деякі нещодавні друки не вдалося заархівувати — у принтері немає носія',
bodyNoExternalStorage: 'Принтер не бачить ані картки, ані флешки у слоті, тож нарізаному файлу не було куди потрапити, а Bambuddy — що читати. Вставте носій, і наступний друк заархівується повністю.',
titleInternalHistory: 'Деякі нещодавні друки запущено з файлу, який уже був на принтері',
bodyInternalHistory: 'Ці друки йшли з власної бібліотеки принтера — повторний друк з його екрана, запуск із Handy або файл, надісланий раніше й надрукований пізніше. Bambuddy читає файли друку через FTP, а той віддає лише картку чи флешку, тоді як принтер тримає цю бібліотеку в області, куди FTP не дістає, — читати 3MF не було де. Налаштування слайсера тут нічого не змінюють: для цих друків нічого не надсилалося. Вони й далі архівуються з назвою та часом, а в «Редагувати архів» витрачений філамент можна вписати вручну. Щоб архів був повним, запускайте друк із Bambuddy або зі свого слайсера.',
+ titleFtpsCooloff: 'Деякі нещодавні друки не вдалося заархівувати — принтер відхилив файлове з\'єднання',
+ bodyFtpsCooloff: 'Bambuddy відкрив порт передавання файлів принтера (FTPS 990), і принтер відповів чимось, що не є TLS, тож прочитати з нього нічого не вдалося. Ці друки все одно збережені в архіві з назвою та часом, лише без мініатюри й метаданих слайсера. Це не налаштування слайсера і не те, що ви змінювали: ті самі моделі та прошивки нормально працюють на інших встановленнях, а уражений принтер зазвичай згодом починає працювати сам. Після такої помилки Bambuddy призупиняє передавання до цього принтера на п\'ять хвилин і повертається по файл, коли пауза завершується, тож короткий епізод виправляється сам; якщо картка й далі порожня, відмова тривала довше за повторну спробу. Що саме це спричиняє, поки невідомо. Якщо повторюється, увімкніть налагоджувальне журналювання на сторінці Система та додайте пакет підтримки до свого звіту.',
+ titleFtpTransferFailed: 'Деякі нещодавні друки не вдалося заархівувати — передавання файлу не вклалося в час',
+ bodyFtpTransferFailed: 'Нарізаний файл лежав на картці принтера, і принтер справді його віддавав, але передавання не завершилося у відведений час. На старті друку принтер одночасно обслуговує камеру, повідомлення про стан і завантаження завдання, і великий 3MF не завжди встигає пройти на цьому тлі. Ці друки й далі заархівовані з назвою та часом, лише без мініатюри та даних слайсера. Це не налаштування слайсера і не наслідок ваших змін. Bambuddy повертається по файл ще тричі протягом наступних десяти хвилин, тож більшість таких випадків закриваються самі; якщо картка так і лишилася порожньою, то й усі ці спроби не вклалися в час. Якщо це повторюється, збільште Тайм-аут підключення в розділі Налаштування > Мережа > Повтор FTP.',
dismissLabel: "Відхилити це повідомлення",
},
searchPlaceholder: "Пошук в архівах...",
@@ -1502,7 +1508,7 @@ export default {
// Time
time: {
etaIfStartedNow: "Час завершення, якщо запустити це завдання зараз",
- asap: "Якнайшвидше",
+ whenFree: "Коли принтер звільниться",
overdue: "Прострочено",
now: "Зараз",
lessThanMinute: "Менше ніж за хвилину",
@@ -1526,6 +1532,7 @@ export default {
staged: "Очікує ручного запуску",
autoPowerOff: "Автоматичне вимкнення після друку",
requirePrevious: "Вимагати успішного завершення попереднього завдання",
+ gcodeInjection: "Вставляти G-код",
printOptions: "Параметри друку",
bedLevelling: "Вирівнювання столу",
flowCalibration: "Калібрування потоку",
@@ -2349,7 +2356,7 @@ export default {
preheatEnabled: "Увімкнути попереднє нагрівання й термовитримку",
preheatEnabledDesc: "Якщо вимкнено, завдання з черги надсилаються на друк негайно. Для кожного елемента черги це налаштування можна перевизначити окремо.",
preheatFilamentTargetsLabel: "Цільова температура камери за типом філаменту (°C)",
- preheatFilamentTargetsHint: "Bambuddy вибирає найвищу цільову температуру серед завантажених слотів AMS. Для друку лише з PLA визначається значення 0, тому етап нагрівання камери автоматично пропускається.",
+ preheatFilamentTargetsHint: "Bambuddy вибирає найвищу цільову температуру серед завантажених слотів AMS. Друк, для всіх слотів якого визначається 0 — PLA, PETG, TPU, PVA, — повністю пропускає прогрівання й починається одразу.",
preheatFilamentTargetsReset: "Відновити налаштування за замовчуванням",
preheatFilamentTargetsDefaultRow: "Інше / не зіставлене",
preheatMaxWait: "Максимальний час очікування (секунди)",
@@ -2366,6 +2373,7 @@ export default {
calibrationMode_on: "Увімкнено",
calibrationMode_auto: "Авто",
preheatTargetOverride: "Інша цільова температура камери (°C; порожньо — типове значення для філаменту)",
+ preheatTargetOverrideHelp: "0 нагріває стіл і виконує витримку без камери. Якщо поле порожнє, друк, якому камера не потрібна, повністю пропускає прогрівання.",
plateClear: "Підтвердження очищення друкарської пластини",
requirePlateClear: "Вимагати підтвердження очищення друкарської пластини",
requirePlateClearDescription: "Якщо ввімкнено, планувальник перед запуском наступного завдання на принтері із завершеним друком очікує підтвердження, що друкарську пластину очищено. Вимкнення також приховує індикатор стану пластини та кнопку «Позначити пластину як очищену» на картках принтерів.",
@@ -4957,7 +4965,7 @@ export default {
linkToSpool: "Прив’язати до котушки",
tagLinked: "Тег пов’язано з котушкою",
tagLinkFailed: "Не вдалося зв’язати тег",
- tagAlreadyLinked: "Тег уже пов’язано з іншою котушкою",
+ tagAlreadyLinked: 'Тег уже пов’язано з котушкою #{{id}}',
unknownTag: "Виявлено невідомий тег RFID.",
// Usage history
usageHistory: "Історія використання",
@@ -5444,7 +5452,7 @@ export default {
project: "Проєкт",
noProject: "Жодного проєкту",
itemsPrinted: "Надруковано об’єктів",
- itemsPrintedHelp: "Кількість елементів, виготовлених у цьому завданні друку",
+ itemsPrintedHelp: "Кількість елементів, виготовлених у цьому завданні друку. Укажіть 0, якщо зіпсовано всю пластину.",
filamentUsed: "Витрачений філамент (г)",
filamentUsedPlaceholder: "напр. 46.16",
filamentUsedHelp: "Вкажіть вручну, якщо друк заархівовано без 3MF — тоді він потрапить до підсумків з філаменту. Повторне сканування архіву, який має 3MF, зчитує значення з файлу.",
@@ -6837,6 +6845,7 @@ export default {
scaleWeight: "Показання ваг",
netWeight: "Нетто",
lastUsed: "Останнє використання",
+ unknownColor: "Невідомий колір",
},
ams: {
noData: "AMS не виявлено",
@@ -7014,7 +7023,7 @@ export default {
skip: "Не перевірено — потрібне активне MQTT-з’єднання. У старіших слайсерах, де цей параметр існує лише в самому слайсері, принтер його не повідомляє. Тому перевірка може бути успішною навіть за вимкненого параметра — перевірте крок установлення 4 вручну.",
skip_unsupported_model: "Ця модель має слот для SD-картки, але не дає змоги ввімкнути цей параметр: поточна прошивка принтерів серії P1 не показує перемикач у Bambu Studio, а сам принтер не має екрана. Виправляти нічого не потрібно; доки Bambu Lab не додасть підтримку в прошивці, в архівованих друках можуть бути відсутні мініатюри та метадані слайсера.",
fail_no_media: 'Параметр увімкнено, але принтер не бачить ані картки, ані флешки у слоті, тож надісланим файлам немає куди подітися. Вставте носій і надрукуйте ще раз — доти кожен заархівований друк буде без мініатюри та метаданих слайсера.',
- warn_internal_storage: 'Параметр увімкнено і носій на місці, але останній друк усе одно потрапив у внутрішню пам\'ять принтера, яку Bambuddy не може прочитати. На серії H2 та P2S кнопка «Друк» у Bambu Studio завжди надсилає туди, незалежно від цього параметра. Друки архівуються з назвою та часом, але без мініатюри та метаданих слайсера. Щоб архіви були повними, запускайте друк із Bambuddy, нарізайте в OrcaSlicer або в Bambu Studio скористайтеся «Надіслати» із «Зовнішнім носієм», а потім запускайте друк.',
+ warn_internal_storage: 'Параметр увімкнено і носій на місці, але останній друк усе одно потрапив у внутрішню пам\'ять принтера, яку Bambuddy не може прочитати. На серії H2, P2S та X2D кнопка «Друк» у Bambu Studio завжди надсилає туди, незалежно від цього параметра. Друки архівуються з назвою та часом, але без мініатюри та метаданих слайсера. Щоб архіви були повними, запускайте друк із Bambuddy, нарізайте в OrcaSlicer або в Bambu Studio скористайтеся «Надіслати» із «Зовнішнім носієм», а потім запускайте друк.',
warn_internal_history: 'Опція увімкнена й носій на місці, але останній друк ішов із файлу, який уже був на принтері, — повторний друк з його екрана, запуск із Handy або файл, надісланий раніше. Ця бібліотека лежить у внутрішній пам\'яті, яку Bambuddy не читає, і жодні налаштування цього не змінюють: для того друку нічого не надсилалося. Він і далі архівується з назвою та часом, але без мініатюри та метаданих слайсера. Щоб архіви були повними, запускайте друк із Bambuddy або надсилайте його зі слайсера.',
},
port_rtsps: {
@@ -7022,11 +7031,20 @@ export default {
pass: "Доступно — потік камери працюватиме.",
warn: "Порт {{port}} недоступний. Перегляд з камери в реальному часі не працюватиме. Це не впливає на друк.",
},
+ macos_local_network: {
+ title: 'Дозвіл macOS на доступ до локальної мережі',
+ pass: 'macOS дозволяє Bambuddy доступ до локальної мережі.',
+ warn_unsigned: 'Python, під яким працює Bambuddy, не має підпису коду, тож macOS нема до чого прив’язати дозвіл на локальну мережу і мовчки відкидає всі з’єднання з принтером — без помилки та без запиту. Запустіть сценарій оновлення Bambuddy (install/update_macos.sh), який його підписує, і перезапустіть Bambuddy. Інтерпретатор: {{executable}}',
+ warn_permission: 'Якщо принтер увімкнений і доступний за цією адресою, відкрийте Системні параметри > Конфіденційність і безпека > Локальна мережа та переконайтеся, що Python від Bambuddy увімкнено. Інакше macOS мовчки відкидає локальні з’єднання, а оновлення Python може залишити старий дозвіл позаду.',
+ },
network_mode: {
- title: "Мережевий режим Docker",
- pass: "Працює в режимі хост-мережі.",
- warn: "Bambuddy працює в мостовому мережевому режимі Docker. Для виявлення принтерів і роботи віртуального принтера потрібен режим мережі хоста — повторно створіть контейнер із параметром \"network_mode: host\".",
- skip: "Не працює в Docker — не застосовується.",
+ title: "Мережевий режим контейнера",
+ genericRuntime: "контейнері",
+ pass: "Працює в {{runtime}} із мережею хоста.",
+ warn: "Bambuddy працює в {{runtime}} із мостовою мережею. Для виявлення принтерів і роботи віртуального принтера потрібна мережа хоста — повторно створіть контейнер із мережею хоста (\"network_mode: host\" у docker-compose, \"--network=host\" для Podman).",
+ skip: "Не працює в контейнері — не застосовується.",
+ skip_unknown: "Bambuddy працює в {{runtime}}, але визначити мережевий режим не вдалося. Якщо виявлення принтерів або віртуальний принтер не працюють, повторно створіть контейнер із мережею хоста.",
+ skip_system_container: "Bambuddy працює в системному контейнері {{runtime}}, який перебуває в локальній мережі як віртуальна машина — не застосовується.",
},
subnet: {
title: "Мережева підмережа",
diff --git a/frontend/src/i18n/locales/zh-CN.ts b/frontend/src/i18n/locales/zh-CN.ts
index 9d4ed9840..7e3350f55 100644
--- a/frontend/src/i18n/locales/zh-CN.ts
+++ b/frontend/src/i18n/locales/zh-CN.ts
@@ -534,6 +534,8 @@ export default {
step: '步长 (mm)',
up: '热床上移',
down: '热床下移',
+ toolheadUp: '喷头上移',
+ toolheadDown: '喷头下移',
disabledWhilePrinting: '打印中已禁用',
notHomedTitle: '打印机未归零',
notHomedMessage: '打印机自上次打印以来尚未归零。请先执行自动归零以确保安全定位(先停放喷头,然后归零 X、Y 和 Z),或者直接移动 — 软限位将被绕过。',
@@ -901,11 +903,15 @@ export default {
docsLink: '查看安装步骤 4',
docsLinkInternalStorage: '为什么会这样',
titleInternalStorage: '最近有些打印留在了打印机的内部存储中',
- bodyInternalStorage: 'Bambu Studio 把切片文件保存到了打印机的内部存储而不是存储卡上,因此 Bambuddy 通过 FTP 读不到任何东西。在 H2 系列和 P2S 上,「打印」按钮总是这样做,只有「发送」才提供选择,而它的默认值也是「缓存」。这些打印仍会带着名称和时间归档,只是没有缩略图和切片元数据。要获得完整归档,请从 Bambuddy 启动打印,或改用 OrcaSlicer 切片;若要继续用 Bambu Studio,请用「发送」并选择「外部存储」,之后再启动打印。以上都需要打印机中插有存储卡或 U 盘。',
+ bodyInternalStorage: 'Bambu Studio 把切片文件保存到了打印机的内部存储而不是存储卡上,因此 Bambuddy 通过 FTP 读不到任何东西。在 H2 系列、P2S 和 X2D 上,「打印」按钮总是这样做,只有「发送」才提供选择,而它的默认值也是「缓存」。这些打印仍会带着名称和时间归档,只是没有缩略图和切片元数据。要获得完整归档,请从 Bambuddy 启动打印,或改用 OrcaSlicer 切片;若要继续用 Bambu Studio,请用「发送」并选择「外部存储」,之后再启动打印。以上都需要打印机中插有存储卡或 U 盘。',
titleNoExternalStorage: '最近有些打印无法归档 — 打印机中没有存储介质',
bodyNoExternalStorage: '打印机的插槽中未检测到存储卡或U盘,切片文件无处存放,Bambuddy 也无从读取。插入一个,下次打印就会完整归档。',
titleInternalHistory: '最近有些打印是从打印机里已有的文件启动的',
bodyInternalHistory: '这些打印来自打印机自己的文件库 — 从它的屏幕重新打印、从 Handy 启动,或是先前发送、稍后才打印的文件。Bambuddy 通过 FTP 读取打印文件,而 FTP 只提供存储卡或 U 盘,打印机却把这个文件库放在 FTP 够不到的区域,所以没有 3MF 可读。切片软件的任何设置都改变不了这一点,因为这些打印根本没有发送过文件。它们仍会带着名称和时间归档,并且可以在「编辑归档」里手动填写已用耗材。要获得完整归档,请从 Bambuddy 或你的切片软件启动打印。',
+ titleFtpsCooloff: '最近有些打印无法归档 — 打印机拒绝了文件连接',
+ bodyFtpsCooloff: 'Bambuddy 打开了打印机的文件传输端口 (FTPS 990),而打印机回应的内容并不是 TLS,因此无法从它读取任何东西。这些打印仍会带着名称和时间归档,只是没有缩略图和切片元数据。这不是切片软件的设置,也不是你改动了什么 —— 相同型号、相同固件在别的安装上运行正常,受影响的打印机通常过一阵子会自行恢复。出现这种失败后,Bambuddy 会暂停对该打印机的传输五分钟,暂停结束后再回来取文件,所以短暂的一次会自行补全;卡片仍然是空的,说明拒绝持续得比重试更久。触发原因目前尚不清楚。如果反复出现,请在系统页面打开调试日志,并把支持包附在你的报告里。',
+ titleFtpTransferFailed: '部分近期打印未能归档 — 文件传输超时',
+ bodyFtpTransferFailed: '切片文件就在打印机的存储卡上,打印机也确实开始传输了,但传输未能在允许的时间内完成。打印开始时,打印机还要同时处理摄像头、状态消息和作业上传,较大的 3MF 文件未必能在这种情况下传完。这些打印仍然保留了名称和时间,只是缺少缩略图和切片数据。这不是切片软件的设置问题,也不是你改动造成的。Bambuddy 会在接下来的十分钟内再取三次文件,因此多数情况会自行补齐;如果卡片仍然是空的,说明那几次尝试同样超时了。若经常出现,请在 设置 > 网络 > FTP 重试 中调高 连接超时。',
dismissLabel: '关闭此通知',
},
searchPlaceholder: '搜索归档...',
@@ -1487,7 +1493,7 @@ export default {
// Time
time: {
etaIfStartedNow: '若此任务现在开始的预计完成时间',
- asap: '尽快',
+ whenFree: '有空闲打印机时',
overdue: '已逾期',
now: '现在',
lessThanMinute: '不到一分钟',
@@ -1511,6 +1517,7 @@ export default {
staged: '暂存(手动开始)',
autoPowerOff: '打印后自动关机',
requirePrevious: '要求前一个成功',
+ gcodeInjection: '注入G-code',
printOptions: '打印选项',
bedLevelling: '热床调平',
flowCalibration: '流量校准',
@@ -2331,7 +2338,7 @@ export default {
preheatEnabled: '启用预热与保温',
preheatEnabledDesc: '关闭时,排队打印立即开始。每个队列项可按单次打印覆盖。',
preheatFilamentTargetsLabel: '按耗材的腔体目标 (°C)',
- preheatFilamentTargetsHint: 'Bambuddy 会在已装入的 AMS 槽位中选择最高目标;仅 PLA 的打印推导为 0 并自动跳过腔体阶段。',
+ preheatFilamentTargetsHint: 'Bambuddy 会在已装入的 AMS 槽位中选择最高目标;所有槽位都推导为 0 的打印(PLA、PETG、TPU、PVA)将完全跳过预热并立即开始。',
preheatFilamentTargetsReset: '重置为默认值',
preheatFilamentTargetsDefaultRow: '其他 / 未映射',
preheatMaxWait: '最长等待(秒)',
@@ -2348,6 +2355,7 @@ export default {
calibrationMode_on: '开启',
calibrationMode_auto: '自动',
preheatTargetOverride: '覆盖腔体目标 (°C,留空使用耗材默认)',
+ preheatTargetOverrideHelp: '0 表示只加热热床并执行保温,不加热腔体。留空时,不需要腔体加热的打印会完全跳过预热。',
plateClear: '热床清空确认',
requirePlateClear: '需要热床清空确认',
requirePlateClearDescription: '启用后,调度器会在已完成打印的打印机上启动排队打印之前,等待每台打印机的热床清空确认。禁用后,也会隐藏打印机卡片上的打印板状态标记和“将打印板标记为已清理”按钮。',
@@ -4909,7 +4917,7 @@ export default {
linkToSpool: '链接到耗材',
tagLinked: '标签已链接到耗材',
tagLinkFailed: '链接标签失败',
- tagAlreadyLinked: '标签已链接到其他耗材',
+ tagAlreadyLinked: '标签已链接到耗材 #{{id}}',
unknownTag: '检测到未知 RFID 标签',
// Usage history
usageHistory: '使用历史',
@@ -5390,7 +5398,7 @@ export default {
project: '项目',
noProject: '无项目',
itemsPrinted: '打印数量',
- itemsPrintedHelp: '此打印任务中生产的物品数量',
+ itemsPrintedHelp: '此打印任务中生产的物品数量。整块打印板报废时填 0。',
filamentUsed: '已用耗材 (g)',
filamentUsedPlaceholder: '例如 46.16',
filamentUsedHelp: '当打印任务在没有 3MF 的情况下归档时,可手动填写,使其仍计入耗材总量。重新扫描带有 3MF 的归档时,该数值会从文件中重新读取。',
@@ -6781,6 +6789,7 @@ export default {
scaleWeight: '秤重',
netWeight: '净重',
lastUsed: '上次使用',
+ unknownColor: '未知颜色',
},
ams: {
noData: '未检测到 AMS',
@@ -6958,7 +6967,7 @@ export default {
skip: '未检查 — 需要有效的 MQTT 连接。在该设置仅存在于切片机中的较旧切片机上,打印机不会报告此设置,因此即使选项已关闭,此检查也会通过 — 请手动验证安装步骤 4。',
skip_unsupported_model: '此型号有 SD 卡槽,但无法开启该选项 — 当前 P1 系列固件不会在 Bambu Studio 中显示此开关,且打印机没有屏幕。这里无需修复;在 Bambu Lab 通过固件添加支持之前,存档的打印可能缺少缩略图和切片元数据。',
fail_no_media: '该选项已开启,但打印机的插槽中未检测到存储卡或U盘,发送的文件无处存放。插入一个再打印一次 — 在此之前,每一次归档的打印都会缺少缩略图和切片元数据。',
- warn_internal_storage: '该选项已开启且存储介质在位,但上一次打印仍进入了 Bambuddy 无法读取的打印机内部存储。在 H2 系列和 P2S 上,无论此选项如何设置,Bambu Studio 的「打印」按钮总是发送到那里。打印会带着名称和时间归档,但没有缩略图和切片元数据。要获得完整归档,请从 Bambuddy 启动打印、改用 OrcaSlicer 切片,或在 Bambu Studio 中用「发送」选择「外部存储」后再启动打印。',
+ warn_internal_storage: '该选项已开启且存储介质在位,但上一次打印仍进入了 Bambuddy 无法读取的打印机内部存储。在 H2 系列、P2S 和 X2D 上,无论此选项如何设置,Bambu Studio 的「打印」按钮总是发送到那里。打印会带着名称和时间归档,但没有缩略图和切片元数据。要获得完整归档,请从 Bambuddy 启动打印、改用 OrcaSlicer 切片,或在 Bambu Studio 中用「发送」选择「外部存储」后再启动打印。',
warn_internal_history: '该选项已开启,存储介质也在,但最近一次打印来自打印机里已有的文件 — 从它的屏幕重新打印、从 Handy 启动,或是先前发送过的文件。这个文件库位于 Bambuddy 读不到的内部存储中,而且任何设置都改变不了,因为那次打印根本没有发送过文件。它仍会带着名称和时间归档,但没有缩略图和切片元数据。要获得完整归档,请从 Bambuddy 启动打印,或从切片软件发送。',
},
port_rtsps: {
@@ -6966,11 +6975,20 @@ export default {
pass: '可达 — 摄像头视频流将正常工作。',
warn: '端口 {{port}} 不可达。实时摄像头视图将无法工作。这不影响打印。',
},
+ macos_local_network: {
+ title: 'macOS 本地网络权限',
+ pass: 'macOS 已允许 Bambuddy 访问本地网络。',
+ warn_unsigned: '运行 Bambuddy 的 Python 没有代码签名,macOS 因此无法将本地网络权限绑定到任何身份,会静默丢弃所有到打印机的连接——既没有错误,也不会弹出授权提示。请运行会完成签名的 Bambuddy 更新脚本 (install/update_macos.sh),然后重启 Bambuddy。解释器:{{executable}}',
+ warn_permission: '如果打印机已开机并可通过该地址访问,请打开 系统设置 > 隐私与安全性 > 本地网络,确认 Bambuddy 使用的 Python 已启用。未启用时 macOS 会静默丢弃本地连接,而且升级 Python 可能不会保留原有授权。',
+ },
network_mode: {
- title: 'Docker 网络模式',
- pass: '正在以 host 网络模式运行。',
- warn: 'Bambuddy 正在以 Docker bridge 网络运行。打印机发现和虚拟打印机需要 host 网络模式 — 请使用 "network_mode: host" 重新创建容器。',
- skip: '未在 Docker 中运行 — 不适用。',
+ title: '容器网络模式',
+ genericRuntime: '容器',
+ pass: '正在 {{runtime}} 中以 host 网络运行。',
+ warn: 'Bambuddy 正在 {{runtime}} 中以 bridge 网络运行。打印机发现和虚拟打印机需要 host 网络 — 请使用 host 网络重新创建容器(docker-compose 中为 "network_mode: host",Podman 中为 "--network=host")。',
+ skip: '未在容器中运行 — 不适用。',
+ skip_unknown: 'Bambuddy 正在 {{runtime}} 中运行,但无法确定其网络模式。如果打印机发现或虚拟打印机无法使用,请使用 host 网络重新创建容器。',
+ skip_system_container: 'Bambuddy 正在 {{runtime}} 系统容器中运行,它像虚拟机一样直接位于局域网中 — 不适用。',
},
subnet: {
title: '网络子网',
diff --git a/frontend/src/i18n/locales/zh-TW.ts b/frontend/src/i18n/locales/zh-TW.ts
index 0b77f4be6..33910c38d 100644
--- a/frontend/src/i18n/locales/zh-TW.ts
+++ b/frontend/src/i18n/locales/zh-TW.ts
@@ -534,6 +534,8 @@ export default {
step: '步長 (mm)',
up: '熱床上移',
down: '熱床下移',
+ toolheadUp: '噴頭上移',
+ toolheadDown: '噴頭下移',
disabledWhilePrinting: '列印中已停用',
notHomedTitle: '印表機未歸零',
notHomedMessage: '印表機自上次列印以來尚未歸零。請先執行自動歸零以確保安全定位(先停放噴頭,然後歸零 X、Y 和 Z),或者直接移動 — 軟限位將被繞過。',
@@ -901,11 +903,15 @@ export default {
docsLink: '檢視安裝步驟 4',
docsLinkInternalStorage: '為什麼會這樣',
titleInternalStorage: '最近有些列印留在了印表機的內部儲存中',
- bodyInternalStorage: 'Bambu Studio 把切片檔案儲存到了印表機的內部儲存而不是記憶卡上,因此 Bambuddy 透過 FTP 讀不到任何東西。在 H2 系列與 P2S 上,「列印」按鈕總是這樣做,只有「傳送」才提供選擇,而它的預設值也是「快取」。這些列印仍會帶著名稱與時間歸檔,只是沒有縮圖與切片中繼資料。要取得完整歸檔,請從 Bambuddy 啟動列印,或改用 OrcaSlicer 切片;若要繼續用 Bambu Studio,請用「傳送」並選擇「外部儲存」,之後再啟動列印。以上都需要印表機中插有記憶卡或 USB 隨身碟。',
+ bodyInternalStorage: 'Bambu Studio 把切片檔案儲存到了印表機的內部儲存而不是記憶卡上,因此 Bambuddy 透過 FTP 讀不到任何東西。在 H2 系列、P2S 與 X2D 上,「列印」按鈕總是這樣做,只有「傳送」才提供選擇,而它的預設值也是「快取」。這些列印仍會帶著名稱與時間歸檔,只是沒有縮圖與切片中繼資料。要取得完整歸檔,請從 Bambuddy 啟動列印,或改用 OrcaSlicer 切片;若要繼續用 Bambu Studio,請用「傳送」並選擇「外部儲存」,之後再啟動列印。以上都需要印表機中插有記憶卡或 USB 隨身碟。',
titleNoExternalStorage: '最近有些列印無法歸檔 — 印表機中沒有儲存媒體',
bodyNoExternalStorage: '印表機的插槽中未偵測到記憶卡或隨身碟,切片檔案無處存放,Bambuddy 也無從讀取。插入一個,下次列印就會完整歸檔。',
titleInternalHistory: '最近有些列印是從印表機裡既有的檔案啟動的',
bodyInternalHistory: '這些列印來自印表機自己的檔案庫 — 從它的螢幕重新列印、從 Handy 啟動,或是先前傳送、稍後才列印的檔案。Bambuddy 透過 FTP 讀取列印檔案,而 FTP 只提供記憶卡或隨身碟,印表機卻把這個檔案庫放在 FTP 搆不到的區域,因此沒有 3MF 可讀。切片軟體的任何設定都改變不了這一點,因為這些列印根本沒有傳送過檔案。它們仍會帶著名稱與時間歸檔,並且可以在「編輯歸檔」中手動填入已用耗材。要獲得完整歸檔,請從 Bambuddy 或你的切片軟體啟動列印。',
+ titleFtpsCooloff: '最近有些列印無法歸檔 — 印表機拒絕了檔案連線',
+ bodyFtpsCooloff: 'Bambuddy 開啟了印表機的檔案傳輸連接埠 (FTPS 990),而印表機回應的內容並不是 TLS,因此無法從它讀取任何東西。這些列印仍會帶著名稱和時間歸檔,只是沒有縮圖和切片中繼資料。這不是切片軟體的設定,也不是你改動了什麼 —— 相同型號、相同韌體在其他安裝上運作正常,受影響的印表機通常過一陣子會自行恢復。出現這種失敗後,Bambuddy 會暫停對該印表機的傳輸五分鐘,暫停結束後再回來取檔案,所以短暫的一次會自行補齊;卡片仍然是空的,表示拒絕持續得比重試更久。觸發原因目前尚不清楚。如果反覆出現,請在系統頁面開啟除錯記錄,並把支援套件附在你的報告裡。',
+ titleFtpTransferFailed: '部分近期列印未能封存 — 檔案傳輸逾時',
+ bodyFtpTransferFailed: '切片檔就在印表機的記憶卡上,印表機也確實開始傳輸了,但傳輸未能在允許的時間內完成。列印開始時,印表機還要同時處理攝影機、狀態訊息與工作上傳,較大的 3MF 檔未必能在這種情況下傳完。這些列印仍保留名稱與時間,只是缺少縮圖與切片資料。這不是切片軟體的設定問題,也不是你更動造成的。Bambuddy 會在接下來的十分鐘內再取三次檔案,因此多數情況會自行補齊;如果卡片仍是空的,代表那幾次嘗試同樣逾時了。若經常發生,請在 設定 > 網路 > FTP 重試 中調高 連線逾時。',
dismissLabel: '關閉此通知',
},
searchPlaceholder: '搜尋歸檔...',
@@ -1487,7 +1493,7 @@ export default {
// Time
time: {
etaIfStartedNow: '若此工作現在開始的預計完成時間',
- asap: '儘快',
+ whenFree: '有空閒印表機時',
overdue: '已逾期',
now: '現在',
lessThanMinute: '不到一分鐘',
@@ -1511,6 +1517,7 @@ export default {
staged: '暫存(手動開始)',
autoPowerOff: '列印後自動關機',
requirePrevious: '要求前一個成功',
+ gcodeInjection: '注入G-code',
printOptions: '列印選項',
bedLevelling: '熱床調平',
flowCalibration: '流量校準',
@@ -2331,7 +2338,7 @@ export default {
preheatEnabled: '啟用預熱與保溫',
preheatEnabledDesc: '關閉時,佇列列印立即開始。每個佇列項目可依單次列印覆寫。',
preheatFilamentTargetsLabel: '依耗材的腔體目標 (°C)',
- preheatFilamentTargetsHint: 'Bambuddy 在已裝入的 AMS 槽位中挑選最高目標;只有 PLA 的列印推導為 0,並自動跳過腔體階段。',
+ preheatFilamentTargetsHint: 'Bambuddy 在已裝入的 AMS 槽位中挑選最高目標;所有槽位都推導為 0 的列印(PLA、PETG、TPU、PVA)會完全跳過預熱並立即開始。',
preheatFilamentTargetsReset: '重設為預設值',
preheatFilamentTargetsDefaultRow: '其他 / 未對應',
preheatMaxWait: '最長等待(秒)',
@@ -2348,6 +2355,7 @@ export default {
calibrationMode_on: '開啟',
calibrationMode_auto: '自動',
preheatTargetOverride: '覆寫腔體目標 (°C,留空使用耗材預設)',
+ preheatTargetOverrideHelp: '0 表示只加熱熱床並執行保溫,不加熱腔體。留空時,不需要腔體加熱的列印會完全跳過預熱。',
plateClear: '熱床清空確認',
requirePlateClear: '需要熱床清空確認',
requirePlateClearDescription: '啟用後,排程器會在已完成列印的印表機上啟動佇列列印之前,等待每臺印表機的熱床清空確認。停用後,也會隱藏印表機卡片上的列印板狀態標記和「將列印板標記為已清理」按鈕。',
@@ -4909,7 +4917,7 @@ export default {
linkToSpool: '連結到耗材',
tagLinked: '標籤已連結到耗材',
tagLinkFailed: '連結標籤失敗',
- tagAlreadyLinked: '標籤已連結到其他耗材',
+ tagAlreadyLinked: '標籤已連結到耗材 #{{id}}',
unknownTag: '偵測到未知 RFID 標籤',
// Usage history
usageHistory: '使用歷史',
@@ -5390,7 +5398,7 @@ export default {
project: '專案',
noProject: '無專案',
itemsPrinted: '列印數量',
- itemsPrintedHelp: '此列印任務中生產的物品數量',
+ itemsPrintedHelp: '此列印任務中生產的物品數量。整塊列印板報廢時填 0。',
filamentUsed: '已用耗材 (g)',
filamentUsedPlaceholder: '例如 46.16',
filamentUsedHelp: '當列印工作在沒有 3MF 的情況下封存時,可手動填寫,使其仍計入耗材總量。重新掃描具有 3MF 的封存項目時,此數值會從檔案重新讀取。',
@@ -6781,6 +6789,7 @@ export default {
scaleWeight: '磅秤重',
netWeight: '淨重',
lastUsed: '上次使用',
+ unknownColor: '未知顏色',
},
ams: {
noData: '未偵測到 AMS',
@@ -6958,7 +6967,7 @@ export default {
skip: '未檢查 — 需要有效的 MQTT 連線。在該設定僅存在於切片機中的較舊切片機上,印表機不會回報此設定,因此即使選項已關閉,此檢查也會通過 — 請手動驗證安裝步驟 4。',
skip_unsupported_model: '此型號有 SD 卡槽,但無法開啟該選項 — 目前 P1 系列韌體不會在 Bambu Studio 中顯示此開關,且印表機沒有螢幕。這裡無需修復;在 Bambu Lab 透過韌體加入支援之前,封存的列印可能缺少縮圖和切片中繼資料。',
fail_no_media: '該選項已開啟,但印表機的插槽中未偵測到記憶卡或隨身碟,傳送的檔案無處存放。插入一個再列印一次 — 在此之前,每一次歸檔的列印都會缺少縮圖與切片中繼資料。',
- warn_internal_storage: '該選項已開啟且儲存媒體在位,但上一次列印仍進入了 Bambuddy 無法讀取的印表機內部儲存。在 H2 系列與 P2S 上,無論此選項如何設定,Bambu Studio 的「列印」按鈕總是傳送到那裡。列印會帶著名稱與時間歸檔,但沒有縮圖與切片中繼資料。要取得完整歸檔,請從 Bambuddy 啟動列印、改用 OrcaSlicer 切片,或在 Bambu Studio 中用「傳送」選擇「外部儲存」後再啟動列印。',
+ warn_internal_storage: '該選項已開啟且儲存媒體在位,但上一次列印仍進入了 Bambuddy 無法讀取的印表機內部儲存。在 H2 系列、P2S 與 X2D 上,無論此選項如何設定,Bambu Studio 的「列印」按鈕總是傳送到那裡。列印會帶著名稱與時間歸檔,但沒有縮圖與切片中繼資料。要取得完整歸檔,請從 Bambuddy 啟動列印、改用 OrcaSlicer 切片,或在 Bambu Studio 中用「傳送」選擇「外部儲存」後再啟動列印。',
warn_internal_history: '該選項已開啟,儲存媒體也在,但最近一次列印來自印表機裡既有的檔案 — 從它的螢幕重新列印、從 Handy 啟動,或是先前傳送過的檔案。這個檔案庫位於 Bambuddy 讀不到的內部儲存中,而且任何設定都改變不了,因為那次列印根本沒有傳送過檔案。它仍會帶著名稱與時間歸檔,但沒有縮圖與切片中繼資料。要獲得完整歸檔,請從 Bambuddy 啟動列印,或從切片軟體傳送。',
},
port_rtsps: {
@@ -6966,11 +6975,20 @@ export default {
pass: '可達 — 攝影機串流將正常運作。',
warn: '連接埠 {{port}} 無法連線。即時攝影機檢視將無法運作。這不影響列印。',
},
+ macos_local_network: {
+ title: 'macOS 本地網路權限',
+ pass: 'macOS 已允許 Bambuddy 存取本地網路。',
+ warn_unsigned: '執行 Bambuddy 的 Python 沒有程式碼簽章,macOS 因此無法將本地網路權限繫結到任何身分,會靜默丟棄所有連往印表機的連線——既沒有錯誤,也不會出現授權提示。請執行會完成簽章的 Bambuddy 更新指令碼 (install/update_macos.sh),然後重新啟動 Bambuddy。直譯器:{{executable}}',
+ warn_permission: '若印表機已開機且可透過此位址連線,請開啟 系統設定 > 隱私權與安全性 > 本地網路,確認 Bambuddy 使用的 Python 已啟用。未啟用時 macOS 會靜默丟棄本地連線,而且更新 Python 可能不會保留原有授權。',
+ },
network_mode: {
- title: 'Docker 網路模式',
- pass: '正在以 host 網路模式執行。',
- warn: 'Bambuddy 正在以 Docker bridge 網路執行。印表機探索與虛擬印表機需要 host 網路模式 — 請使用 "network_mode: host" 重新建立容器。',
- skip: '未在 Docker 中執行 — 不適用。',
+ title: '容器網路模式',
+ genericRuntime: '容器',
+ pass: '正在 {{runtime}} 中以 host 網路執行。',
+ warn: 'Bambuddy 正在 {{runtime}} 中以 bridge 網路執行。印表機探索與虛擬印表機需要 host 網路 — 請使用 host 網路重新建立容器(docker-compose 中為 "network_mode: host",Podman 中為 "--network=host")。',
+ skip: '未在容器中執行 — 不適用。',
+ skip_unknown: 'Bambuddy 正在 {{runtime}} 中執行,但無法判斷其網路模式。如果印表機探索或虛擬印表機無法使用,請使用 host 網路重新建立容器。',
+ skip_system_container: 'Bambuddy 正在 {{runtime}} 系統容器中執行,它像虛擬機器一樣直接位於區域網路中 — 不適用。',
},
subnet: {
title: '網路子網路',
diff --git a/frontend/src/index.css b/frontend/src/index.css
index 63a7bf987..bdf5fedc5 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -348,6 +348,16 @@ body {
color: var(--text-primary);
}
+/* Same, for the hover variant (#1909). Tailwind compiles `hover:text-white`
+ to `.hover\:text-white:hover`, a different selector the rule above never
+ matches -- so every one of the ~400 `text-bambu-gray hover:text-white`
+ controls turned literally white on hover and vanished against the light
+ theme's background. Unlayered, like the rule above, so it outranks
+ Tailwind's `@layer utilities` output. */
+.hover\:text-white:hover {
+ color: var(--text-primary);
+}
+
/* Smooth transitions for theme changes */
.bg-bambu-dark,
.bg-bambu-dark-secondary,
diff --git a/frontend/src/pages/ArchivesPage.tsx b/frontend/src/pages/ArchivesPage.tsx
index 5a6829e26..6f6797621 100644
--- a/frontend/src/pages/ArchivesPage.tsx
+++ b/frontend/src/pages/ArchivesPage.tsx
@@ -2867,29 +2867,41 @@ export function ArchivesPage() {
setNo3MFWarningDismissed(true);
};
// Why the 3MF was missing decides what to tell the user, and the original
- // single wording is wrong for three of the four cases: it sends H2-series and
+ // single wording is wrong for five of the six cases: it sends H2-series and
// P2S owners to switch on a setting that is already on and would not have
// helped, it blames the slicer when the real answer is an empty card slot
- // (#2780), and it blames a slicer that was never involved when the print was
- // started from a file already on the printer (#1820). An unknown/absent
- // reason keeps the original text.
+ // (#2780), it blames a slicer that was never involved when the print was
+ // started from a file already on the printer (#1820), it blames the
+ // slicer again when the printer's own file service refused the TLS handshake
+ // and no lookup was ever attempted (#2957, surfaced by #2780), and it blames
+ // it a third time when the file was on the card, was served, and the transfer
+ // simply ran out of time (#3063). An unknown/absent reason keeps the original
+ // text.
const no3MFVariant =
- no3MFWarning?.reason === 'internal_storage'
- ? 'InternalStorage'
- : no3MFWarning?.reason === 'no_external_storage'
- ? 'NoExternalStorage'
- : no3MFWarning?.reason === 'internal_history'
- ? 'InternalHistory'
- : '';
+ no3MFWarning?.reason === 'ftps_cooloff'
+ ? 'FtpsCooloff'
+ : no3MFWarning?.reason === 'ftp_transfer_failed'
+ ? 'FtpTransferFailed'
+ : no3MFWarning?.reason === 'internal_storage'
+ ? 'InternalStorage'
+ : no3MFWarning?.reason === 'no_external_storage'
+ ? 'NoExternalStorage'
+ : no3MFWarning?.reason === 'internal_history'
+ ? 'InternalHistory'
+ : '';
// Nothing to link for the empty-slot case — "put a card in" is the whole fix.
const no3MFDocsHref =
- no3MFWarning?.reason === 'internal_storage'
- ? 'https://wiki.bambuddy.cool/reference/troubleshooting/#archive-card-has-only-a-name'
- : no3MFWarning?.reason === 'no_external_storage'
- ? null
- : no3MFWarning?.reason === 'internal_history'
- ? 'https://wiki.bambuddy.cool/reference/troubleshooting/#print-started-on-the-printer-has-no-thumbnail'
- : 'https://wiki.bambuddy.cool/getting-started/#step-4-enable-store-sent-files-on-external-storage';
+ no3MFWarning?.reason === 'ftps_cooloff'
+ ? 'https://wiki.bambuddy.cool/reference/troubleshooting/#ftps-tls-failure'
+ : no3MFWarning?.reason === 'ftp_transfer_failed'
+ ? 'https://wiki.bambuddy.cool/reference/troubleshooting/#ftp-transfer-timed-out'
+ : no3MFWarning?.reason === 'internal_storage'
+ ? 'https://wiki.bambuddy.cool/reference/troubleshooting/#archive-card-has-only-a-name'
+ : no3MFWarning?.reason === 'no_external_storage'
+ ? null
+ : no3MFWarning?.reason === 'internal_history'
+ ? 'https://wiki.bambuddy.cool/reference/troubleshooting/#print-started-on-the-printer-has-no-thumbnail'
+ : 'https://wiki.bambuddy.cool/getting-started/#step-4-enable-store-sent-files-on-external-storage';
const [isSelectionMode, setIsSelectionMode] = useState(false);
const [showBulkDeleteConfirm, setShowBulkDeleteConfirm] = useState(false);
const [showBatchTag, setShowBatchTag] = useState(false);
@@ -3720,7 +3732,9 @@ export function ArchivesPage() {
>
{t(
no3MFWarning?.reason === 'internal_storage' ||
- no3MFWarning?.reason === 'internal_history'
+ no3MFWarning?.reason === 'internal_history' ||
+ no3MFWarning?.reason === 'ftps_cooloff' ||
+ no3MFWarning?.reason === 'ftp_transfer_failed'
? 'archives.no3mfBanner.docsLinkInternalStorage'
: 'archives.no3mfBanner.docsLink',
)}
diff --git a/frontend/src/pages/FileManagerPage.tsx b/frontend/src/pages/FileManagerPage.tsx
index 8258e95cb..47c0b5bff 100644
--- a/frontend/src/pages/FileManagerPage.tsx
+++ b/frontend/src/pages/FileManagerPage.tsx
@@ -755,6 +755,10 @@ interface FileCardProps {
onOpenInSlicer?: (file: LibraryFileListItem) => void;
onRunPipeline?: (file: LibraryFileListItem) => void;
useSlicerApi?: boolean;
+ // Which slicer the desktop handoff targets. Decides whether an STL or STEP
+ // gets a Slice action at all: Bambu Studio's protocol handler takes 3MF only
+ // (#3029), so offering one there would only ever fail.
+ desktopSlicer: SlicerType;
canSlice?: boolean;
onPreview3d?: (file: LibraryFileListItem) => void;
onRename?: (file: LibraryFileListItem) => void;
@@ -768,7 +772,7 @@ interface FileCardProps {
t: TFunction;
}
-function FileCard({ file, isSelected, onSelect, onDelete, onDownload, onPrint, onSlice, onOpenInSlicer, onRunPipeline, useSlicerApi, canSlice, onPreview3d, onRename, onGenerateThumbnail, onTagClick, thumbnailVersion, hasPermission, canModify, authEnabled, showModified, t }: FileCardProps) {
+function FileCard({ file, isSelected, onSelect, onDelete, onDownload, onPrint, onSlice, onOpenInSlicer, onRunPipeline, useSlicerApi, desktopSlicer, canSlice, onPreview3d, onRename, onGenerateThumbnail, onTagClick, thumbnailVersion, hasPermission, canModify, authEnabled, showModified, t }: FileCardProps) {
// Viewport coordinates rather than a flag, because the menu is rendered by
// `ContextMenu` at `position: fixed` and anchored to the button (#2846). The
// card it belongs to is only ~270px tall for a bare STL, which is shorter
@@ -792,7 +796,7 @@ function FileCard({ file, isSelected, onSelect, onDelete, onDownload, onPrint, o
title: !hasPermission('queue:create') ? t('fileManager.noPermissionAddToQueue') : undefined,
});
}
- if (isSliceableLibraryFile(file, !!useSlicerApi) && (useSlicerApi ? onSlice : onOpenInSlicer)) {
+ if (isSliceableLibraryFile(file, !!useSlicerApi, desktopSlicer) && (useSlicerApi ? onSlice : onOpenInSlicer)) {
menuItems.push({
label: t('slice.action'),
icon: useSlicerApi ? : ,
@@ -801,7 +805,7 @@ function FileCard({ file, isSelected, onSelect, onDelete, onDownload, onPrint, o
title: !canSlice ? (useSlicerApi ? t('fileManager.noPermissionSlice') : t('fileManager.noPermissionDownload')) : undefined,
});
}
- if (onRunPipeline && useSlicerApi && isSliceableLibraryFile(file, true)) {
+ if (onRunPipeline && useSlicerApi && isSliceableLibraryFile(file, true, desktopSlicer)) {
menuItems.push({
label: t('library.runWithPipeline.actionLabel'),
icon: ,
@@ -2428,6 +2432,7 @@ export function FileManagerPage() {
onPrint={setPrintFile}
onSlice={setSliceFile}
onOpenInSlicer={handleOpenInSlicer}
+ desktopSlicer={preferredSlicer}
onRunPipeline={setRunPipelineFile}
useSlicerApi={settings?.use_slicer_api ?? false}
canSlice={canSlice()}
@@ -2606,7 +2611,7 @@ export function FileManagerPage() {
>
)}
- {isSliceableLibraryFile(file, !!settings?.use_slicer_api) && (
+ {isSliceableLibraryFile(file, !!settings?.use_slicer_api, preferredSlicer) && (
{
if (!canSlice()) return;
@@ -2623,7 +2628,7 @@ export function FileManagerPage() {
{settings?.use_slicer_api ? : }
)}
- {(settings?.use_slicer_api ?? false) && isSliceableLibraryFile(file, true) && (
+ {(settings?.use_slicer_api ?? false) && isSliceableLibraryFile(file, true, preferredSlicer) && (
hasPermission('pipelines:run') && setRunPipelineFile(file)}
className={`p-1.5 rounded transition-colors ${
@@ -2869,7 +2874,7 @@ export function FileManagerPage() {
onSliceWithBambuddy={
// Only offer in-app slicing on files the SliceModal can actually
// handle (matches the file-row Cog visibility check at :2127).
- isSliceableLibraryFile(viewerFile, true) && hasPermission('library:upload')
+ isSliceableLibraryFile(viewerFile, true, preferredSlicer) && hasPermission('library:upload')
? () => {
const f = viewerFile;
setViewerFile(null);
diff --git a/frontend/src/pages/FinancePage.tsx b/frontend/src/pages/FinancePage.tsx
index c7548fa2d..e0dc7416d 100644
--- a/frontend/src/pages/FinancePage.tsx
+++ b/frontend/src/pages/FinancePage.tsx
@@ -186,6 +186,15 @@ export function FinancePage() {
enabled: canReadOwn,
});
+ // Currency comes from the install setting, like every other page. Read from
+ // /settings/ui-flags rather than /settings, which needs SETTINGS_READ that a
+ // cost_centers:read_own user does not have (#3023). The Layout already holds
+ // this query key, so this is served from cache.
+ const { data: uiFlags } = useQuery({
+ queryKey: ['ui-flags'],
+ queryFn: api.getUiFlags,
+ });
+
const { data: transactionsResponse, isLoading: personalTxLoading } = useQuery({
queryKey: ['finance', 'me', 'transactions', txLimit, txOffset],
queryFn: () => api.getMyTransactions(txLimit, txOffset),
@@ -583,7 +592,7 @@ export function FinancePage() {
removeMemberMutation.mutate({ costCenterId: selectedManageCenterId, userId });
};
- const currency = wallet?.currency || 'EUR';
+ const currency = uiFlags?.currency || 'USD';
const currencySymbol = getCurrencySymbol(currency);
const sortedUsers = useMemo(() => {
diff --git a/frontend/src/pages/InventoryPage.tsx b/frontend/src/pages/InventoryPage.tsx
index 5b00e87ae..561cc776c 100644
--- a/frontend/src/pages/InventoryPage.tsx
+++ b/frontend/src/pages/InventoryPage.tsx
@@ -26,6 +26,7 @@ import { BulkEditSpoolsModal } from '../components/BulkEditSpoolsModal';
import { useToast } from '../contexts/ToastContext';
import { useAuth } from '../contexts/AuthContext';
import { colorSortKey, resolveSpoolColorName } from '../utils/colors';
+import { useColorCatalogVersion } from '../hooks/useColorCatalogVersion';
import { getCurrencySymbol } from '../utils/currency';
import { formatDateInput, parseUTCDate, type DateFormat } from '../utils/date';
import { formatSlotLabel } from '../utils/amsHelpers';
@@ -269,7 +270,7 @@ const columnCells: Record ReactNode> = {
{spool.subtype || '-'}
),
color_name: ({ spool }) => (
- {resolveSpoolColorName(spool.color_name, spool.rgba) || '-'}
+ {resolveSpoolColorName(spool.color_name, spool.rgba, spool.color_name_is_synthesized) || '-'}
),
brand: ({ spool }) => (
{spool.brand || '-'}
@@ -573,6 +574,9 @@ export default function InventoryPageRouter() {
function InventoryPage({ spoolmanMode = false, spoolmanModeReady = true }: { spoolmanMode?: boolean; spoolmanModeReady?: boolean }) {
const { t } = useTranslation();
+ // The spool filter below resolves colour names through the catalog; its
+ // memo has to recompute when the catalog finishes loading (#3090).
+ const colorCatalogVersion = useColorCatalogVersion();
const queryClient = useQueryClient();
const { showToast } = useToast();
const { hasPermission, loading: authLoading } = useAuth();
@@ -1029,7 +1033,13 @@ function InventoryPage({ spoolmanMode = false, spoolmanModeReady = true }: { spo
await spoolbuddyApi.updateSpoolWeight(spool.id, spool.last_scale_weight);
}
queryClient.invalidateQueries({ queryKey: spoolsQueryKey });
- const spoolName = [spool.brand, spool.material, spool.color_name].filter(Boolean).join(' ');
+ const spoolName = [
+ spool.brand,
+ spool.material,
+ resolveSpoolColorName(spool.color_name, spool.rgba, spool.color_name_is_synthesized),
+ ]
+ .filter(Boolean)
+ .join(' ');
showToast(`Synced "${spoolName}" to scale weight`, 'success');
} catch (e) {
const is404 = e instanceof ApiError && e.status === 404;
@@ -1217,6 +1227,11 @@ function InventoryPage({ spoolmanMode = false, spoolmanModeReady = true }: { spo
// Filtering pipeline
const filteredSpools = useMemo(() => {
+ // Named so this memo depends on it: the global search below resolves
+ // colour names through the catalog, which `resolveSpoolColorName` reads
+ // from module state the linter cannot follow. Without it a query typed
+ // before the catalog loads keeps its empty result (#3090).
+ void colorCatalogVersion;
let filtered = spools || [];
// Archive filter
@@ -1294,7 +1309,7 @@ function InventoryPage({ spoolmanMode = false, spoolmanModeReady = true }: { spo
}
return filtered;
- }, [spools, archiveFilter, usageFilter, materialFilter, brandFilter, categoryFilter, spoolFilter, stockFilter, storageLocationFilter, search, lowStockThreshold, storageLocations]);
+ }, [spools, archiveFilter, usageFilter, materialFilter, brandFilter, categoryFilter, spoolFilter, stockFilter, storageLocationFilter, search, lowStockThreshold, storageLocations, colorCatalogVersion]);
// Reset page on filter changes
const resetPage = () => setPageIndex(0);
@@ -2056,7 +2071,7 @@ function InventoryPage({ spoolmanMode = false, spoolmanModeReady = true }: { spo
>
- {resolveSpoolColorName(rep.color_name, rep.rgba) || '-'}
+ {resolveSpoolColorName(rep.color_name, rep.rgba, rep.color_name_is_synthesized) || '-'}
@@ -2617,7 +2632,7 @@ function SpoolCard({
>
- {resolveSpoolColorName(spool.color_name, spool.rgba) || '-'}
+ {resolveSpoolColorName(spool.color_name, spool.rgba, spool.color_name_is_synthesized) || '-'}
{onCopy && (
{
// Slicer protocol handlers can't send Authorization headers, so we mint a
- // short-lived single-use path-embedded token and hand the slicer that URL
+ // short-lived, file-bound path-embedded token and hand the slicer that URL
// instead of the auth-gated /download endpoint. Mirrors ArchivesPage's
- // ``openInSlicerWithToken`` pattern.
+ // ``openInSlicerWithToken`` pattern. The token stays valid for its whole
+ // TTL rather than for one fetch -- the slicer is a separate process and
+ // may request the URL more than once (#3029).
try {
const { token } = await api.createLibrarySlicerToken(fileId);
const path = api.getLibrarySlicerDownloadUrl(fileId, token, filename);
diff --git a/frontend/src/pages/PrintersPage.tsx b/frontend/src/pages/PrintersPage.tsx
index 5d35d6cb1..c3b9ec6e7 100644
--- a/frontend/src/pages/PrintersPage.tsx
+++ b/frontend/src/pages/PrintersPage.tsx
@@ -2,6 +2,7 @@ import { useState, useEffect, useLayoutEffect, useMemo, useRef, useCallback } fr
import { createPortal } from 'react-dom';
import { compareFwVersions } from '../utils/firmwareVersion';
import { formatPrintName } from '../utils/printName';
+import { isBedSlinger } from '../utils/bedSlinger';
import { computePopoverPosition, type PopoverPosition } from '../utils/popoverPosition';
import {
openCameraWindow,
@@ -143,7 +144,7 @@ import {
// Aliased: lucide-react already exports a `Link` icon into this module.
import { Link as RouterLink, useNavigate } from 'react-router-dom';
-import { api, discoveryApi, firmwareApi, withStreamToken, ApiError } from '../api/client';
+import { api, discoveryApi, firmwareApi, withMediaToken, ApiError } from '../api/client';
import { formatDateOnly, formatDateTime, formatETA, formatDuration, formatDurationFromHours, parseUTCDate } from '../utils/date';
import type { Printer, PrinterCreate, PrinterStatus, AMSUnit, DiscoveredPrinter, FirmwareUpdateInfo, FirmwareUploadStatus, LinkedSpoolInfo, SpoolAssignment, HMSError, InventorySpool, SmartPlug, PrinterDiagnosticResult } from '../api/client';
import { Card, CardContent } from '../components/Card';
@@ -1109,7 +1110,7 @@ export function CoverImage({
const cacheBustedUrl = useMemo(() => {
if (!url) return null;
const sep = url.includes('?') ? '&' : '?';
- return withStreamToken(`${url}${sep}v=${encodeURIComponent(printName || Date.now().toString())}`);
+ return withMediaToken(`${url}${sep}v=${encodeURIComponent(printName || Date.now().toString())}`);
}, [url, printName]);
// Re-evaluate load state when the image URL changes, and ask the element
@@ -4924,14 +4925,20 @@ function PrinterCard({
{(() => {
const canControl = hasPermission('printers:control');
const disabled = isPrinting || !canControl;
- const bambuIsPlateBelow = true; // positive Z moves plate away from nozzle
const jogButtonClass = 'flex h-8 w-8 items-center justify-center rounded bg-indigo-100 dark:bg-indigo-500/15 text-indigo-700 dark:text-indigo-300 transition-colors hover:bg-indigo-200 dark:hover:bg-indigo-500/30 disabled:cursor-not-allowed disabled:opacity-50';
- const requestZJog = (direction: 1 | -1) => {
- const signed = direction * bedJogStep * (bambuIsPlateBelow ? 1 : -1);
- // The jog never disables the soft endstops (#2579), so it's always
- // safe: the firmware clamps the move at the travel limit, or refuses
- // it if the printer isn't homed. No not-homed bypass to gate.
- bedJogMutation.mutate({ distance: signed });
+ // Which part the Z axis moves (#1334). The endpoint takes a signed
+ // nozzle-bed gap that means the same thing on every printer, so the
+ // arrows are ours to interpret: on a bed-slinger "up" lifts the
+ // toolhead and opens the gap, on a bed-on-Z printer it raises the
+ // plate toward the nozzle and closes it.
+ const zMovesToolhead = isBedSlinger(printer.model);
+ const requestZJog = (arrow: 'up' | 'down') => {
+ const opensGap = zMovesToolhead ? arrow === 'up' : arrow === 'down';
+ // No not-homed gate here, and no endstop bypass to gate either:
+ // since #2579 the jog is a bare move that never touches M211. That
+ // does not make it clamped — the firmware ignores soft endstops on
+ // MQTT G-code entirely, which is what the banner above warns about.
+ bedJogMutation.mutate({ distance: opensGap ? bedJogStep : -bedJogStep });
};
const requestXyJog = (x: number, y: number) => {
xyJogMutation.mutate({ x, y });
@@ -5023,10 +5030,10 @@ function PrinterCard({
requestZJog(-1)}
+ onClick={() => requestZJog('up')}
disabled={bedJogMutation.isPending}
className={jogButtonClass}
- aria-label={t('printers.bedJog.up')}
+ aria-label={t(zMovesToolhead ? 'printers.bedJog.toolheadUp' : 'printers.bedJog.up')}
>
@@ -5034,10 +5041,10 @@ function PrinterCard({
requestZJog(1)}
+ onClick={() => requestZJog('down')}
disabled={bedJogMutation.isPending}
className={jogButtonClass}
- aria-label={t('printers.bedJog.down')}
+ aria-label={t(zMovesToolhead ? 'printers.bedJog.toolheadDown' : 'printers.bedJog.down')}
>
@@ -5651,6 +5658,7 @@ function PrinterCard({
subtype: spoolmanSpool.subtype,
brand: spoolmanSpool.brand ?? null,
color_name: spoolmanSpool.color_name ?? null,
+ color_name_is_synthesized: spoolmanSpool.color_name_is_synthesized,
// The spool's own swatch (#2967). Spoolman carries the
// extra stops but has no effect field at all, so those
// rolls gradient and never shimmer.
@@ -6057,6 +6065,7 @@ function PrinterCard({
subtype: spoolmanSpool.subtype,
brand: spoolmanSpool.brand ?? null,
color_name: spoolmanSpool.color_name ?? null,
+ color_name_is_synthesized: spoolmanSpool.color_name_is_synthesized,
// The spool's own swatch (#2967). Spoolman carries the
// extra stops but has no effect field at all, so those
// rolls gradient and never shimmer.
@@ -6348,6 +6357,7 @@ function PrinterCard({
subtype: spoolmanSpool.subtype,
brand: spoolmanSpool.brand ?? null,
color_name: spoolmanSpool.color_name ?? null,
+ color_name_is_synthesized: spoolmanSpool.color_name_is_synthesized,
// The spool's own swatch (#2967). Spoolman carries the
// extra stops but has no effect field at all, so those
// rolls gradient and never shimmer.
diff --git a/frontend/src/pages/ProjectsPage.tsx b/frontend/src/pages/ProjectsPage.tsx
index 4a17ea3be..52c4b52fe 100644
--- a/frontend/src/pages/ProjectsPage.tsx
+++ b/frontend/src/pages/ProjectsPage.tsx
@@ -242,7 +242,7 @@ export function ProjectModal({ project, onClose, onSave, isLoading, currencySymb
{coverImageFilename ? (
diff --git a/frontend/src/pages/QueuePage.tsx b/frontend/src/pages/QueuePage.tsx
index 3671665da..6eea82bf0 100644
--- a/frontend/src/pages/QueuePage.tsx
+++ b/frontend/src/pages/QueuePage.tsx
@@ -77,6 +77,7 @@ import { useAuth } from '../contexts/AuthContext';
import { QueueStatsBar } from '../components/QueueStatsBar';
import { CompactHistoryRow } from '../components/CompactHistoryRow';
import { QueueTimelineView } from '../components/QueueTimelineView';
+import { compareQueueOrder, compareQueueOrderAcrossLanes } from '../utils/queueOrder';
import { BatchOrdersView } from '../components/BatchOrdersView';
function formatWeight(g: number, useKg = false): string {
@@ -132,6 +133,7 @@ function BulkEditModal({
onClose,
isSaving,
canControlPrinter,
+ hasGcodeSnippets,
t,
}: {
selectedCount: number;
@@ -140,12 +142,14 @@ function BulkEditModal({
onClose: () => void;
isSaving: boolean;
canControlPrinter: boolean;
+ hasGcodeSnippets: boolean;
t: (key: string, options?: Record
) => string;
}) {
const [printerId, setPrinterId] = useState('unchanged');
const [manualStart, setManualStart] = useState('unchanged');
const [autoOffAfter, setAutoOffAfter] = useState('unchanged');
const [requirePreviousSuccess, setRequirePreviousSuccess] = useState('unchanged');
+ const [gcodeInjection, setGcodeInjection] = useState('unchanged');
const [bedLevelling, setBedLevelling] = useState('unchanged');
const [flowCali, setFlowCali] = useState('unchanged');
const [vibrationCali, setVibrationCali] = useState('unchanged');
@@ -165,6 +169,7 @@ function BulkEditModal({
if (manualStart !== 'unchanged') data.manual_start = manualStart;
if (autoOffAfter !== 'unchanged') data.auto_off_after = autoOffAfter;
if (requirePreviousSuccess !== 'unchanged') data.require_previous_success = requirePreviousSuccess;
+ if (gcodeInjection !== 'unchanged') data.gcode_injection = gcodeInjection;
if (bedLevelling !== 'unchanged') data.bed_levelling = bedLevelling;
if (flowCali !== 'unchanged') data.flow_cali = flowCali;
if (vibrationCali !== 'unchanged') data.vibration_cali = vibrationCali;
@@ -178,7 +183,7 @@ function BulkEditModal({
const hasChanges = printerId !== 'unchanged' || manualStart !== 'unchanged' || autoOffAfter !== 'unchanged' ||
requirePreviousSuccess !== 'unchanged' || bedLevelling !== 'unchanged' || flowCali !== 'unchanged' ||
vibrationCali !== 'unchanged' || layerInspect !== 'unchanged' || timelapse !== 'unchanged' || useAms !== 'unchanged' ||
- nozzleOffsetCali !== 'unchanged';
+ nozzleOffsetCali !== 'unchanged' || gcodeInjection !== 'unchanged';
return (
@@ -225,6 +230,12 @@ function BulkEditModal({
+ {/* Same gate as the print modal's checkbox (#3058): hidden until an
+ admin has saved a snippet for some printer model, so the toggle
+ never promises an injection that has nothing to inject. */}
+ {hasGcodeSnippets && (
+
+ )}
@@ -664,11 +675,18 @@ function SortableQueueItem({
{isPending && !item.manual_start && (
+ {/* An item with no scheduled time used to render as "ASAP", which is the
+ name of a dispatch mode the user may well not have picked -- ASAP and
+ Queue differ only in insert position, and neither is stored on the
+ item, so the two are indistinguishable here. Someone who chose Queue
+ saw their row labelled ASAP and read it as Bambuddy overriding them
+ (#2557, #3018). This column answers "when does it run", so it now says
+ that instead of borrowing a mode name. */}
{item.scheduled_time
? ((parseUTCDate(item.scheduled_time)?.getTime() ?? 0) - Date.now() < -60000
? t?.('queue.time.overdue') ?? 'Overdue'
: formatRelativeTime(item.scheduled_time, timeFormat, t))
- : t?.('queue.time.asap') ?? 'ASAP'}
+ : t?.('queue.time.whenFree') ?? 'When a printer is free'}
)}
@@ -1777,22 +1795,7 @@ export function QueuePage() {
// When SJF is enabled, override sort to match scheduler order
if (settings?.queue_shortest_first) {
- return [...items].sort((a, b) => {
- // Group by printer first (nulls = model-based, grouped by target_model)
- const aPrinter = a.printer_id ?? -(a.target_model?.charCodeAt(0) ?? 0);
- const bPrinter = b.printer_id ?? -(b.target_model?.charCodeAt(0) ?? 0);
- if (aPrinter !== bPrinter) return aPrinter - bPrinter;
- // Within same printer/model: jumped items first (starvation guard)
- const aJumped = a.been_jumped ? 1 : 0;
- const bJumped = b.been_jumped ? 1 : 0;
- if (aJumped !== bJumped) return bJumped - aJumped;
- // Shortest print time next (nulls last)
- const aTime = a.print_time_seconds ?? Infinity;
- const bTime = b.print_time_seconds ?? Infinity;
- if (aTime !== bTime) return aTime - bTime;
- // Position as tiebreaker
- return a.position - b.position;
- });
+ return [...items].sort((a, b) => compareQueueOrderAcrossLanes(a, b, true));
}
return [...items].sort((a, b) => {
@@ -1833,10 +1836,12 @@ export function QueuePage() {
// Queue items eligible for an "if started now" ETA (#2740).
//
// The ETA answers "when would this finish if it began right now", so it may
- // only appear on items that really could begin right now. Deriving that from
- // waiting_reason alone is not enough: the scheduler only writes that field on
- // the model-based assignment path (print_scheduler.py), so an item pinned to a
- // specific printer sits behind a running job with waiting_reason still NULL.
+ // only appear on items that really could begin right now. waiting_reason now
+ // covers the pinned-printer case too (#3074), but it is still not enough on
+ // its own: it says whether the scheduler had a reason to hold the item on its
+ // last pass, not whether this item is the one that printer takes next. Two
+ // items pinned to the same free printer both come back with no reason, and
+ // only one of them can start now — which is what the ordering below works out.
//
// Computed from the unfiltered queue on purpose — hiding a printer behind the
// location filter must not make its printer look free.
@@ -1856,17 +1861,10 @@ export function QueuePage() {
// Mirrors the scheduler's own ordering so "next up" here means the item the
// scheduler would actually dispatch next, not whatever the user sorted by.
- const schedulerOrder = (a: PrintQueueItem, b: PrintQueueItem): number => {
- if (settings?.queue_shortest_first) {
- const aJumped = a.been_jumped ? 1 : 0;
- const bJumped = b.been_jumped ? 1 : 0;
- if (aJumped !== bJumped) return bJumped - aJumped;
- const aTime = a.print_time_seconds ?? Infinity;
- const bTime = b.print_time_seconds ?? Infinity;
- if (aTime !== bTime) return aTime - bTime;
- }
- return a.position - b.position;
- };
+ // Bucketed by printer immediately below, so the within-lane comparator is
+ // the right one -- no cross-lane grouping needed.
+ const schedulerOrder = (a: PrintQueueItem, b: PrintQueueItem): number =>
+ compareQueueOrder(a, b, settings?.queue_shortest_first ?? false);
// Claimants for each printer, in the order the scheduler would take them.
// Staged and future-scheduled items are excluded: the scheduler skips both
@@ -2550,6 +2548,7 @@ export function QueuePage() {
queueItems={queue || []}
printers={printers || []}
printerStatuses={printerStatusMap}
+ sjfEnabled={settings?.queue_shortest_first ?? false}
onItemClick={(item) => {
if (['completed', 'failed', 'skipped', 'cancelled'].includes(item.status)) {
setRequeueItem(item);
@@ -2959,6 +2958,7 @@ export function QueuePage() {
onClose={() => setShowBulkEditModal(false)}
isSaving={bulkUpdateMutation.isPending}
canControlPrinter={hasPermission('printers:control')}
+ hasGcodeSnippets={!!settings?.gcode_snippets}
t={t}
/>
)}
diff --git a/frontend/src/pages/SettingsPage.tsx b/frontend/src/pages/SettingsPage.tsx
index b650e7440..d45b0baf0 100644
--- a/frontend/src/pages/SettingsPage.tsx
+++ b/frontend/src/pages/SettingsPage.tsx
@@ -1081,6 +1081,10 @@ export function SettingsPage() {
// re-compare the updated `settings` with current `localSettings` and
// debounce-save any remaining differences.
queryClient.invalidateQueries({ queryKey: ['archiveStats'] });
+ // /settings/ui-flags serves currency and the sidebar gates to users who
+ // cannot read /settings. Nothing invalidated it, so a currency change
+ // sat behind that query's own staleTime instead of showing up (#3123).
+ queryClient.invalidateQueries({ queryKey: ['ui-flags'] });
showToast(t('settings.toast.settingsSaved'), 'success');
},
onError: (error: Error) => {
diff --git a/frontend/src/pages/spoolbuddy/SpoolBuddyAmsPage.tsx b/frontend/src/pages/spoolbuddy/SpoolBuddyAmsPage.tsx
index 782da7b33..97ca07631 100644
--- a/frontend/src/pages/spoolbuddy/SpoolBuddyAmsPage.tsx
+++ b/frontend/src/pages/spoolbuddy/SpoolBuddyAmsPage.tsx
@@ -7,7 +7,24 @@ import type { SpoolBuddyOutletContext } from '../../components/spoolbuddy/SpoolB
import { api } from '../../api/client';
import type { PrinterStatus, AMSTray, SpoolAssignment } from '../../api/client';
import { getGlobalTrayId, getFillBarColor, getSpoolmanFillLevel, getFallbackSpoolTag, formatSlotLabel, isBambuLabSpool, resolveSlotNozzleDiameter } from '../../utils/amsHelpers';
-import { getSwatchStyle } from '../../utils/colors';
+import { getSwatchStyle, resolveSpoolColorName } from '../../utils/colors';
+
+/**
+ * " - Candy Red", or nothing when the colour has no name we can show.
+ *
+ * Not `spool.color_name`: a Bambu tag often carries no colour name, and
+ * Spoolman has no field for one at all, so the stored value is frequently
+ * empty or the spool's subtype standing in for it. The catalog resolves the
+ * swatch's own hex instead (#3090).
+ */
+function colorNameSuffix(spool: {
+ color_name: string | null;
+ rgba: string | null;
+ color_name_is_synthesized?: boolean;
+}): string {
+ const name = resolveSpoolColorName(spool.color_name, spool.rgba, spool.color_name_is_synthesized);
+ return name ? ` - ${name}` : '';
+}
import { AmsUnitCard, HumidityIndicator, TemperatureIndicator, NozzleBadge } from '../../components/spoolbuddy/AmsUnitCard';
import type { AmsThresholds } from '../../components/spoolbuddy/AmsUnitCard';
import { ConfigureAmsSlotModal } from '../../components/ConfigureAmsSlotModal';
@@ -757,7 +774,7 @@ export function SpoolBuddyAmsPage() {
{assignment.spool.brand ? `${assignment.spool.brand} ` : ''}{assignment.spool.material}
{assignment.spool.subtype ? ` ${assignment.spool.subtype}` : ''}
- {assignment.spool.color_name ? ` - ${assignment.spool.color_name}` : ''}
+ {colorNameSuffix(assignment.spool)}
#{assignment.spool.id}
@@ -791,7 +808,7 @@ export function SpoolBuddyAmsPage() {
{spoolmanAssignedSpool.brand ? `${spoolmanAssignedSpool.brand} ` : ''}{spoolmanAssignedSpool.material}
{spoolmanAssignedSpool.subtype ? ` ${spoolmanAssignedSpool.subtype}` : ''}
- {spoolmanAssignedSpool.color_name ? ` - ${spoolmanAssignedSpool.color_name}` : ''}
+ {colorNameSuffix(spoolmanAssignedSpool)}
diff --git a/frontend/src/pages/spoolbuddy/SpoolBuddyDashboard.tsx b/frontend/src/pages/spoolbuddy/SpoolBuddyDashboard.tsx
index 18ea3166b..469b3749f 100644
--- a/frontend/src/pages/spoolbuddy/SpoolBuddyDashboard.tsx
+++ b/frontend/src/pages/spoolbuddy/SpoolBuddyDashboard.tsx
@@ -3,7 +3,7 @@ import { useOutletContext } from 'react-router-dom';
import { useQuery, useQueries, useMutation, useQueryClient } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import type { SpoolBuddyOutletContext } from '../../components/spoolbuddy/SpoolBuddyLayout';
-import { api, type InventorySpool, type Printer, type PrinterStatus } from '../../api/client';
+import { api, ApiError, type InventorySpool, type Printer, type PrinterStatus } from '../../api/client';
import type { MatchedSpool } from '../../hooks/useSpoolBuddyState';
import { useToast } from '../../contexts/ToastContext';
import { SpoolIcon } from '../../components/spoolbuddy/SpoolIcon';
@@ -293,6 +293,7 @@ export function SpoolBuddyDashboard() {
material: m.material,
subtype: m.subtype,
color_name: m.color_name,
+ color_name_is_synthesized: m.color_name_is_synthesized,
rgba: m.rgba,
brand: m.brand,
label_weight: m.label_weight,
@@ -362,8 +363,22 @@ export function SpoolBuddyDashboard() {
showToast(t('spoolman.linkFailed'), 'error');
return;
}
- const { id, material, subtype, color_name, rgba, brand, label_weight, core_weight, weight_used } = updated;
- setJustLinkedSpool({ id, material, subtype, color_name, rgba, brand, label_weight, core_weight, weight_used });
+ const { id, material, subtype, color_name, color_name_is_synthesized, rgba, brand, label_weight, core_weight, weight_used } =
+ updated;
+ // color_name_is_synthesized travels with color_name or the card
+ // shows "Silk+" where the catalog knows the colour (#3090).
+ setJustLinkedSpool({
+ id,
+ material,
+ subtype,
+ color_name,
+ color_name_is_synthesized,
+ rgba,
+ brand,
+ label_weight,
+ core_weight,
+ weight_used,
+ });
showToast(t('spoolman.linkSuccess'), 'success');
} else {
await api.linkTagToSpool(spool.id, {
@@ -375,7 +390,18 @@ export function SpoolBuddyDashboard() {
refetchSpools();
} catch (e) {
console.error('Failed to link tag:', e);
- showToast(t('spoolman.linkFailed'), 'error');
+ // The tag is already on another spool -- name it, so the operator can
+ // walk to that spool instead of retrying a scan that cannot succeed.
+ // Both inventory modes answer this with the same structured 409 (#3110);
+ // every other failure keeps the generic toast.
+ const holder =
+ e instanceof ApiError && e.status === 409 && e.code === 'tag_already_linked'
+ ? e.detail?.spool_id
+ : undefined;
+ showToast(
+ typeof holder === 'number' ? t('inventory.tagAlreadyLinked', { id: holder }) : t('spoolman.linkFailed'),
+ 'error',
+ );
} finally {
setShowLinkModal(false);
}
@@ -619,6 +645,7 @@ export function SpoolBuddyDashboard() {
material: s.material,
subtype: s.subtype,
color_name: s.color_name,
+ color_name_is_synthesized: s.color_name_is_synthesized,
rgba: s.rgba,
brand: s.brand,
label_weight: s.label_weight,
diff --git a/frontend/src/pages/spoolbuddy/SpoolBuddyInventoryPage.tsx b/frontend/src/pages/spoolbuddy/SpoolBuddyInventoryPage.tsx
index 87adb2050..a5b051832 100644
--- a/frontend/src/pages/spoolbuddy/SpoolBuddyInventoryPage.tsx
+++ b/frontend/src/pages/spoolbuddy/SpoolBuddyInventoryPage.tsx
@@ -8,6 +8,7 @@ import type { InventorySpool } from '../../api/client';
import { resolveSpoolColorName, getSwatchStyle, spoolColorString } from '../../utils/colors';
import { formatSlotLabel } from '../../utils/amsHelpers';
import { filterSpoolsByQuery } from '../../utils/inventorySearch';
+import { useColorCatalogVersion } from '../../hooks/useColorCatalogVersion';
import { InventorySpoolInfoCard } from '../../components/spoolbuddy/InventorySpoolInfoCard';
import { AssignToAmsModal } from '../../components/spoolbuddy/AssignToAmsModal';
import type { SpoolBuddyOutletContext } from '../../components/spoolbuddy/SpoolBuddyLayout';
@@ -65,6 +66,9 @@ function SpoolCircle({ color, size = 56 }: { color: string; size?: number }) {
export function SpoolBuddyInventoryPage() {
const { sbState, selectedPrinterId } = useOutletContext
();
const { t } = useTranslation();
+ // The spool filter below resolves colour names through the catalog; its
+ // memo has to recompute when the catalog finishes loading (#3090).
+ const colorCatalogVersion = useColorCatalogVersion();
const { showToast } = useToast();
const queryClient = useQueryClient();
const [searchQuery, setSearchQuery] = useState('');
@@ -137,6 +141,11 @@ export function SpoolBuddyInventoryPage() {
// Filter and sort
const filteredSpools = useMemo(() => {
+ // Named so this memo depends on it: the search below resolves colour
+ // names through the catalog, which `resolveSpoolColorName` reads from
+ // module state the linter cannot follow. Without it a query typed
+ // before the catalog loads keeps its empty result (#3090).
+ void colorCatalogVersion;
let list = activeSpools;
if (filterMode === 'in_ams') {
@@ -154,7 +163,7 @@ export function SpoolBuddyInventoryPage() {
if (aAssigned !== bAssigned) return aAssigned - bAssigned;
return new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime();
});
- }, [activeSpools, filterMode, searchQuery, assignedSpoolIds]);
+ }, [activeSpools, filterMode, searchQuery, assignedSpoolIds, colorCatalogVersion]);
return (
@@ -306,7 +315,7 @@ function CatalogCard({ spool, assignment, onClick }: {
const color = spoolColor(spool);
const pct = spoolPct(spool);
const remaining = spoolRemaining(spool);
- const colorName = resolveSpoolColorName(spool.color_name, spool.rgba);
+ const colorName = resolveSpoolColorName(spool.color_name, spool.rgba, spool.color_name_is_synthesized);
return (
();
@@ -95,6 +117,11 @@ export function SpoolBuddyWriteTagPage() {
// Filter spools based on tab
const filteredSpools = useMemo(() => {
+ // Named here so the memo actually depends on it: the search below resolves
+ // colour names through the catalog, which `displayColorName` reads from
+ // module state the linter cannot follow. Without this the list keeps the
+ // names it resolved before the catalog finished loading (#3090).
+ void colorCatalogVersion;
let list: InventorySpool[];
if (activeTab === 'existing') {
list = spools.filter(s => !s.tag_uid && !s.archived_at);
@@ -108,6 +135,9 @@ export function SpoolBuddyWriteTagPage() {
const q = searchQuery.toLowerCase();
list = list.filter(s =>
(s.material?.toLowerCase().includes(q)) ||
+ // Both: the resolved name is what the list shows, the stored one is
+ // what a user who knows Bambu's internal codes might type (#3090).
+ (displayColorName(s)?.toLowerCase().includes(q)) ||
(s.color_name?.toLowerCase().includes(q)) ||
(s.brand?.toLowerCase().includes(q)) ||
(s.subtype?.toLowerCase().includes(q))
@@ -115,7 +145,7 @@ export function SpoolBuddyWriteTagPage() {
}
return list;
- }, [spools, activeTab, searchQuery]);
+ }, [spools, activeTab, searchQuery, colorCatalogVersion]);
// Listen for tag events
const handleUnknownTag = useCallback((e: Event) => {
@@ -394,7 +424,7 @@ function SpoolListItem({ spool, selected, showTag, onClick }: {
#{spool.id}
- {spool.color_name && {spool.color_name} }
+ {displayColorName(spool) && {displayColorName(spool)} }
{remaining}g / {spool.label_weight}g ({pct}%)
{showTag && spool.tag_uid && (
@@ -805,7 +835,9 @@ function NewSpoolTouchForm({ currencySymbol, onCreated, selectedSpool, spoolmanM
{selectedSpool.brand ? `${selectedSpool.brand} ` : ''}{selectedSpool.material}
- {selectedSpool.color_name && {selectedSpool.color_name}
}
+ {displayColorName(selectedSpool) && (
+ {displayColorName(selectedSpool)}
+ )}
{selectedSpool.label_weight}g
{t('spoolbuddy.writeTag.spoolCreated', 'Spool created! Ready to write.')}
@@ -996,7 +1028,9 @@ function NewSpoolTouchForm({ currencySymbol, onCreated, selectedSpool, spoolmanM
{selectedSpool.brand ? `${selectedSpool.brand} ` : ''}{selectedSpool.material}
- {selectedSpool.color_name &&