From d85cd6b18e7269de3ef12751dd1662bffbe8ad12 Mon Sep 17 00:00:00 2001 From: u8array Date: Fri, 8 May 2026 18:09:11 +0200 Subject: [PATCH 1/6] feat(preview): Labelary privacy notice and per-service gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ZPL labels can carry sensitive payloads (patient/serial/lot data); until now the preview silently sent the full ZPL to api.labelary.com without any user-facing acknowledgement. Adds: - store.thirdParty.labelary gate (build-time default via VITE_THIRD_PARTY_LABELARY env, runtime-overridable; persisted) - one-shot privacy notice on first preview open, persisted via labelaryNoticeAcknowledged - permanent footer link 'Preview via api.labelary.com' in the modal - disabled-state placeholder for when the gate is off (forward-looking for Tauri/Docker builds where preview will be off by default) - 6 new locale strings across all 32 locales The shape (per-service object) anticipates future services (BrowserPrint, …) without forcing a state migration. --- src/components/Output/LabelPreview.tsx | 61 +++++++++++++++++++++++--- src/locales/ar.ts | 6 +++ src/locales/bg.ts | 6 +++ src/locales/cs.ts | 6 +++ src/locales/da.ts | 6 +++ src/locales/de.ts | 6 +++ src/locales/el.ts | 6 +++ src/locales/en.ts | 6 +++ src/locales/es.ts | 6 +++ src/locales/et.ts | 6 +++ src/locales/fa.ts | 6 +++ src/locales/fi.ts | 6 +++ src/locales/fr.ts | 6 +++ src/locales/he.ts | 6 +++ src/locales/hr.ts | 6 +++ src/locales/hu.ts | 6 +++ src/locales/it.ts | 6 +++ src/locales/ja.ts | 6 +++ src/locales/ko.ts | 6 +++ src/locales/lt.ts | 6 +++ src/locales/lv.ts | 6 +++ src/locales/nl.ts | 6 +++ src/locales/no.ts | 6 +++ src/locales/pl.ts | 6 +++ src/locales/pt.ts | 6 +++ src/locales/ro.ts | 6 +++ src/locales/sk.ts | 6 +++ src/locales/sl.ts | 6 +++ src/locales/sr.ts | 6 +++ src/locales/sv.ts | 6 +++ src/locales/tr.ts | 6 +++ src/locales/zh-hans.ts | 6 +++ src/locales/zh-hant.ts | 6 +++ src/store/labelStore.ts | 25 +++++++++++ 34 files changed, 271 insertions(+), 7 deletions(-) diff --git a/src/components/Output/LabelPreview.tsx b/src/components/Output/LabelPreview.tsx index 6c0b71f9..584c3829 100644 --- a/src/components/Output/LabelPreview.tsx +++ b/src/components/Output/LabelPreview.tsx @@ -15,31 +15,40 @@ export function LabelPreviewModal({ onClose }: Props) { const t = useT(); const label = useLabelStore((s) => s.label); const objects = useCurrentObjects(); + const labelaryEnabled = useLabelStore((s) => s.thirdParty.labelary); + const noticeAcknowledged = useLabelStore((s) => s.labelaryNoticeAcknowledged); + const acknowledgeLabelaryNotice = useLabelStore((s) => s.acknowledgeLabelaryNotice); + + // Notice gates the network call: until the user dismisses the first-run + // privacy hint we render the notice instead of contacting Labelary. + const showNotice = labelaryEnabled && !noticeAcknowledged; + const canFetch = labelaryEnabled && noticeAcknowledged; + const [previewUrl, setPreviewUrl] = useState(null); - const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const urlRef = useRef(null); const zplRef = useRef(generateZPL(label, objects)); + // Derived: fetch is in flight while we have neither result nor error yet. + const loading = canFetch && !previewUrl && !error; useEffect(() => { + if (!canFetch) return; let cancelled = false; fetchPreview(zplRef.current, label) .then((url) => { if (cancelled) { URL.revokeObjectURL(url); return; } urlRef.current = url; setPreviewUrl(url); - setLoading(false); }) .catch((e: unknown) => { if (cancelled) return; setError(labelaryErrorMessage(e)); - setLoading(false); }); return () => { cancelled = true; if (urlRef.current) URL.revokeObjectURL(urlRef.current); }; - }, []); // eslint-disable-line react-hooks/exhaustive-deps + }, [canFetch]); // eslint-disable-line react-hooks/exhaustive-deps const handleDownloadFallback = () => { triggerDownload(new Blob([zplRef.current], { type: 'text/plain' }), 'label.zpl'); @@ -72,10 +81,35 @@ export function LabelPreviewModal({ onClose }: Props) { viewport so small previews are still centered. */}
- {loading && ( + {showNotice && ( +
+ {t.output.previewNoticeTitle} + {t.output.previewNoticeBody} + + {t.output.previewNoticePrivacyLink} + + +
+ )} + {!labelaryEnabled && ( + + {t.output.previewDisabled} + + )} + {canFetch && loading && ( {t.output.loading} )} - {!loading && error && ( + {canFetch && !loading && error && (
{error}
)} - {!loading && !error && previewUrl && ( + {canFetch && !loading && !error && previewUrl && ( Label preview
+ + {labelaryEnabled && ( + + )}
); diff --git a/src/locales/ar.ts b/src/locales/ar.ts index 2e86692e..9faa9f05 100644 --- a/src/locales/ar.ts +++ b/src/locales/ar.ts @@ -118,6 +118,12 @@ const ar = { loading: 'جارٍ التحميل…', unavailable: 'غير متاح', previewEmpty: 'تظهر المعاينة\nبعد إجراء تغييرات', + previewProvider: 'معاينة عبر api.labelary.com', + previewDisabled: 'المعاينة معطلة.', + previewNoticeTitle: 'إشعار الخصوصية', + previewNoticeBody: 'يتم إنشاء المعاينة بواسطة الخدمة الخارجية api.labelary.com. يتم إرسال ZPL الكامل للملصق، بما في ذلك أي بيانات حساسة، عبر الشبكة.', + previewNoticePrivacyLink: 'معلومات خصوصية Labelary', + previewNoticeAcknowledge: 'فهمت، تابع', }, registry: { diff --git a/src/locales/bg.ts b/src/locales/bg.ts index 0111685b..6f50db44 100644 --- a/src/locales/bg.ts +++ b/src/locales/bg.ts @@ -118,6 +118,12 @@ const bg = { loading: 'Зарежда…', unavailable: 'Недостъпно', previewEmpty: 'Преглед се появява\nслед промени', + previewProvider: 'Преглед чрез api.labelary.com', + previewDisabled: 'Прегледът е изключен.', + previewNoticeTitle: 'Известие за поверителност', + previewNoticeBody: 'Прегледът се изобразява от външната услуга api.labelary.com. Целият ZPL на етикета, включително чувствителните данни, се изпраща през мрежата.', + previewNoticePrivacyLink: 'Информация за поверителност на Labelary', + previewNoticeAcknowledge: 'Разбрах, продължи', }, registry: { diff --git a/src/locales/cs.ts b/src/locales/cs.ts index cf8c3aca..4881863c 100644 --- a/src/locales/cs.ts +++ b/src/locales/cs.ts @@ -118,6 +118,12 @@ const cs = { loading: 'Načítání…', unavailable: 'Nedostupné', previewEmpty: 'Náhled se zobrazí\npo úpravách', + previewProvider: 'Náhled přes api.labelary.com', + previewDisabled: 'Náhled je vypnutý.', + previewNoticeTitle: 'Upozornění na ochranu údajů', + previewNoticeBody: 'Náhled se generuje pomocí externí služby api.labelary.com. Celý ZPL štítku, včetně citlivých údajů, je odesílán přes síť.', + previewNoticePrivacyLink: 'Informace o ochraně údajů Labelary', + previewNoticeAcknowledge: 'Rozumím, pokračovat', }, registry: { diff --git a/src/locales/da.ts b/src/locales/da.ts index 6fa8e006..bd2f9df2 100644 --- a/src/locales/da.ts +++ b/src/locales/da.ts @@ -118,6 +118,12 @@ const da = { loading: 'Indlæser…', unavailable: 'Ikke tilgængelig', previewEmpty: 'Forhåndsvisning vises\nefter ændringer', + previewProvider: 'Forhåndsvisning via api.labelary.com', + previewDisabled: 'Forhåndsvisning er deaktiveret.', + previewNoticeTitle: 'Privatlivsmeddelelse', + previewNoticeBody: 'Forhåndsvisningen genereres af den eksterne tjeneste api.labelary.com. Hele etikettens ZPL, inklusive følsomme data, sendes over netværket.', + previewNoticePrivacyLink: 'Labelary-privatlivsoplysninger', + previewNoticeAcknowledge: 'Forstået, fortsæt', }, registry: { diff --git a/src/locales/de.ts b/src/locales/de.ts index 536b13f7..30f3c804 100644 --- a/src/locales/de.ts +++ b/src/locales/de.ts @@ -138,6 +138,12 @@ const de = { loading: 'Lädt…', unavailable: 'Nicht verfügbar', previewEmpty: 'Vorschau erscheint\nnach Änderungen', + previewProvider: 'Vorschau über api.labelary.com', + previewDisabled: 'Vorschau deaktiviert.', + previewNoticeTitle: 'Datenschutzhinweis', + previewNoticeBody: 'Die Vorschau wird vom externen Dienst api.labelary.com erstellt. Dabei wird das vollständige Label-ZPL inklusive sensibler Daten übertragen.', + previewNoticePrivacyLink: 'Labelary-Datenschutzhinweise', + previewNoticeAcknowledge: 'Verstanden, fortfahren', }, registry: { diff --git a/src/locales/el.ts b/src/locales/el.ts index 3562a154..49471523 100644 --- a/src/locales/el.ts +++ b/src/locales/el.ts @@ -118,6 +118,12 @@ const el = { loading: 'Φόρτωση…', unavailable: 'Μη διαθέσιμο', previewEmpty: 'Η προεπισκόπηση εμφανίζεται\nμετά τις αλλαγές', + previewProvider: 'Προεπισκόπηση μέσω api.labelary.com', + previewDisabled: 'Η προεπισκόπηση είναι απενεργοποιημένη.', + previewNoticeTitle: 'Ειδοποίηση απορρήτου', + previewNoticeBody: 'Η προεπισκόπηση δημιουργείται από την εξωτερική υπηρεσία api.labelary.com. Ολόκληρο το ZPL της ετικέτας, συμπεριλαμβανομένων ευαίσθητων δεδομένων, αποστέλλεται μέσω δικτύου.', + previewNoticePrivacyLink: 'Πληροφορίες απορρήτου Labelary', + previewNoticeAcknowledge: 'Κατάλαβα, συνέχεια', }, registry: { diff --git a/src/locales/en.ts b/src/locales/en.ts index c8f319f9..697a7b55 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -138,6 +138,12 @@ const en = { loading: 'Loading…', unavailable: 'Unavailable', previewEmpty: 'Preview appears\nafter changes', + previewProvider: 'Preview via api.labelary.com', + previewDisabled: 'Preview is disabled.', + previewNoticeTitle: 'Privacy notice', + previewNoticeBody: 'Preview rendering is performed by the external service api.labelary.com. The full label ZPL, including any sensitive data, is sent over the network.', + previewNoticePrivacyLink: 'Labelary privacy information', + previewNoticeAcknowledge: 'Got it, continue', }, registry: { diff --git a/src/locales/es.ts b/src/locales/es.ts index f90984ba..d72d36d7 100644 --- a/src/locales/es.ts +++ b/src/locales/es.ts @@ -118,6 +118,12 @@ const es = { loading: 'Cargando…', unavailable: 'No disponible', previewEmpty: 'La vista previa aparece\ntras los cambios', + previewProvider: 'Vista previa vía api.labelary.com', + previewDisabled: 'Vista previa desactivada.', + previewNoticeTitle: 'Aviso de privacidad', + previewNoticeBody: 'La previsualización se genera mediante el servicio externo api.labelary.com. Se envía el ZPL completo de la etiqueta, incluidos los datos sensibles.', + previewNoticePrivacyLink: 'Información de privacidad de Labelary', + previewNoticeAcknowledge: 'Entendido, continuar', }, registry: { diff --git a/src/locales/et.ts b/src/locales/et.ts index 7e25a0d8..63152a2c 100644 --- a/src/locales/et.ts +++ b/src/locales/et.ts @@ -118,6 +118,12 @@ const et = { loading: 'Laadimine…', unavailable: 'Pole saadaval', previewEmpty: 'Eelvaade kuvatakse\npärast muudatusi', + previewProvider: 'Eelvaade api.labelary.com kaudu', + previewDisabled: 'Eelvaade on välja lülitatud.', + previewNoticeTitle: 'Privaatsusteatis', + previewNoticeBody: 'Eelvaate genereerib väline teenus api.labelary.com. Sildi terve ZPL, sealhulgas tundlikud andmed, saadetakse võrgu kaudu.', + previewNoticePrivacyLink: 'Labelary privaatsusteave', + previewNoticeAcknowledge: 'Selge, jätka', }, registry: { diff --git a/src/locales/fa.ts b/src/locales/fa.ts index f994a9dd..f1a934b4 100644 --- a/src/locales/fa.ts +++ b/src/locales/fa.ts @@ -118,6 +118,12 @@ const fa = { loading: 'در حال بارگذاری…', unavailable: 'در دسترس نیست', previewEmpty: 'پیش‌نمایش پس از\nتغییرات نمایش داده می‌شود', + previewProvider: 'پیش‌نمایش از طریق api.labelary.com', + previewDisabled: 'پیش‌نمایش غیرفعال است.', + previewNoticeTitle: 'اطلاعیه حریم خصوصی', + previewNoticeBody: 'پیش‌نمایش توسط سرویس خارجی api.labelary.com تولید می‌شود. ZPL کامل برچسب، شامل داده‌های حساس، از طریق شبکه ارسال می‌شود.', + previewNoticePrivacyLink: 'اطلاعات حریم خصوصی Labelary', + previewNoticeAcknowledge: 'متوجه شدم، ادامه', }, registry: { diff --git a/src/locales/fi.ts b/src/locales/fi.ts index 2a9b4685..cbecff3d 100644 --- a/src/locales/fi.ts +++ b/src/locales/fi.ts @@ -118,6 +118,12 @@ const fi = { loading: 'Ladataan…', unavailable: 'Ei saatavilla', previewEmpty: 'Esikatselu näkyy\nmuutosten jälkeen', + previewProvider: 'Esikatselu palvelusta api.labelary.com', + previewDisabled: 'Esikatselu on poistettu käytöstä.', + previewNoticeTitle: 'Tietosuojailmoitus', + previewNoticeBody: 'Esikatselun tuottaa ulkoinen palvelu api.labelary.com. Etiketin koko ZPL, mukaan lukien arkaluonteiset tiedot, lähetetään verkon yli.', + previewNoticePrivacyLink: 'Labelary-tietosuojatiedot', + previewNoticeAcknowledge: 'Selvä, jatka', }, registry: { diff --git a/src/locales/fr.ts b/src/locales/fr.ts index bccc3b38..773d4672 100644 --- a/src/locales/fr.ts +++ b/src/locales/fr.ts @@ -118,6 +118,12 @@ const fr = { loading: 'Chargement…', unavailable: 'Indisponible', previewEmpty: "L'aperçu apparaît\naprès les modifications", + previewProvider: 'Aperçu via api.labelary.com', + previewDisabled: 'Aperçu désactivé.', + previewNoticeTitle: 'Avis de confidentialité', + previewNoticeBody: 'Le rendu de l\'aperçu est effectué par le service externe api.labelary.com. Le ZPL complet de l\'étiquette, y compris toute donnée sensible, est envoyé sur le réseau.', + previewNoticePrivacyLink: 'Informations sur la confidentialité de Labelary', + previewNoticeAcknowledge: 'Compris, continuer', }, registry: { diff --git a/src/locales/he.ts b/src/locales/he.ts index 1efa18cf..6db7f86c 100644 --- a/src/locales/he.ts +++ b/src/locales/he.ts @@ -118,6 +118,12 @@ const he = { loading: 'טוען…', unavailable: 'לא זמין', previewEmpty: 'התצוגה המקדימה מופיעה\nלאחר שינויים', + previewProvider: 'תצוגה מקדימה דרך api.labelary.com', + previewDisabled: 'התצוגה המקדימה מושבתת.', + previewNoticeTitle: 'הודעת פרטיות', + previewNoticeBody: 'התצוגה המקדימה מופקת על ידי השירות החיצוני api.labelary.com. ה-ZPL המלא של התווית, כולל נתונים רגישים, נשלח ברשת.', + previewNoticePrivacyLink: 'מידע פרטיות של Labelary', + previewNoticeAcknowledge: 'הבנתי, המשך', }, registry: { diff --git a/src/locales/hr.ts b/src/locales/hr.ts index d5b5244e..a9c25fdf 100644 --- a/src/locales/hr.ts +++ b/src/locales/hr.ts @@ -118,6 +118,12 @@ const hr = { loading: 'Učitavanje…', unavailable: 'Nedostupno', previewEmpty: 'Pregled se prikazuje\nnakon promjena', + previewProvider: 'Pregled putem api.labelary.com', + previewDisabled: 'Pregled je onemogućen.', + previewNoticeTitle: 'Obavijest o privatnosti', + previewNoticeBody: 'Pregled generira vanjska usluga api.labelary.com. Cijeli ZPL naljepnice, uključujući osjetljive podatke, šalje se putem mreže.', + previewNoticePrivacyLink: 'Informacije o privatnosti Labelary', + previewNoticeAcknowledge: 'Razumijem, nastavi', }, registry: { diff --git a/src/locales/hu.ts b/src/locales/hu.ts index f87c9cab..ca749b4f 100644 --- a/src/locales/hu.ts +++ b/src/locales/hu.ts @@ -118,6 +118,12 @@ const hu = { loading: 'Betöltés…', unavailable: 'Nem elérhető', previewEmpty: 'Az előnézet megjelenik\nmódosítás után', + previewProvider: 'Előnézet az api.labelary.com útján', + previewDisabled: 'Az előnézet ki van kapcsolva.', + previewNoticeTitle: 'Adatvédelmi tájékoztató', + previewNoticeBody: 'Az előnézetet a külső api.labelary.com szolgáltatás készíti. A címke teljes ZPL-je, beleértve a bizalmas adatokat is, hálózaton keresztül kerül elküldésre.', + previewNoticePrivacyLink: 'Labelary adatvédelmi tájékoztató', + previewNoticeAcknowledge: 'Értem, folytatás', }, registry: { diff --git a/src/locales/it.ts b/src/locales/it.ts index 0db72e7f..ff02ebda 100644 --- a/src/locales/it.ts +++ b/src/locales/it.ts @@ -118,6 +118,12 @@ const it = { loading: 'Caricamento…', unavailable: 'Non disponibile', previewEmpty: "L'anteprima appare\ndopo le modifiche", + previewProvider: 'Anteprima tramite api.labelary.com', + previewDisabled: 'Anteprima disattivata.', + previewNoticeTitle: 'Informativa sulla privacy', + previewNoticeBody: 'L\'anteprima è generata dal servizio esterno api.labelary.com. L\'intero ZPL dell\'etichetta, inclusi eventuali dati sensibili, viene trasmesso in rete.', + previewNoticePrivacyLink: 'Informativa sulla privacy di Labelary', + previewNoticeAcknowledge: 'Ho capito, continua', }, registry: { diff --git a/src/locales/ja.ts b/src/locales/ja.ts index 4ead9c31..3f58f4d0 100644 --- a/src/locales/ja.ts +++ b/src/locales/ja.ts @@ -118,6 +118,12 @@ const ja = { loading: '読み込み中…', unavailable: '利用不可', previewEmpty: '変更後にプレビューが\n表示されます', + previewProvider: 'api.labelary.com 経由のプレビュー', + previewDisabled: 'プレビューは無効です。', + previewNoticeTitle: 'プライバシーに関する通知', + previewNoticeBody: 'プレビューは外部サービス api.labelary.com で生成されます。機密データを含むラベル全体の ZPL がネットワーク経由で送信されます。', + previewNoticePrivacyLink: 'Labelary のプライバシー情報', + previewNoticeAcknowledge: '了解しました、続行', }, registry: { diff --git a/src/locales/ko.ts b/src/locales/ko.ts index b5361322..f3b74d0c 100644 --- a/src/locales/ko.ts +++ b/src/locales/ko.ts @@ -118,6 +118,12 @@ const ko = { loading: '로딩 중…', unavailable: '사용 불가', previewEmpty: '변경 후 미리보기가\n표시됩니다', + previewProvider: 'api.labelary.com을(를) 통한 미리 보기', + previewDisabled: '미리 보기가 비활성화되어 있습니다.', + previewNoticeTitle: '개인정보 보호 고지', + previewNoticeBody: '미리 보기는 외부 서비스 api.labelary.com에서 생성됩니다. 민감한 데이터를 포함한 전체 라벨 ZPL이 네트워크를 통해 전송됩니다.', + previewNoticePrivacyLink: 'Labelary 개인정보 보호 정보', + previewNoticeAcknowledge: '이해했습니다, 계속', }, registry: { diff --git a/src/locales/lt.ts b/src/locales/lt.ts index cb573169..cd3d218d 100644 --- a/src/locales/lt.ts +++ b/src/locales/lt.ts @@ -118,6 +118,12 @@ const lt = { loading: 'Įkeliama…', unavailable: 'Nepasiekiama', previewEmpty: 'Peržiūra rodoma\npo pakeitimų', + previewProvider: 'Peržiūra per api.labelary.com', + previewDisabled: 'Peržiūra išjungta.', + previewNoticeTitle: 'Privatumo pranešimas', + previewNoticeBody: 'Peržiūrą generuoja išorinė paslauga api.labelary.com. Visa etiketės ZPL, įskaitant jautrius duomenis, siunčiama tinklu.', + previewNoticePrivacyLink: 'Labelary privatumo informacija', + previewNoticeAcknowledge: 'Supratau, tęsti', }, registry: { diff --git a/src/locales/lv.ts b/src/locales/lv.ts index f7612a79..bb3748e0 100644 --- a/src/locales/lv.ts +++ b/src/locales/lv.ts @@ -118,6 +118,12 @@ const lv = { loading: 'Ielādē…', unavailable: 'Nav pieejams', previewEmpty: 'Priekšskatījums parādās\npēc izmaiņām', + previewProvider: 'Priekšskatījums caur api.labelary.com', + previewDisabled: 'Priekšskatījums ir atspējots.', + previewNoticeTitle: 'Paziņojums par privātumu', + previewNoticeBody: 'Priekšskatījumu ģenerē ārējais pakalpojums api.labelary.com. Visa etiķetes ZPL, tostarp sensitīvi dati, tiek sūtīta tīklā.', + previewNoticePrivacyLink: 'Labelary privātuma informācija', + previewNoticeAcknowledge: 'Saprotu, turpināt', }, registry: { diff --git a/src/locales/nl.ts b/src/locales/nl.ts index 4f5966f4..5653f014 100644 --- a/src/locales/nl.ts +++ b/src/locales/nl.ts @@ -118,6 +118,12 @@ const nl = { loading: 'Laden…', unavailable: 'Niet beschikbaar', previewEmpty: 'Voorbeeld verschijnt\nna wijzigingen', + previewProvider: 'Voorbeeld via api.labelary.com', + previewDisabled: 'Voorbeeld uitgeschakeld.', + previewNoticeTitle: 'Privacymelding', + previewNoticeBody: 'De voorbeeldweergave wordt gegenereerd door de externe dienst api.labelary.com. De volledige ZPL van het label, inclusief gevoelige gegevens, wordt verzonden.', + previewNoticePrivacyLink: 'Privacy-informatie van Labelary', + previewNoticeAcknowledge: 'Begrepen, doorgaan', }, registry: { diff --git a/src/locales/no.ts b/src/locales/no.ts index 51581d68..31a96715 100644 --- a/src/locales/no.ts +++ b/src/locales/no.ts @@ -118,6 +118,12 @@ const no = { loading: 'Laster…', unavailable: 'Utilgjengelig', previewEmpty: 'Forhåndsvisning vises\netter endringer', + previewProvider: 'Forhåndsvisning via api.labelary.com', + previewDisabled: 'Forhåndsvisning er deaktivert.', + previewNoticeTitle: 'Personvernmelding', + previewNoticeBody: 'Forhåndsvisningen genereres av den eksterne tjenesten api.labelary.com. Hele etikettens ZPL, inkludert sensitive data, sendes over nettverket.', + previewNoticePrivacyLink: 'Labelary personverninformasjon', + previewNoticeAcknowledge: 'Forstått, fortsett', }, registry: { diff --git a/src/locales/pl.ts b/src/locales/pl.ts index e116f2ff..475a0424 100644 --- a/src/locales/pl.ts +++ b/src/locales/pl.ts @@ -118,6 +118,12 @@ const pl = { loading: 'Ładowanie…', unavailable: 'Niedostępny', previewEmpty: 'Podgląd pojawi się\npo zmianach', + previewProvider: 'Podgląd przez api.labelary.com', + previewDisabled: 'Podgląd wyłączony.', + previewNoticeTitle: 'Informacja o prywatności', + previewNoticeBody: 'Podgląd jest renderowany przez zewnętrzny serwis api.labelary.com. Cały ZPL etykiety, włącznie z danymi wrażliwymi, jest wysyłany przez sieć.', + previewNoticePrivacyLink: 'Informacje o prywatności Labelary', + previewNoticeAcknowledge: 'Rozumiem, kontynuuj', }, registry: { diff --git a/src/locales/pt.ts b/src/locales/pt.ts index 20e05dc8..13498c3b 100644 --- a/src/locales/pt.ts +++ b/src/locales/pt.ts @@ -118,6 +118,12 @@ const pt = { loading: 'A carregar…', unavailable: 'Indisponível', previewEmpty: 'A pré-visualização aparece\napós alterações', + previewProvider: 'Pré-visualização via api.labelary.com', + previewDisabled: 'Pré-visualização desativada.', + previewNoticeTitle: 'Aviso de privacidade', + previewNoticeBody: 'A pré-visualização é gerada pelo serviço externo api.labelary.com. O ZPL completo da etiqueta, incluindo dados sensíveis, é enviado pela rede.', + previewNoticePrivacyLink: 'Informações de privacidade da Labelary', + previewNoticeAcknowledge: 'Entendi, continuar', }, registry: { diff --git a/src/locales/ro.ts b/src/locales/ro.ts index fa6f8c85..f4e08704 100644 --- a/src/locales/ro.ts +++ b/src/locales/ro.ts @@ -118,6 +118,12 @@ const ro = { loading: 'Se încarcă…', unavailable: 'Indisponibil', previewEmpty: 'Previzualizarea apare\ndupă modificări', + previewProvider: 'Previzualizare prin api.labelary.com', + previewDisabled: 'Previzualizarea este dezactivată.', + previewNoticeTitle: 'Notificare de confidențialitate', + previewNoticeBody: 'Previzualizarea este generată de serviciul extern api.labelary.com. Întregul ZPL al etichetei, inclusiv datele sensibile, este trimis prin rețea.', + previewNoticePrivacyLink: 'Informații despre confidențialitate Labelary', + previewNoticeAcknowledge: 'Am înțeles, continuă', }, registry: { diff --git a/src/locales/sk.ts b/src/locales/sk.ts index af84af8b..44b47552 100644 --- a/src/locales/sk.ts +++ b/src/locales/sk.ts @@ -118,6 +118,12 @@ const sk = { loading: 'Načítavanie…', unavailable: 'Nedostupné', previewEmpty: 'Náhľad sa zobrazí\npo úpravách', + previewProvider: 'Náhľad cez api.labelary.com', + previewDisabled: 'Náhľad je vypnutý.', + previewNoticeTitle: 'Upozornenie o ochrane údajov', + previewNoticeBody: 'Náhľad sa generuje pomocou externej služby api.labelary.com. Celý ZPL štítku, vrátane citlivých údajov, sa odosiela cez sieť.', + previewNoticePrivacyLink: 'Informácie o ochrane údajov Labelary', + previewNoticeAcknowledge: 'Rozumiem, pokračovať', }, registry: { diff --git a/src/locales/sl.ts b/src/locales/sl.ts index 26fe5218..ae52a3d8 100644 --- a/src/locales/sl.ts +++ b/src/locales/sl.ts @@ -118,6 +118,12 @@ const sl = { loading: 'Nalaganje…', unavailable: 'Ni na voljo', previewEmpty: 'Predogled se prikaže\npo spremembah', + previewProvider: 'Predogled prek api.labelary.com', + previewDisabled: 'Predogled je onemogočen.', + previewNoticeTitle: 'Obvestilo o zasebnosti', + previewNoticeBody: 'Predogled ustvari zunanja storitev api.labelary.com. Celoten ZPL etikete, vključno z občutljivimi podatki, se pošlje po omrežju.', + previewNoticePrivacyLink: 'Informacije o zasebnosti Labelary', + previewNoticeAcknowledge: 'Razumem, nadaljuj', }, registry: { diff --git a/src/locales/sr.ts b/src/locales/sr.ts index 1990c3dc..50b556af 100644 --- a/src/locales/sr.ts +++ b/src/locales/sr.ts @@ -118,6 +118,12 @@ const sr = { loading: 'Učitavanje…', unavailable: 'Nedostupno', previewEmpty: 'Pregled se prikazuje\nnakon promena', + previewProvider: 'Преглед преко api.labelary.com', + previewDisabled: 'Преглед је онемогућен.', + previewNoticeTitle: 'Обавештење о приватности', + previewNoticeBody: 'Преглед се приказује спољним сервисом api.labelary.com. Цео ZPL етикете, укључујући осетљиве податке, шаље се преко мреже.', + previewNoticePrivacyLink: 'Информације о приватности Labelary', + previewNoticeAcknowledge: 'Разумем, настави', }, registry: { diff --git a/src/locales/sv.ts b/src/locales/sv.ts index 548746da..e0af9356 100644 --- a/src/locales/sv.ts +++ b/src/locales/sv.ts @@ -118,6 +118,12 @@ const sv = { loading: 'Laddar…', unavailable: 'Ej tillgänglig', previewEmpty: 'Förhandsvisning visas\nefter ändringar', + previewProvider: 'Förhandsgranskning via api.labelary.com', + previewDisabled: 'Förhandsgranskning är inaktiverad.', + previewNoticeTitle: 'Integritetsmeddelande', + previewNoticeBody: 'Förhandsgranskningen renderas av den externa tjänsten api.labelary.com. Hela etikettens ZPL, inklusive känsliga data, skickas över nätverket.', + previewNoticePrivacyLink: 'Labelarys integritetsinformation', + previewNoticeAcknowledge: 'Uppfattat, fortsätt', }, registry: { diff --git a/src/locales/tr.ts b/src/locales/tr.ts index 844635b8..7f45e1f3 100644 --- a/src/locales/tr.ts +++ b/src/locales/tr.ts @@ -118,6 +118,12 @@ const tr = { loading: 'Yükleniyor…', unavailable: 'Kullanılamıyor', previewEmpty: 'Önizleme değişikliklerden\nsonra görüntülenir', + previewProvider: 'api.labelary.com üzerinden önizleme', + previewDisabled: 'Önizleme devre dışı.', + previewNoticeTitle: 'Gizlilik bildirimi', + previewNoticeBody: 'Önizleme, harici api.labelary.com hizmeti tarafından oluşturulur. Hassas veriler dahil etiketin tamamı ZPL olarak ağ üzerinden gönderilir.', + previewNoticePrivacyLink: 'Labelary gizlilik bilgileri', + previewNoticeAcknowledge: 'Anladım, devam et', }, registry: { diff --git a/src/locales/zh-hans.ts b/src/locales/zh-hans.ts index a39fff8e..a5ad24c3 100644 --- a/src/locales/zh-hans.ts +++ b/src/locales/zh-hans.ts @@ -118,6 +118,12 @@ const zhHans = { loading: '加载中…', unavailable: '不可用', previewEmpty: '更改后显示预览', + previewProvider: '通过 api.labelary.com 预览', + previewDisabled: '预览已禁用。', + previewNoticeTitle: '隐私提示', + previewNoticeBody: '预览由外部服务 api.labelary.com 生成。完整的标签 ZPL(包括敏感数据)将通过网络发送。', + previewNoticePrivacyLink: 'Labelary 隐私信息', + previewNoticeAcknowledge: '知道了,继续', }, registry: { diff --git a/src/locales/zh-hant.ts b/src/locales/zh-hant.ts index 7153c9ba..4b7d7e4f 100644 --- a/src/locales/zh-hant.ts +++ b/src/locales/zh-hant.ts @@ -118,6 +118,12 @@ const zhHant = { loading: '載入中…', unavailable: '無法使用', previewEmpty: '變更後顯示預覽', + previewProvider: '透過 api.labelary.com 預覽', + previewDisabled: '預覽已停用。', + previewNoticeTitle: '隱私聲明', + previewNoticeBody: '預覽由外部服務 api.labelary.com 產生。完整的標籤 ZPL(包含敏感資料)會透過網路傳送。', + previewNoticePrivacyLink: 'Labelary 隱私資訊', + previewNoticeAcknowledge: '了解,繼續', }, registry: { diff --git a/src/store/labelStore.ts b/src/store/labelStore.ts index 208b7979..66a92837 100644 --- a/src/store/labelStore.ts +++ b/src/store/labelStore.ts @@ -36,6 +36,15 @@ function detectInitialTheme(): 'light' | 'dark' { : 'light'; } +/** Build-time defaults for third-party services. Vite injects VITE_THIRD_PARTY_* + * env values; missing values fall back to enabled. Tauri/Docker builds can flip + * the default by setting VITE_THIRD_PARTY_LABELARY=false in their build env. */ +function thirdPartyDefaults(): { labelary: boolean } { + return { + labelary: import.meta.env.VITE_THIRD_PARTY_LABELARY !== 'false', + }; +} + export interface CanvasSettings { showGrid: boolean; snapEnabled: boolean; @@ -57,6 +66,11 @@ interface LabelState { * the explicit choice persists. */ theme: ThemePreference; canvasSettings: CanvasSettings; + /** Per-service gates for third-party network calls. Initial defaults come + * from build-time env (see thirdPartyDefaults); user choices persist. */ + thirdParty: { labelary: boolean }; + /** Whether the user has dismissed the one-time Labelary privacy notice. */ + labelaryNoticeAcknowledged: boolean; clipboard: LabelObject[]; pasteCount: number; @@ -77,6 +91,8 @@ interface LabelState { setLabelConfig: (config: Partial) => void; setLocale: (locale: LocaleCode) => void; setTheme: (theme: ThemePreference) => void; + setThirdPartyEnabled: (service: 'labelary', enabled: boolean) => void; + acknowledgeLabelaryNotice: () => void; setCanvasSettings: (settings: Partial) => void; loadDesign: (label: LabelConfig, pages: Page[]) => void; moveObjectForward: (id: string) => void; @@ -140,6 +156,8 @@ export const useLabelStore = create()( duplicateCount: 0, locale: detectLocale(), theme: detectInitialTheme(), + thirdParty: thirdPartyDefaults(), + labelaryNoticeAcknowledged: false, canvasSettings: { showGrid: false, snapEnabled: false, snapSizeMm: 1, zoom: 1, unit: 'mm', viewRotation: 0 }, addObject: (type, position = { x: 50, y: 50 }) => { @@ -362,6 +380,11 @@ export const useLabelStore = create()( setTheme: (theme) => set({ theme }), + setThirdPartyEnabled: (service, enabled) => + set((state) => ({ thirdParty: { ...state.thirdParty, [service]: enabled } })), + + acknowledgeLabelaryNotice: () => set({ labelaryNoticeAcknowledged: true }), + setCanvasSettings: (settings) => set((state) => ({ canvasSettings: { ...state.canvasSettings, ...settings } })), @@ -441,6 +464,8 @@ export const useLabelStore = create()( currentPageIndex: state.currentPageIndex, locale: state.locale, theme: state.theme, + thirdParty: state.thirdParty, + labelaryNoticeAcknowledged: state.labelaryNoticeAcknowledged, canvasSettings: state.canvasSettings, }), } From 27b89dd215d390c9ade25ab6fde4599365dc4022 Mon Sep 17 00:00:00 2001 From: u8array Date: Fri, 8 May 2026 18:16:16 +0200 Subject: [PATCH 2/6] refactor(preview): hide entry points instead of disabled placeholder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both the Preview button (ZPLOutput) and the Print menu item (AppShell) go through Labelary; gate them on thirdParty.labelary so the dead end 'preview disabled' state inside the modal is no longer reachable. Removes the disabled-state branch from LabelPreviewModal and drops the now-unused previewDisabled locale key from all 32 locales. The modal flow simplifies to two states: notice → fetch. When a settings UI lands later, toggling labelary back on restores the entry points without further code changes. --- src/components/AppShell.tsx | 17 +++++++----- src/components/Output/LabelPreview.tsx | 38 ++++++++++---------------- src/components/Output/ZPLOutput.tsx | 21 ++++++++------ src/locales/ar.ts | 1 - src/locales/bg.ts | 1 - src/locales/cs.ts | 1 - src/locales/da.ts | 1 - src/locales/de.ts | 1 - src/locales/el.ts | 1 - src/locales/en.ts | 1 - src/locales/es.ts | 1 - src/locales/et.ts | 1 - src/locales/fa.ts | 1 - src/locales/fi.ts | 1 - src/locales/fr.ts | 1 - src/locales/he.ts | 1 - src/locales/hr.ts | 1 - src/locales/hu.ts | 1 - src/locales/it.ts | 1 - src/locales/ja.ts | 1 - src/locales/ko.ts | 1 - src/locales/lt.ts | 1 - src/locales/lv.ts | 1 - src/locales/nl.ts | 1 - src/locales/no.ts | 1 - src/locales/pl.ts | 1 - src/locales/pt.ts | 1 - src/locales/ro.ts | 1 - src/locales/sk.ts | 1 - src/locales/sl.ts | 1 - src/locales/sr.ts | 1 - src/locales/sv.ts | 1 - src/locales/tr.ts | 1 - src/locales/zh-hans.ts | 1 - src/locales/zh-hant.ts | 1 - 35 files changed, 37 insertions(+), 71 deletions(-) diff --git a/src/components/AppShell.tsx b/src/components/AppShell.tsx index 0737359a..4e0abff9 100644 --- a/src/components/AppShell.tsx +++ b/src/components/AppShell.tsx @@ -50,6 +50,7 @@ export function AppShell() { const setLocale = useLabelStore((s) => s.setLocale); const theme = useLabelStore((s) => s.theme); const setTheme = useLabelStore((s) => s.setTheme); + const labelaryEnabled = useLabelStore((s) => s.thirdParty.labelary); // Bridge the theme preference to so the CSS variables in // index.css pick it up. @@ -201,13 +202,15 @@ export function AppShell() { {t.app.saveDesign} - - {t.app.print} - + {labelaryEnabled && ( + + {t.app.print} + + )} s.label); const objects = useCurrentObjects(); - const labelaryEnabled = useLabelStore((s) => s.thirdParty.labelary); const noticeAcknowledged = useLabelStore((s) => s.labelaryNoticeAcknowledged); const acknowledgeLabelaryNotice = useLabelStore((s) => s.acknowledgeLabelaryNotice); - // Notice gates the network call: until the user dismisses the first-run - // privacy hint we render the notice instead of contacting Labelary. - const showNotice = labelaryEnabled && !noticeAcknowledged; - const canFetch = labelaryEnabled && noticeAcknowledged; + // The modal entry point is hidden when the labelary gate is off + // (see ZPLOutput), so reaching this component means the gate is on. + // The first-run notice gates the network call until acknowledged. + const canFetch = noticeAcknowledged; const [previewUrl, setPreviewUrl] = useState(null); const [error, setError] = useState(null); @@ -81,7 +80,7 @@ export function LabelPreviewModal({ onClose }: Props) { viewport so small previews are still centered. */}
- {showNotice && ( + {!noticeAcknowledged && (
{t.output.previewNoticeTitle} {t.output.previewNoticeBody} @@ -101,11 +100,6 @@ export function LabelPreviewModal({ onClose }: Props) {
)} - {!labelaryEnabled && ( - - {t.output.previewDisabled} - - )} {canFetch && loading && ( {t.output.loading} )} @@ -131,18 +125,16 @@ export function LabelPreviewModal({ onClose }: Props) {
- {labelaryEnabled && ( - - )} + ); diff --git a/src/components/Output/ZPLOutput.tsx b/src/components/Output/ZPLOutput.tsx index 80adf041..df304eab 100644 --- a/src/components/Output/ZPLOutput.tsx +++ b/src/components/Output/ZPLOutput.tsx @@ -15,6 +15,7 @@ export function ZPLOutput({ collapsed, onCollapse, onExpand }: Props) { const t = useT(); const label = useLabelStore((s) => s.label); const pages = useLabelStore((s) => s.pages); + const labelaryEnabled = useLabelStore((s) => s.thirdParty.labelary); const [copied, setCopied] = useState(false); const [showPreview, setShowPreview] = useState(false); @@ -43,15 +44,17 @@ export function ZPLOutput({ collapsed, onCollapse, onExpand }: Props) { {t.output.zplHeading}
- + {labelaryEnabled && ( + + )}