From 06e1fa82ea5c882362276a211b6ad81b8dece81a Mon Sep 17 00:00:00 2001 From: u8array Date: Sat, 9 May 2026 08:50:06 +0200 Subject: [PATCH 1/2] feat(ui): show Labelary privacy notice on first Print, not just Preview Print routed through Labelary was previously hidden until the user had acknowledged the notice via Preview. Surface Print whenever the gate is enabled and open the notice on first click instead, matching the Preview flow. Disclaimer is extracted into a shared LabelaryNoticeModal so wording stays in lockstep, locale body now mentions both features, and a new env test pins the build-time gate behavior. --- src/components/AppShell.tsx | 27 +++++--- src/components/Output/LabelPreview.tsx | 46 +++---------- src/components/Output/LabelaryNoticeModal.tsx | 68 +++++++++++++++++++ src/components/Output/ZPLOutput.tsx | 17 +++-- src/locales/ar.ts | 2 +- src/locales/bg.ts | 2 +- src/locales/cs.ts | 2 +- src/locales/da.ts | 2 +- src/locales/de.ts | 2 +- src/locales/el.ts | 2 +- src/locales/en.ts | 2 +- src/locales/es.ts | 2 +- src/locales/et.ts | 2 +- src/locales/fa.ts | 2 +- src/locales/fi.ts | 2 +- src/locales/fr.ts | 2 +- src/locales/he.ts | 2 +- src/locales/hr.ts | 2 +- src/locales/hu.ts | 2 +- src/locales/it.ts | 2 +- src/locales/ja.ts | 2 +- src/locales/ko.ts | 2 +- src/locales/lt.ts | 2 +- src/locales/lv.ts | 2 +- src/locales/nl.ts | 2 +- src/locales/no.ts | 2 +- src/locales/pl.ts | 2 +- src/locales/pt.ts | 2 +- src/locales/ro.ts | 2 +- src/locales/sk.ts | 2 +- src/locales/sl.ts | 2 +- src/locales/sr.ts | 2 +- src/locales/sv.ts | 2 +- src/locales/tr.ts | 2 +- src/locales/zh-hans.ts | 2 +- src/locales/zh-hant.ts | 2 +- src/store/labelStore.ts | 6 +- src/store/labelStoreEnv.test.ts | 41 +++++++++++ 38 files changed, 187 insertions(+), 82 deletions(-) create mode 100644 src/components/Output/LabelaryNoticeModal.tsx create mode 100644 src/store/labelStoreEnv.test.ts diff --git a/src/components/AppShell.tsx b/src/components/AppShell.tsx index 2848b8e6..dfb8b8be 100644 --- a/src/components/AppShell.tsx +++ b/src/components/AppShell.tsx @@ -30,7 +30,8 @@ import { SunIcon, MoonIcon, } from "@heroicons/react/16/solid"; -import { useLabelStore, useHistory, canCallLabelary } from "../store/labelStore"; +import { useLabelStore, useHistory } from "../store/labelStore"; +import { LabelaryNoticeModal } from "./Output/LabelaryNoticeModal"; import { localeNames } from "../locales"; import type { LocaleCode } from "../locales"; import { mmToUnit } from "../lib/units"; @@ -50,7 +51,9 @@ export function AppShell() { const setLocale = useLabelStore((s) => s.setLocale); const theme = useLabelStore((s) => s.theme); const setTheme = useLabelStore((s) => s.setTheme); - const labelaryReady = useLabelStore(canCallLabelary); + const labelaryEnabled = useLabelStore((s) => s.thirdParty.labelary); + const noticeAcknowledged = useLabelStore((s) => s.labelaryNoticeAcknowledged); + const [showPrintNotice, setShowPrintNotice] = useState(false); // Bridge the theme preference to so the CSS variables in // index.css pick it up. @@ -202,14 +205,13 @@ export function AppShell() { {t.app.saveDesign} - {/* Print also routes through Labelary. Until the user has seen - the privacy notice (shown only via the Preview modal), Print - is hidden so the very first Labelary call cannot bypass the - disclosure. After acknowledgement Print stays available. */} - {labelaryReady && ( + {/* Print routes through Labelary. The button is shown whenever + the Labelary gate is on; clicking it before the notice has + been acknowledged opens the disclosure first, then prints. */} + {labelaryEnabled && ( (noticeAcknowledged ? handlePrint() : setShowPrintNotice(true))} disabled={!hasObjects} > {t.app.print} @@ -344,6 +346,15 @@ export function AppShell() { {showZebraPrint && ( )} + {showPrintNotice && ( + setShowPrintNotice(false)} + onContinue={() => { + setShowPrintNotice(false); + handlePrint(); + }} + /> + )} ); } diff --git a/src/components/Output/LabelPreview.tsx b/src/components/Output/LabelPreview.tsx index 2ecbe295..8d19d6b2 100644 --- a/src/components/Output/LabelPreview.tsx +++ b/src/components/Output/LabelPreview.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef, useState } from 'react'; import { XMarkIcon, ArrowDownTrayIcon } from '@heroicons/react/16/solid'; -import { useLabelStore, useCurrentObjects, canCallLabelary } from '../../store/labelStore'; +import { useLabelStore, useCurrentObjects } from '../../store/labelStore'; import { generateZPL } from '../../lib/zplGenerator'; import { fetchPreview, labelaryErrorMessage } from '../../lib/labelary'; import { triggerDownload } from '../../lib/triggerDownload'; @@ -10,28 +10,22 @@ interface Props { onClose: () => void; } - +/** Preview modal — assumes the privacy notice has already been + * acknowledged. Callers (ZPLOutput) gate the modal behind + * LabelaryNoticeModal so this component never has to handle the + * pre-ack state. */ export function LabelPreviewModal({ onClose }: Props) { const t = useT(); const label = useLabelStore((s) => s.label); const objects = useCurrentObjects(); - const noticeAcknowledged = useLabelStore((s) => s.labelaryNoticeAcknowledged); - const acknowledgeLabelaryNotice = useLabelStore((s) => s.acknowledgeLabelaryNotice); - // Same gate as every other Labelary consumer (Print, …) — single source - // of truth in the store. The modal opens only when the gate is on, so in - // practice this resolves to noticeAcknowledged here, but we route through - // the shared selector to stay in lockstep with future call sites. - const canFetch = useLabelStore(canCallLabelary); const [previewUrl, setPreviewUrl] = useState(null); 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; + const loading = !previewUrl && !error; useEffect(() => { - if (!canFetch) return; let cancelled = false; fetchPreview(zplRef.current, label) .then((url) => { @@ -50,7 +44,7 @@ export function LabelPreviewModal({ onClose }: Props) { // `label` and the generated ZPL are intentionally captured once at mount // (via zplRef): the preview should reflect the snapshot the user saw when // they opened the modal, not refetch when the canvas changes underneath. - }, [canFetch]); // eslint-disable-line react-hooks/exhaustive-deps + }, []); // eslint-disable-line react-hooks/exhaustive-deps const handleDownloadFallback = () => { triggerDownload(new Blob([zplRef.current], { type: 'text/plain' }), 'label.zpl'); @@ -83,30 +77,10 @@ export function LabelPreviewModal({ onClose }: Props) { viewport so small previews are still centered. */}
- {!noticeAcknowledged && ( -
- {t.output.previewNoticeTitle} - {t.output.previewNoticeBody} - - {t.output.previewNoticePrivacyLink} - - -
- )} - {canFetch && loading && ( + {loading && ( {t.output.loading} )} - {canFetch && !loading && error && ( + {!loading && error && (
{error}
)} - {canFetch && !loading && !error && previewUrl && ( + {!loading && !error && previewUrl && ( Label preview void; + onClose: () => void; +} + +/** Privacy notice shown the first time the user invokes a Labelary-backed + * feature (Preview, Print). After acknowledgement the gate + * (`canCallLabelary`) opens permanently and this modal is no longer + * rendered. Shared between Preview and Print so the disclosure wording + * stays in lockstep. */ +export function LabelaryNoticeModal({ onContinue, onClose }: Props) { + const t = useT(); + const acknowledgeLabelaryNotice = useLabelStore((s) => s.acknowledgeLabelaryNotice); + + const handleContinue = () => { + acknowledgeLabelaryNotice(); + onContinue(); + }; + + return ( +
+
e.stopPropagation()} + > +
+ + {t.output.previewNoticeTitle} + + +
+ +
+ {t.output.previewNoticeBody} + + {t.output.previewNoticePrivacyLink} + + +
+
+
+ ); +} diff --git a/src/components/Output/ZPLOutput.tsx b/src/components/Output/ZPLOutput.tsx index 381608b3..96df205d 100644 --- a/src/components/Output/ZPLOutput.tsx +++ b/src/components/Output/ZPLOutput.tsx @@ -4,6 +4,7 @@ import { useLabelStore } from '../../store/labelStore'; import { generateMultiPageZPL } from '../../lib/zplGenerator'; import { useT } from '../../lib/useT'; import { LabelPreviewModal } from './LabelPreview'; +import { LabelaryNoticeModal } from './LabelaryNoticeModal'; interface Props { collapsed?: boolean; @@ -15,12 +16,11 @@ export function ZPLOutput({ collapsed, onCollapse, onExpand }: Props) { const t = useT(); const label = useLabelStore((s) => s.label); const pages = useLabelStore((s) => s.pages); - // Direct gate check — Preview is the path to the privacy notice, so the - // button must be reachable before acknowledgement. Other Labelary callers - // (AppShell.Print, LabelPreview.fetch) use the stricter canCallLabelary. const labelaryEnabled = useLabelStore((s) => s.thirdParty.labelary); + const noticeAcknowledged = useLabelStore((s) => s.labelaryNoticeAcknowledged); const [copied, setCopied] = useState(false); const [showPreview, setShowPreview] = useState(false); + const [showNotice, setShowNotice] = useState(false); const hasObjects = pages.some((p) => p.objects.length > 0); const zpl = hasObjects ? generateMultiPageZPL(label, pages) : ''; @@ -49,7 +49,7 @@ export function ZPLOutput({ collapsed, onCollapse, onExpand }: Props) {
{labelaryEnabled && (
); diff --git a/src/locales/ar.ts b/src/locales/ar.ts index 866de5f1..4f99eb38 100644 --- a/src/locales/ar.ts +++ b/src/locales/ar.ts @@ -122,7 +122,7 @@ const ar = { previewEmpty: 'تظهر المعاينة\nبعد إجراء تغييرات', previewProvider: 'معاينة عبر api.labelary.com', previewNoticeTitle: 'إشعار الخصوصية', - previewNoticeBody: 'يتم إنشاء المعاينة بواسطة الخدمة الخارجية api.labelary.com. يتم إرسال ZPL الكامل للملصق، بما في ذلك أي بيانات حساسة، عبر الشبكة.', + previewNoticeBody: 'يتم إنشاء المعاينة والطباعة بواسطة الخدمة الخارجية api.labelary.com. يتم إرسال ZPL الكامل للملصق، بما في ذلك أي بيانات حساسة، عبر الشبكة.', previewNoticePrivacyLink: 'معلومات خصوصية Labelary', previewNoticeAcknowledge: 'فهمت، تابع', }, diff --git a/src/locales/bg.ts b/src/locales/bg.ts index 3e00ea7b..d6f505d6 100644 --- a/src/locales/bg.ts +++ b/src/locales/bg.ts @@ -122,7 +122,7 @@ const bg = { previewEmpty: 'Преглед се появява\nслед промени', previewProvider: 'Преглед чрез api.labelary.com', previewNoticeTitle: 'Известие за поверителност', - previewNoticeBody: 'Прегледът се изобразява от външната услуга api.labelary.com. Целият ZPL на етикета, включително чувствителните данни, се изпраща през мрежата.', + previewNoticeBody: 'Прегледът и отпечатването се изобразяват от външната услуга api.labelary.com. Целият ZPL на етикета, включително чувствителните данни, се изпраща през мрежата.', previewNoticePrivacyLink: 'Информация за поверителност на Labelary', previewNoticeAcknowledge: 'Разбрах, продължи', }, diff --git a/src/locales/cs.ts b/src/locales/cs.ts index 22fc2ddc..815c00d5 100644 --- a/src/locales/cs.ts +++ b/src/locales/cs.ts @@ -122,7 +122,7 @@ const cs = { previewEmpty: 'Náhled se zobrazí\npo úpravách', previewProvider: 'Náhled přes api.labelary.com', 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íť.', + previewNoticeBody: 'Náhled a tisk se generují 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', }, diff --git a/src/locales/da.ts b/src/locales/da.ts index 3e62111c..7e2d9b59 100644 --- a/src/locales/da.ts +++ b/src/locales/da.ts @@ -122,7 +122,7 @@ const da = { previewEmpty: 'Forhåndsvisning vises\nefter ændringer', previewProvider: 'Forhåndsvisning via api.labelary.com', previewNoticeTitle: 'Privatlivsmeddelelse', - previewNoticeBody: 'Forhåndsvisningen genereres af den eksterne tjeneste api.labelary.com. Hele etikettens ZPL, inklusive følsomme data, sendes over netværket.', + previewNoticeBody: 'Forhåndsvisning og udskrivning 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', }, diff --git a/src/locales/de.ts b/src/locales/de.ts index e9ea97b0..1a9c0cc5 100644 --- a/src/locales/de.ts +++ b/src/locales/de.ts @@ -142,7 +142,7 @@ const de = { previewEmpty: 'Vorschau erscheint\nnach Änderungen', previewProvider: 'Vorschau über api.labelary.com', previewNoticeTitle: 'Datenschutzhinweis', - previewNoticeBody: 'Die Vorschau wird vom externen Dienst api.labelary.com erstellt. Dabei wird das vollständige Label-ZPL inklusive sensibler Daten übertragen.', + previewNoticeBody: 'Vorschau und Druck werden vom externen Dienst api.labelary.com erstellt. Dabei wird das vollständige Label-ZPL inklusive sensibler Daten übertragen.', previewNoticePrivacyLink: 'Labelary-Datenschutzhinweise', previewNoticeAcknowledge: 'Verstanden, fortfahren', }, diff --git a/src/locales/el.ts b/src/locales/el.ts index 3314283b..44c2a10b 100644 --- a/src/locales/el.ts +++ b/src/locales/el.ts @@ -122,7 +122,7 @@ const el = { previewEmpty: 'Η προεπισκόπηση εμφανίζεται\nμετά τις αλλαγές', previewProvider: 'Προεπισκόπηση μέσω api.labelary.com', previewNoticeTitle: 'Ειδοποίηση απορρήτου', - previewNoticeBody: 'Η προεπισκόπηση δημιουργείται από την εξωτερική υπηρεσία api.labelary.com. Ολόκληρο το ZPL της ετικέτας, συμπεριλαμβανομένων ευαίσθητων δεδομένων, αποστέλλεται μέσω δικτύου.', + previewNoticeBody: 'Η προεπισκόπηση και η εκτύπωση δημιουργούνται από την εξωτερική υπηρεσία api.labelary.com. Ολόκληρο το ZPL της ετικέτας, συμπεριλαμβανομένων ευαίσθητων δεδομένων, αποστέλλεται μέσω δικτύου.', previewNoticePrivacyLink: 'Πληροφορίες απορρήτου Labelary', previewNoticeAcknowledge: 'Κατάλαβα, συνέχεια', }, diff --git a/src/locales/en.ts b/src/locales/en.ts index 99685ee0..143998b5 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -142,7 +142,7 @@ const en = { previewEmpty: 'Preview appears\nafter changes', previewProvider: 'Preview via api.labelary.com', 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.', + previewNoticeBody: 'Preview and print 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', }, diff --git a/src/locales/es.ts b/src/locales/es.ts index e8e66596..32d9f207 100644 --- a/src/locales/es.ts +++ b/src/locales/es.ts @@ -122,7 +122,7 @@ const es = { previewEmpty: 'La vista previa aparece\ntras los cambios', previewProvider: 'Vista previa vía api.labelary.com', 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.', + previewNoticeBody: 'La previsualización y la impresión se generan 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', }, diff --git a/src/locales/et.ts b/src/locales/et.ts index 10b7ddfd..f0c533ce 100644 --- a/src/locales/et.ts +++ b/src/locales/et.ts @@ -122,7 +122,7 @@ const et = { previewEmpty: 'Eelvaade kuvatakse\npärast muudatusi', previewProvider: 'Eelvaade api.labelary.com kaudu', previewNoticeTitle: 'Privaatsusteatis', - previewNoticeBody: 'Eelvaate genereerib väline teenus api.labelary.com. Sildi terve ZPL, sealhulgas tundlikud andmed, saadetakse võrgu kaudu.', + previewNoticeBody: 'Eelvaate ja printimise genereerib väline teenus api.labelary.com. Sildi terve ZPL, sealhulgas tundlikud andmed, saadetakse võrgu kaudu.', previewNoticePrivacyLink: 'Labelary privaatsusteave', previewNoticeAcknowledge: 'Selge, jätka', }, diff --git a/src/locales/fa.ts b/src/locales/fa.ts index bfd2d578..99a3e741 100644 --- a/src/locales/fa.ts +++ b/src/locales/fa.ts @@ -122,7 +122,7 @@ const fa = { previewEmpty: 'پیش‌نمایش پس از\nتغییرات نمایش داده می‌شود', previewProvider: 'پیش‌نمایش از طریق api.labelary.com', previewNoticeTitle: 'اطلاعیه حریم خصوصی', - previewNoticeBody: 'پیش‌نمایش توسط سرویس خارجی api.labelary.com تولید می‌شود. ZPL کامل برچسب، شامل داده‌های حساس، از طریق شبکه ارسال می‌شود.', + previewNoticeBody: 'پیش‌نمایش و چاپ توسط سرویس خارجی api.labelary.com تولید می‌شود. ZPL کامل برچسب، شامل داده‌های حساس، از طریق شبکه ارسال می‌شود.', previewNoticePrivacyLink: 'اطلاعات حریم خصوصی Labelary', previewNoticeAcknowledge: 'متوجه شدم، ادامه', }, diff --git a/src/locales/fi.ts b/src/locales/fi.ts index 591448ba..e1ea4018 100644 --- a/src/locales/fi.ts +++ b/src/locales/fi.ts @@ -122,7 +122,7 @@ const fi = { previewEmpty: 'Esikatselu näkyy\nmuutosten jälkeen', previewProvider: 'Esikatselu palvelusta api.labelary.com', previewNoticeTitle: 'Tietosuojailmoitus', - previewNoticeBody: 'Esikatselun tuottaa ulkoinen palvelu api.labelary.com. Etiketin koko ZPL, mukaan lukien arkaluonteiset tiedot, lähetetään verkon yli.', + previewNoticeBody: 'Esikatselun ja tulostuksen tuottaa ulkoinen palvelu api.labelary.com. Etiketin koko ZPL, mukaan lukien arkaluonteiset tiedot, lähetetään verkon yli.', previewNoticePrivacyLink: 'Labelary-tietosuojatiedot', previewNoticeAcknowledge: 'Selvä, jatka', }, diff --git a/src/locales/fr.ts b/src/locales/fr.ts index 9a3d8848..2163194d 100644 --- a/src/locales/fr.ts +++ b/src/locales/fr.ts @@ -122,7 +122,7 @@ const fr = { previewEmpty: "L'aperçu apparaît\naprès les modifications", previewProvider: 'Aperçu via api.labelary.com', 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.', + previewNoticeBody: "Le rendu de l'aperçu et de l'impression 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', }, diff --git a/src/locales/he.ts b/src/locales/he.ts index b81a4697..43b2b179 100644 --- a/src/locales/he.ts +++ b/src/locales/he.ts @@ -122,7 +122,7 @@ const he = { previewEmpty: 'התצוגה המקדימה מופיעה\nלאחר שינויים', previewProvider: 'תצוגה מקדימה דרך api.labelary.com', previewNoticeTitle: 'הודעת פרטיות', - previewNoticeBody: 'התצוגה המקדימה מופקת על ידי השירות החיצוני api.labelary.com. ה-ZPL המלא של התווית, כולל נתונים רגישים, נשלח ברשת.', + previewNoticeBody: 'התצוגה המקדימה וההדפסה מופקות על ידי השירות החיצוני api.labelary.com. ה-ZPL המלא של התווית, כולל נתונים רגישים, נשלח ברשת.', previewNoticePrivacyLink: 'מידע פרטיות של Labelary', previewNoticeAcknowledge: 'הבנתי, המשך', }, diff --git a/src/locales/hr.ts b/src/locales/hr.ts index b743134b..edb1613f 100644 --- a/src/locales/hr.ts +++ b/src/locales/hr.ts @@ -122,7 +122,7 @@ const hr = { previewEmpty: 'Pregled se prikazuje\nnakon promjena', previewProvider: 'Pregled putem api.labelary.com', 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.', + previewNoticeBody: 'Pregled i ispis 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', }, diff --git a/src/locales/hu.ts b/src/locales/hu.ts index 83258c7b..c855dc7f 100644 --- a/src/locales/hu.ts +++ b/src/locales/hu.ts @@ -122,7 +122,7 @@ const hu = { previewEmpty: 'Az előnézet megjelenik\nmódosítás után', previewProvider: 'Előnézet az api.labelary.com útján', 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.', + previewNoticeBody: 'Az előnézetet és a nyomtatást 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', }, diff --git a/src/locales/it.ts b/src/locales/it.ts index 0f8c2786..22ccdd39 100644 --- a/src/locales/it.ts +++ b/src/locales/it.ts @@ -122,7 +122,7 @@ const it = { previewEmpty: "L'anteprima appare\ndopo le modifiche", previewProvider: 'Anteprima tramite api.labelary.com', 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.', + previewNoticeBody: "L'anteprima e la stampa sono generate 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', }, diff --git a/src/locales/ja.ts b/src/locales/ja.ts index b736ba77..10b4cfeb 100644 --- a/src/locales/ja.ts +++ b/src/locales/ja.ts @@ -122,7 +122,7 @@ const ja = { previewEmpty: '変更後にプレビューが\n表示されます', previewProvider: 'api.labelary.com 経由のプレビュー', previewNoticeTitle: 'プライバシーに関する通知', - previewNoticeBody: 'プレビューは外部サービス api.labelary.com で生成されます。機密データを含むラベル全体の ZPL がネットワーク経由で送信されます。', + previewNoticeBody: 'プレビューと印刷は外部サービス api.labelary.com で生成されます。機密データを含むラベル全体の ZPL がネットワーク経由で送信されます。', previewNoticePrivacyLink: 'Labelary のプライバシー情報', previewNoticeAcknowledge: '了解しました、続行', }, diff --git a/src/locales/ko.ts b/src/locales/ko.ts index dab88f33..5a667ebd 100644 --- a/src/locales/ko.ts +++ b/src/locales/ko.ts @@ -122,7 +122,7 @@ const ko = { previewEmpty: '변경 후 미리보기가\n표시됩니다', previewProvider: 'api.labelary.com을(를) 통한 미리 보기', previewNoticeTitle: '개인정보 보호 고지', - previewNoticeBody: '미리 보기는 외부 서비스 api.labelary.com에서 생성됩니다. 민감한 데이터를 포함한 전체 라벨 ZPL이 네트워크를 통해 전송됩니다.', + previewNoticeBody: '미리 보기와 인쇄는 외부 서비스 api.labelary.com에서 생성됩니다. 민감한 데이터를 포함한 전체 라벨 ZPL이 네트워크를 통해 전송됩니다.', previewNoticePrivacyLink: 'Labelary 개인정보 보호 정보', previewNoticeAcknowledge: '이해했습니다, 계속', }, diff --git a/src/locales/lt.ts b/src/locales/lt.ts index 6e51ac7e..84e83af2 100644 --- a/src/locales/lt.ts +++ b/src/locales/lt.ts @@ -122,7 +122,7 @@ const lt = { previewEmpty: 'Peržiūra rodoma\npo pakeitimų', previewProvider: 'Peržiūra per api.labelary.com', 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.', + previewNoticeBody: 'Peržiūrą ir spausdinimą 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', }, diff --git a/src/locales/lv.ts b/src/locales/lv.ts index 59bd7d23..ee848ca4 100644 --- a/src/locales/lv.ts +++ b/src/locales/lv.ts @@ -122,7 +122,7 @@ const lv = { previewEmpty: 'Priekšskatījums parādās\npēc izmaiņām', previewProvider: 'Priekšskatījums caur api.labelary.com', 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ā.', + previewNoticeBody: 'Priekšskatījumu un drukāšanu ģ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', }, diff --git a/src/locales/nl.ts b/src/locales/nl.ts index f30bd17e..4195b5d6 100644 --- a/src/locales/nl.ts +++ b/src/locales/nl.ts @@ -122,7 +122,7 @@ const nl = { previewEmpty: 'Voorbeeld verschijnt\nna wijzigingen', previewProvider: 'Voorbeeld via api.labelary.com', 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.', + previewNoticeBody: 'De voorbeeldweergave en het afdrukken worden 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', }, diff --git a/src/locales/no.ts b/src/locales/no.ts index b7d930f7..23af85e0 100644 --- a/src/locales/no.ts +++ b/src/locales/no.ts @@ -122,7 +122,7 @@ const no = { previewEmpty: 'Forhåndsvisning vises\netter endringer', previewProvider: 'Forhåndsvisning via api.labelary.com', previewNoticeTitle: 'Personvernmelding', - previewNoticeBody: 'Forhåndsvisningen genereres av den eksterne tjenesten api.labelary.com. Hele etikettens ZPL, inkludert sensitive data, sendes over nettverket.', + previewNoticeBody: 'Forhåndsvisning og utskrift genereres av den eksterne tjenesten api.labelary.com. Hele etikettens ZPL, inkludert sensitive data, sendes over nettverket.', previewNoticePrivacyLink: 'Labelary personverninformasjon', previewNoticeAcknowledge: 'Forstått, fortsett', }, diff --git a/src/locales/pl.ts b/src/locales/pl.ts index d80bbd89..ea81883a 100644 --- a/src/locales/pl.ts +++ b/src/locales/pl.ts @@ -122,7 +122,7 @@ const pl = { previewEmpty: 'Podgląd pojawi się\npo zmianach', previewProvider: 'Podgląd przez api.labelary.com', 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ć.', + previewNoticeBody: 'Podgląd i drukowanie są renderowane 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', }, diff --git a/src/locales/pt.ts b/src/locales/pt.ts index 3eac66f4..64c12cf5 100644 --- a/src/locales/pt.ts +++ b/src/locales/pt.ts @@ -122,7 +122,7 @@ const pt = { previewEmpty: 'A pré-visualização aparece\napós alterações', previewProvider: 'Pré-visualização via api.labelary.com', 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.', + previewNoticeBody: 'A pré-visualização e a impressão são geradas 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', }, diff --git a/src/locales/ro.ts b/src/locales/ro.ts index 2ece6198..accae7bc 100644 --- a/src/locales/ro.ts +++ b/src/locales/ro.ts @@ -122,7 +122,7 @@ const ro = { previewEmpty: 'Previzualizarea apare\ndupă modificări', previewProvider: 'Previzualizare prin api.labelary.com', 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.', + previewNoticeBody: 'Previzualizarea și imprimarea sunt generate 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ă', }, diff --git a/src/locales/sk.ts b/src/locales/sk.ts index df3315cc..8b76707e 100644 --- a/src/locales/sk.ts +++ b/src/locales/sk.ts @@ -122,7 +122,7 @@ const sk = { previewEmpty: 'Náhľad sa zobrazí\npo úpravách', previewProvider: 'Náhľad cez api.labelary.com', 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ť.', + previewNoticeBody: 'Náhľad a tlač sa generujú 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ť', }, diff --git a/src/locales/sl.ts b/src/locales/sl.ts index 8ce35eb9..f34a7ef1 100644 --- a/src/locales/sl.ts +++ b/src/locales/sl.ts @@ -122,7 +122,7 @@ const sl = { previewEmpty: 'Predogled se prikaže\npo spremembah', previewProvider: 'Predogled prek api.labelary.com', 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.', + previewNoticeBody: 'Predogled in tiskanje 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', }, diff --git a/src/locales/sr.ts b/src/locales/sr.ts index 1f66a197..0631afb4 100644 --- a/src/locales/sr.ts +++ b/src/locales/sr.ts @@ -122,7 +122,7 @@ const sr = { previewEmpty: 'Pregled se prikazuje\nnakon promena', previewProvider: 'Преглед преко api.labelary.com', previewNoticeTitle: 'Обавештење о приватности', - previewNoticeBody: 'Преглед се приказује спољним сервисом api.labelary.com. Цео ZPL етикете, укључујући осетљиве податке, шаље се преко мреже.', + previewNoticeBody: 'Преглед и штампање приказује спољни сервис api.labelary.com. Цео ZPL етикете, укључујући осетљиве податке, шаље се преко мреже.', previewNoticePrivacyLink: 'Информације о приватности Labelary', previewNoticeAcknowledge: 'Разумем, настави', }, diff --git a/src/locales/sv.ts b/src/locales/sv.ts index fcfe937f..e61f0fa3 100644 --- a/src/locales/sv.ts +++ b/src/locales/sv.ts @@ -122,7 +122,7 @@ const sv = { previewEmpty: 'Förhandsvisning visas\nefter ändringar', previewProvider: 'Förhandsgranskning via api.labelary.com', 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.', + previewNoticeBody: 'Förhandsgranskning och utskrift 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', }, diff --git a/src/locales/tr.ts b/src/locales/tr.ts index ff375fca..ae198c59 100644 --- a/src/locales/tr.ts +++ b/src/locales/tr.ts @@ -122,7 +122,7 @@ const tr = { previewEmpty: 'Önizleme değişikliklerden\nsonra görüntülenir', previewProvider: 'api.labelary.com üzerinden önizleme', 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.', + previewNoticeBody: 'Önizleme ve yazdırma, 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', }, diff --git a/src/locales/zh-hans.ts b/src/locales/zh-hans.ts index f7e242c2..cb24f3cb 100644 --- a/src/locales/zh-hans.ts +++ b/src/locales/zh-hans.ts @@ -122,7 +122,7 @@ const zhHans = { previewEmpty: '更改后显示预览', previewProvider: '通过 api.labelary.com 预览', previewNoticeTitle: '隐私提示', - previewNoticeBody: '预览由外部服务 api.labelary.com 生成。完整的标签 ZPL(包括敏感数据)将通过网络发送。', + previewNoticeBody: '预览和打印由外部服务 api.labelary.com 生成。完整的标签 ZPL(包括敏感数据)将通过网络发送。', previewNoticePrivacyLink: 'Labelary 隐私信息', previewNoticeAcknowledge: '知道了,继续', }, diff --git a/src/locales/zh-hant.ts b/src/locales/zh-hant.ts index 8a4fd1f8..37ae3715 100644 --- a/src/locales/zh-hant.ts +++ b/src/locales/zh-hant.ts @@ -122,7 +122,7 @@ const zhHant = { previewEmpty: '變更後顯示預覽', previewProvider: '透過 api.labelary.com 預覽', previewNoticeTitle: '隱私聲明', - previewNoticeBody: '預覽由外部服務 api.labelary.com 產生。完整的標籤 ZPL(包含敏感資料)會透過網路傳送。', + previewNoticeBody: '預覽和列印由外部服務 api.labelary.com 產生。完整的標籤 ZPL(包含敏感資料)會透過網路傳送。', previewNoticePrivacyLink: 'Labelary 隱私資訊', previewNoticeAcknowledge: '了解,繼續', }, diff --git a/src/store/labelStore.ts b/src/store/labelStore.ts index 956c5f94..121a9e18 100644 --- a/src/store/labelStore.ts +++ b/src/store/labelStore.ts @@ -114,8 +114,10 @@ export const currentObjects = (state: PageState): LabelObject[] => state.pages[state.currentPageIndex]?.objects ?? []; /** True when a Labelary network call is permitted: the gate is on AND the - * user has seen the privacy notice. Single source of truth so every UI - * consumer (Preview, Print, …) stays in lockstep. */ + * user has seen the privacy notice. Kept as a documented invariant; UI + * buttons read `thirdParty.labelary` and `labelaryNoticeAcknowledged` + * separately because they need to distinguish "hide" (gate off) from + * "show notice first" (gate on, not yet acknowledged). */ export const canCallLabelary = (s: LabelState): boolean => s.thirdParty.labelary && s.labelaryNoticeAcknowledged; diff --git a/src/store/labelStoreEnv.test.ts b/src/store/labelStoreEnv.test.ts new file mode 100644 index 00000000..050a85b0 --- /dev/null +++ b/src/store/labelStoreEnv.test.ts @@ -0,0 +1,41 @@ +import { describe, it, expect, vi, afterEach } from 'vitest'; + +/** Verifies that VITE_THIRD_PARTY_LABELARY=false flips the Labelary gate + * off at store init. The UI gates Print/Preview buttons on + * `thirdParty.labelary` directly, so a false default keeps both buttons + * hidden on Tauri/Docker builds — even though the user can still + * acknowledge the notice (in-memory) for the lifetime of the session. + * + * Lives in a dedicated file because each case needs to re-import the + * store module after stubbing import.meta.env. */ +describe('thirdParty defaults from env', () => { + afterEach(() => { + vi.unstubAllEnvs(); + vi.resetModules(); + }); + + it('defaults Labelary gate to true when env is unset', async () => { + vi.stubEnv('VITE_THIRD_PARTY_LABELARY', ''); + vi.resetModules(); + const { useLabelStore } = await import('./labelStore'); + expect(useLabelStore.getState().thirdParty.labelary).toBe(true); + }); + + it('defaults Labelary gate to false when env is "false"', async () => { + vi.stubEnv('VITE_THIRD_PARTY_LABELARY', 'false'); + vi.resetModules(); + const { useLabelStore, canCallLabelary } = await import('./labelStore'); + const s = useLabelStore.getState(); + expect(s.thirdParty.labelary).toBe(false); + // Acknowledgement alone must not unlock Labelary when the gate is off. + s.acknowledgeLabelaryNotice(); + expect(canCallLabelary(useLabelStore.getState())).toBe(false); + }); + + it('treats other env values as "enabled" (only "false" disables)', async () => { + vi.stubEnv('VITE_THIRD_PARTY_LABELARY', 'true'); + vi.resetModules(); + const { useLabelStore } = await import('./labelStore'); + expect(useLabelStore.getState().thirdParty.labelary).toBe(true); + }); +}); From 35b6e3f15303cba8a992c2664ca780a702ec7b01 Mon Sep 17 00:00:00 2001 From: u8array Date: Sat, 9 May 2026 08:58:03 +0200 Subject: [PATCH 2/2] a11y(output): add ARIA dialog roles and aria-label on close buttons LabelaryNoticeModal and LabelPreviewModal now expose role=dialog, aria-modal and aria-labelledby so screen readers announce them as modal dialogs. The icon-only close buttons get a localized aria-label via a new app.close translation across all 32 locales. --- src/components/Output/LabelPreview.tsx | 6 +++++- src/components/Output/LabelaryNoticeModal.tsx | 6 +++++- 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 + 34 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/components/Output/LabelPreview.tsx b/src/components/Output/LabelPreview.tsx index 8d19d6b2..c7300fca 100644 --- a/src/components/Output/LabelPreview.tsx +++ b/src/components/Output/LabelPreview.tsx @@ -54,17 +54,21 @@ export function LabelPreviewModal({ onClose }: Props) {
e.stopPropagation()} >
- + {t.output.previewHeading}