@nice2dev/ui-printing 1.1.7 โ†’ 1.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/index.cjs +2 -2
  2. package/dist/index.mjs +1900 -1734
  3. package/dist/ui-printing.css +1 -1
  4. package/package.json +3 -7
  5. package/src/print-button/NicePrintButton.tsx +39 -17
  6. package/src/print-button/PrintButton.module.css +9 -9
  7. package/src/print-preview/NicePrintPreview.tsx +10 -9
  8. package/src/print-preview/PrintPreview.module.css +7 -7
  9. package/src/print-queue/NicePrintQueue.tsx +76 -42
  10. package/src/print-queue/PrintQueue.module.css +9 -9
  11. package/src/template-browser/NiceTemplateBrowser.tsx +39 -18
  12. package/src/template-browser/TemplateBrowser.module.css +17 -17
  13. package/src/template-editor/NiceTemplateEditor.tsx +306 -179
  14. package/src/template-editor/TemplateEditor.module.css +90 -83
  15. package/src/types/printingTypes.ts +1 -1
  16. package/dist/index.d.ts.map +0 -1
  17. package/dist/print-button/NicePrintButton.d.ts.map +0 -1
  18. package/dist/print-button/index.d.ts.map +0 -1
  19. package/dist/print-preview/NicePrintPreview.d.ts.map +0 -1
  20. package/dist/print-preview/index.d.ts.map +0 -1
  21. package/dist/print-queue/NicePrintQueue.d.ts.map +0 -1
  22. package/dist/print-queue/index.d.ts.map +0 -1
  23. package/dist/style.css +0 -1
  24. package/dist/template-browser/NiceTemplateBrowser.d.ts.map +0 -1
  25. package/dist/template-browser/index.d.ts.map +0 -1
  26. package/dist/template-editor/NiceTemplateEditor.d.ts.map +0 -1
  27. package/dist/template-editor/index.d.ts.map +0 -1
  28. package/dist/types/printingTypes.d.ts.map +0 -1
  29. package/src/template-editor/NiceTemplateEditor.tsx.first +0 -1011
  30. package/src/template-editor/NiceTemplateEditor.tsx.tmp +0 -392
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable jsx-a11y/click-events-have-key-events -- modal overlay backdrops (Esc-on-parent provides keyboard close), selection rows in .map() accompanied by visible cursor:pointer styling and sibling buttons; full keyboard migration via useAccessibleClick + extracted subcomponents tracked in TODO_1.0.10 A2.3 */
2
- import { NiceAppShell } from '@nice2dev/ui';
2
+ import { NiceAppShell, NiceIcon, useNiceTranslation } from '@nice2dev/ui';
3
+ import type { NiceTranslateFn } from '@nice2dev/ui';
3
4
  import React, {
4
5
  useState,
5
6
  useCallback,
@@ -55,46 +56,51 @@ const FONT_FAMILIES = [
55
56
  ];
56
57
  const FONT_SIZES = [6, 7, 8, 9, 10, 11, 12, 14, 16, 18, 20, 24, 28, 32, 36, 48, 60, 72];
57
58
 
59
+ /** NiceIcon name per field type. */
58
60
  const FIELD_TYPE_ICONS: Record<string, string> = {
59
- text: '๐Ÿ“',
60
- number: '#',
61
- currency: 'โ‚ฌ',
62
- date: '๐Ÿ“…',
63
- datetime: '๐Ÿ•',
64
- boolean: 'โœ“',
65
- image: '๐Ÿ–ผ',
66
- barcode: 'โ–ฎโ–Œโ–ฎ',
67
- qr: 'โฌ›',
68
- signature: 'โœ',
69
- static_text: 'T',
70
- static_image: '๐Ÿ–ผ',
71
- line: 'โ”€',
72
- rect: 'โ–ญ',
73
- page_number: '#p',
74
- total_pages: '#pp',
75
- print_date: '๐Ÿ“…',
61
+ text: 'type',
62
+ number: 'hash',
63
+ currency: 'dollar',
64
+ date: 'calendar',
65
+ datetime: 'clock',
66
+ boolean: 'check-square',
67
+ image: 'image',
68
+ barcode: 'barcode',
69
+ qr: 'qr-code',
70
+ signature: 'pen',
71
+ static_text: 'type',
72
+ static_image: 'image',
73
+ line: 'line',
74
+ rect: 'square',
75
+ page_number: 'hash',
76
+ total_pages: 'layers',
77
+ print_date: 'calendar',
76
78
  };
77
79
 
78
80
  const FIELD_TYPE_LABELS: Record<string, string> = {
79
- text: 'Tekst',
80
- number: 'Liczba',
81
- currency: 'Waluta',
82
- date: 'Data',
83
- datetime: 'Data i czas',
84
- boolean: 'Tak/Nie',
85
- image: 'Obraz',
86
- barcode: 'Kod kreskowy',
81
+ text: 'Text',
82
+ number: 'Number',
83
+ currency: 'Currency',
84
+ date: 'Date',
85
+ datetime: 'Date and time',
86
+ boolean: 'Yes/No',
87
+ image: 'Image',
88
+ barcode: 'Barcode',
87
89
  qr: 'QR',
88
- signature: 'Podpis',
89
- static_text: 'Tekst staล‚y',
90
- static_image: 'Obraz staล‚y',
91
- line: 'Linia',
92
- rect: 'Prostokฤ…t',
93
- page_number: 'Numer strony',
94
- total_pages: 'Liczba stron',
95
- print_date: 'Data wydruku',
90
+ signature: 'Signature',
91
+ static_text: 'Static text',
92
+ static_image: 'Static image',
93
+ line: 'Line',
94
+ rect: 'Rectangle',
95
+ page_number: 'Page number',
96
+ total_pages: 'Total pages',
97
+ print_date: 'Print date',
96
98
  };
97
99
 
100
+ /** Field-type label in the active language; the table above holds the English default. */
101
+ const fieldTypeLabel = (tr: NiceTranslateFn, type: string): string =>
102
+ FIELD_TYPE_LABELS[type] ? tr(`printing.fieldType.${type}`, FIELD_TYPE_LABELS[type]) : type;
103
+
98
104
  // โ”€โ”€โ”€ Undo/Redo history โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
99
105
  type HistoryState = { past: PrintTemplate[]; present: PrintTemplate; future: PrintTemplate[] };
100
106
  type HistoryAction =
@@ -134,6 +140,17 @@ function historyReducer(state: HistoryState, action: HistoryAction): HistoryStat
134
140
  }
135
141
 
136
142
  // โ”€โ”€โ”€ Helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
143
+ /**
144
+ * Document ink, as a LITERAL rather than a token โ€” same reason as `344d1b2` in the vector
145
+ * editor. These values are template DATA: they are written into exported documents, where
146
+ * no --nice-* property is declared, and they are fed to `<input type="color">`, which only
147
+ * accepts a 7-character hex and silently shows black for anything else. A `var(โ€ฆ)` string
148
+ * here never reached the paper as a colour and could not be edited in the picker.
149
+ */
150
+ const INK = '#000000';
151
+ /** Same, for the faint grey a watermark is drawn in. */
152
+ const INK_FAINT = '#94a3b8';
153
+
137
154
  function uid() {
138
155
  return `f-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
139
156
  }
@@ -145,7 +162,7 @@ function makeField(type: string, schemaField?: DataSourceField): PrintDataField
145
162
  label: schemaField?.label ?? FIELD_TYPE_LABELS[type] ?? type,
146
163
  type: type as PrintDataField['type'],
147
164
  dataPath: schemaField?.path ?? '',
148
- fallback: type === 'static_text' ? 'Tekst statyczny' : undefined,
165
+ fallback: type === 'static_text' ? 'Static text' : undefined,
149
166
  format: type === 'date' || type === 'datetime' ? 'DD.MM.YYYY' : undefined,
150
167
  position: {
151
168
  x: 15,
@@ -153,7 +170,7 @@ function makeField(type: string, schemaField?: DataSourceField): PrintDataField
153
170
  width: type === 'line' ? 80 : 60,
154
171
  height: type === 'line' ? 1 : type === 'image' ? 30 : 8,
155
172
  },
156
- style: { fontSize: 10, color: 'var(--nice-text, #000000)', fontFamily: 'sans-serif' },
173
+ style: { fontSize: 10, color: INK, fontFamily: 'sans-serif' },
157
174
  };
158
175
  if (type === 'rect') {
159
176
  base.style.backgroundColor = 'var(--nice-bg-secondary, #f1f5f9)';
@@ -162,7 +179,7 @@ function makeField(type: string, schemaField?: DataSourceField): PrintDataField
162
179
  base.style = {
163
180
  ...base.style,
164
181
  borderWidth: 0.5,
165
- borderColor: 'var(--nice-text, #000000)',
182
+ borderColor: INK,
166
183
  borderStyle: 'solid',
167
184
  };
168
185
  }
@@ -233,7 +250,7 @@ interface FieldElementProps {
233
250
 
234
251
  function resolveFieldPreview(field: PrintDataField, data?: Record<string, unknown>): string {
235
252
  if (field.type === 'static_text') {
236
- return field.fallback ?? 'Tekst';
253
+ return field.fallback ?? 'Text';
237
254
  }
238
255
  if (field.type === 'page_number') {
239
256
  return '1';
@@ -293,7 +310,7 @@ const FieldElement: React.FC<FieldElementProps> = ({
293
310
  fontFamily: st.fontFamily ?? 'sans-serif',
294
311
  fontWeight: st.fontWeight ?? 'normal',
295
312
  fontStyle: st.fontStyle ?? 'normal',
296
- color: st.color ?? 'var(--nice-text, #000)',
313
+ color: st.color ?? INK,
297
314
  textAlign: st.textAlign ?? 'left',
298
315
  lineHeight: st.lineHeight ?? 1.4,
299
316
  letterSpacing: st.letterSpacing ? `${st.letterSpacing}em` : undefined,
@@ -317,17 +334,22 @@ const FieldElement: React.FC<FieldElementProps> = ({
317
334
  <div
318
335
  style={{
319
336
  width: '100%',
320
- borderBottom: `${(st.borderWidth ?? 0.5) * zoom}px ${st.borderStyle ?? 'solid'} ${st.borderColor ?? 'var(--nice-text, #000)'}`,
337
+ borderBottom: `${(st.borderWidth ?? 0.5) * zoom}px ${st.borderStyle ?? 'solid'} ${st.borderColor ?? INK}`,
321
338
  }}
322
339
  />
323
340
  ) : isRect ? null : isImage ? (
324
- <div className={styles.fieldImagePlaceholder}>๐Ÿ–ผ</div>
341
+ <div className={styles.fieldImagePlaceholder}>
342
+ <NiceIcon name="image" size={24} />
343
+ </div>
325
344
  ) : isBarcode ? (
326
345
  <div className={styles.fieldBarcodePlaceholder}>
327
- {field.type === 'qr' ? 'โฌ› QR' : 'โ–ฎโ–Œโ–ฎ Barcode'}
346
+ <NiceIcon name={field.type === 'qr' ? 'qr-code' : 'barcode'} size={14} />{' '}
347
+ {field.type === 'qr' ? 'QR' : 'Barcode'}
328
348
  </div>
329
349
  ) : field.type === 'signature' ? (
330
- <div className={styles.fieldSignaturePlaceholder}>โœ {field.label}</div>
350
+ <div className={styles.fieldSignaturePlaceholder}>
351
+ <NiceIcon name="pen" size={14} /> {field.label}
352
+ </div>
331
353
  ) : (
332
354
  <span style={{ display: 'block', width: '100%' }}>
333
355
  {resolveFieldPreview(field, sampleData)}
@@ -467,21 +489,21 @@ const ShortcutChip: React.FC<{ keys: string; label: string }> = ({ keys, label }
467
489
 
468
490
  // โ”€โ”€โ”€ Left panel tabs โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
469
491
  type LeftTab = 'fields' | 'layers' | 'watermarks' | 'signatures' | 'settings';
470
- const LEFT_TABS: { id: LeftTab; icon: string; label: string }[] = [
471
- { id: 'fields', icon: '๐Ÿ“', label: 'Pola' },
472
- { id: 'layers', icon: 'โ—ง', label: 'Warstwy' },
473
- { id: 'watermarks', icon: '๐Ÿ’ง', label: 'Znaki' },
474
- { id: 'signatures', icon: 'โœ', label: 'Podpisy' },
475
- { id: 'settings', icon: 'โš™', label: 'Ustawienia' },
492
+ const LEFT_TABS: { id: LeftTab; icon: string; key: string; label: string }[] = [
493
+ { id: 'fields', icon: 'list', key: 'printing.editor.tabFields', label: 'Fields' },
494
+ { id: 'layers', icon: 'layers', key: 'printing.editor.tabLayers', label: 'Layers' },
495
+ { id: 'watermarks', icon: 'droplet', key: 'printing.editor.tabWatermarks', label: 'Watermarks' },
496
+ { id: 'signatures', icon: 'pen', key: 'printing.editor.tabSignatures', label: 'Signatures' },
497
+ { id: 'settings', icon: 'settings', key: 'printing.editor.tabSettings', label: 'Settings' },
476
498
  ];
477
499
 
478
500
  // โ”€โ”€โ”€ Right panel tabs โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
479
501
  type RightTab = 'position' | 'style' | 'data' | 'conditional';
480
- const RIGHT_TABS: { id: RightTab; label: string }[] = [
481
- { id: 'position', label: 'Pozycja' },
482
- { id: 'style', label: 'Styl' },
483
- { id: 'data', label: 'Dane' },
484
- { id: 'conditional', label: 'Warunki' },
502
+ const RIGHT_TABS: { id: RightTab; key: string; label: string }[] = [
503
+ { id: 'position', key: 'printing.editor.tabPosition', label: 'Position' },
504
+ { id: 'style', key: 'printing.editor.tabStyle', label: 'Style' },
505
+ { id: 'data', key: 'printing.editor.tabData', label: 'Data' },
506
+ { id: 'conditional', key: 'printing.editor.tabConditional', label: 'Conditions' },
485
507
  ];
486
508
 
487
509
  // โ”€โ”€โ”€ Toolbar โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -534,6 +556,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
534
556
  onSendBackward,
535
557
  onSave,
536
558
  }) => {
559
+ const { t: tr } = useNiceTranslation();
537
560
  const [insertOpen, setInsertOpen] = useState(false);
538
561
  const ZOOM_PRESETS = [0.5, 0.75, 1, 1.25, 1.5, 2];
539
562
 
@@ -557,14 +580,14 @@ const Toolbar: React.FC<ToolbarProps> = ({
557
580
  onClick={() => onPaperChange(tpl.paperSize, 'portrait')}
558
581
  title="Pion"
559
582
  >
560
- โฌœ
583
+ <NiceIcon name="file" size={14} />
561
584
  </button>
562
585
  <button
563
586
  className={`${styles.toolbarBtn} ${tpl.orientation === 'landscape' ? styles.toolbarBtnActive : ''}`}
564
587
  onClick={() => onPaperChange(tpl.paperSize, 'landscape')}
565
588
  title="Poziom"
566
589
  >
567
- โ–ญ
590
+ <NiceIcon name="file" size={14} style={{ transform: 'rotate(90deg)' }} />
568
591
  </button>
569
592
  </div>
570
593
 
@@ -575,17 +598,17 @@ const Toolbar: React.FC<ToolbarProps> = ({
575
598
  className={styles.toolbarBtn}
576
599
  disabled={!canUndo}
577
600
  onClick={onUndo}
578
- title="Cofnij (Ctrl+Z)"
601
+ title={`${tr('printing.editor.undo', 'Undo')} (Ctrl+Z)`}
579
602
  >
580
- โ†ฉ
603
+ <NiceIcon name="undo" size={14} />
581
604
  </button>
582
605
  <button
583
606
  className={styles.toolbarBtn}
584
607
  disabled={!canRedo}
585
608
  onClick={onRedo}
586
- title="Ponรณw (Ctrl+Y)"
609
+ title={`${tr('printing.editor.redo', 'Redo')} (Ctrl+Y)`}
587
610
  >
588
- โ†ช
611
+ <NiceIcon name="redo" size={14} />
589
612
  </button>
590
613
  </div>
591
614
  )}
@@ -597,11 +620,14 @@ const Toolbar: React.FC<ToolbarProps> = ({
597
620
  className={`${styles.toolbarBtn} ${styles.insertBtn}`}
598
621
  onClick={() => setInsertOpen((o) => !o)}
599
622
  >
600
- ๏ผ‹ Wstaw โ–พ
623
+ <NiceIcon name="plus" size={14} /> {tr('printing.editor.insert', 'Insert')}{' '}
624
+ <NiceIcon name="chevron-down" size={12} />
601
625
  </button>
602
626
  {insertOpen && (
603
627
  <div className={styles.insertDropdown} onMouseLeave={() => setInsertOpen(false)}>
604
- <div className={styles.insertCategory}>Pola danych</div>
628
+ <div className={styles.insertCategory}>
629
+ {tr('printing.editor.dataFields', 'Data fields')}
630
+ </div>
605
631
  {['text', 'number', 'currency', 'date', 'image', 'barcode', 'qr'].map((t) => (
606
632
  <button
607
633
  key={t}
@@ -612,7 +638,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
612
638
  }}
613
639
  >
614
640
  <span className={styles.insertIcon}>{FIELD_TYPE_ICONS[t]}</span>
615
- {FIELD_TYPE_LABELS[t]}
641
+ {fieldTypeLabel(tr, t)}
616
642
  </button>
617
643
  ))}
618
644
  <div className={styles.insertCategory}>Elementy statyczne</div>
@@ -634,7 +660,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
634
660
  }}
635
661
  >
636
662
  <span className={styles.insertIcon}>{FIELD_TYPE_ICONS[t]}</span>
637
- {FIELD_TYPE_LABELS[t]}
663
+ {fieldTypeLabel(tr, t)}
638
664
  </button>
639
665
  ))}
640
666
  <div className={styles.insertCategory}>Specjalne</div>
@@ -645,7 +671,10 @@ const Toolbar: React.FC<ToolbarProps> = ({
645
671
  setInsertOpen(false);
646
672
  }}
647
673
  >
648
- <span className={styles.insertIcon}>๐Ÿ’ง</span>Znak wodny
674
+ <span className={styles.insertIcon}>
675
+ <NiceIcon name="droplet" size={14} />
676
+ </span>
677
+ {tr('printing.editor.watermark', 'Watermark')}
649
678
  </button>
650
679
  <button
651
680
  className={styles.insertItem}
@@ -654,7 +683,10 @@ const Toolbar: React.FC<ToolbarProps> = ({
654
683
  setInsertOpen(false);
655
684
  }}
656
685
  >
657
- <span className={styles.insertIcon}>โœ</span>Podpis
686
+ <span className={styles.insertIcon}>
687
+ <NiceIcon name="pen" size={14} />
688
+ </span>
689
+ {tr('printing.editor.signature', 'Signature')}
658
690
  </button>
659
691
  </div>
660
692
  )}
@@ -664,53 +696,73 @@ const Toolbar: React.FC<ToolbarProps> = ({
664
696
  {/* Selection actions */}
665
697
  {!readOnly && selectedCount > 0 && (
666
698
  <div className={styles.toolbarGroup}>
667
- <button className={styles.toolbarBtn} onClick={onDuplicate} title="Duplikuj (Ctrl+D)">
668
- โง‰
699
+ <button
700
+ className={styles.toolbarBtn}
701
+ onClick={onDuplicate}
702
+ title={`${tr('printing.editor.duplicate', 'Duplicate')} (Ctrl+D)`}
703
+ >
704
+ <NiceIcon name="copy" size={14} />
669
705
  </button>
670
706
  <button
671
707
  className={`${styles.toolbarBtn} ${styles.toolbarBtnDanger}`}
672
708
  onClick={onDelete}
673
- title="Usuล„ (Del)"
709
+ title={`${tr('printing.action.remove', 'Remove')} (Del)`}
674
710
  >
675
- ๐Ÿ—‘
711
+ <NiceIcon name="trash" size={14} />
676
712
  </button>
677
713
  <span className={styles.toolbarSep} />
678
714
  <button
679
715
  className={styles.toolbarBtn}
680
716
  onClick={() => onAlignH('left')}
681
- title="Wyrรณwnaj lewo"
717
+ title={tr('printing.editor.alignLeft', 'Align left')}
682
718
  >
683
- โฌ›โ–กโ–ก
719
+ <NiceIcon name="align-left" size={14} />
684
720
  </button>
685
721
  <button
686
722
  className={styles.toolbarBtn}
687
723
  onClick={() => onAlignH('center')}
688
- title="Wyล›rodkuj"
724
+ title={tr('printing.editor.alignCenter', 'Align centre')}
689
725
  >
690
- โ–กโฌ›โ–ก
726
+ <NiceIcon name="align-center" size={14} />
691
727
  </button>
692
728
  <button
693
729
  className={styles.toolbarBtn}
694
730
  onClick={() => onAlignH('right')}
695
- title="Wyrรณwnaj prawo"
731
+ title={tr('printing.editor.alignRight', 'Align right')}
696
732
  >
697
- โ–กโ–กโฌ›
733
+ <NiceIcon name="align-right" size={14} />
698
734
  </button>
699
- <button className={styles.toolbarBtn} onClick={() => onAlignV('top')} title="Gรณra">
735
+ <button
736
+ className={styles.toolbarBtn}
737
+ onClick={() => onAlignV('top')}
738
+ title={tr('printing.editor.alignTop', 'Align top')}
739
+ >
700
740
  โŠค
701
741
  </button>
702
- <button className={styles.toolbarBtn} onClick={() => onAlignV('center')} title="ลšrodek V">
742
+ <button
743
+ className={styles.toolbarBtn}
744
+ onClick={() => onAlignV('center')}
745
+ title={tr('printing.editor.alignMiddle', 'Align middle')}
746
+ >
703
747
  โŠ•
704
748
  </button>
705
- <button className={styles.toolbarBtn} onClick={() => onAlignV('bottom')} title="Dรณล‚">
749
+ <button
750
+ className={styles.toolbarBtn}
751
+ onClick={() => onAlignV('bottom')}
752
+ title={tr('printing.editor.alignBottom', 'Align bottom')}
753
+ >
706
754
  โŠฅ
707
755
  </button>
708
756
  <span className={styles.toolbarSep} />
709
757
  <button className={styles.toolbarBtn} onClick={onBringForward} title="Do przodu">
710
- โ–ฒ
758
+ <NiceIcon name="arrow-up" size={14} />
711
759
  </button>
712
- <button className={styles.toolbarBtn} onClick={onSendBackward} title="Do tyล‚u">
713
- โ–ผ
760
+ <button
761
+ className={styles.toolbarBtn}
762
+ onClick={onSendBackward}
763
+ title={tr('printing.editor.sendBackward', 'Send backward')}
764
+ >
765
+ <NiceIcon name="arrow-down" size={14} />
714
766
  </button>
715
767
  </div>
716
768
  )}
@@ -720,16 +772,16 @@ const Toolbar: React.FC<ToolbarProps> = ({
720
772
  <button
721
773
  className={`${styles.toolbarBtn} ${showGrid ? styles.toolbarBtnActive : ''}`}
722
774
  onClick={onToggleGrid}
723
- title="Siatka"
775
+ title={tr('printing.editor.grid', 'Grid')}
724
776
  >
725
777
  โŠž
726
778
  </button>
727
779
  <button
728
780
  className={`${styles.toolbarBtn} ${showRulers ? styles.toolbarBtnActive : ''}`}
729
781
  onClick={onToggleRulers}
730
- title="Linijki"
782
+ title={tr('printing.editor.rulers', 'Rulers')}
731
783
  >
732
- โŠข
784
+ <NiceIcon name="ruler" size={14} />
733
785
  </button>
734
786
  </div>
735
787
 
@@ -759,7 +811,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
759
811
 
760
812
  {!readOnly && onSave && (
761
813
  <button className={styles.saveBtn} onClick={onSave}>
762
- ๐Ÿ’พ Zapisz
814
+ <NiceIcon name="save" size={14} /> {tr('printing.editor.save', 'Save')}
763
815
  </button>
764
816
  )}
765
817
  </div>
@@ -792,6 +844,7 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
792
844
  onUpdateTpl,
793
845
  onReorderField,
794
846
  }) => {
847
+ const { t: tr } = useNiceTranslation();
795
848
  const [search, setSearch] = useState('');
796
849
  const filteredFields = schema?.fields.filter(
797
850
  (f) =>
@@ -807,10 +860,12 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
807
860
  key={t.id}
808
861
  className={`${styles.leftTab} ${activeTab === t.id ? styles.leftTabActive : ''}`}
809
862
  onClick={() => onTabChange(t.id)}
810
- title={t.label}
863
+ title={tr(t.key, t.label)}
811
864
  >
812
- <span className={styles.leftTabIcon}>{t.icon}</span>
813
- <span className={styles.leftTabLabel}>{t.label}</span>
865
+ <span className={styles.leftTabIcon}>
866
+ <NiceIcon name={t.icon} size={16} />
867
+ </span>
868
+ <span className={styles.leftTabLabel}>{tr(t.key, t.label)}</span>
814
869
  </button>
815
870
  ))}
816
871
  </div>
@@ -819,11 +874,13 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
819
874
  {/* โ”€โ”€ Fields tab โ”€โ”€ */}
820
875
  {activeTab === 'fields' && (
821
876
  <>
822
- <div className={styles.panelHeader}>Pola danych</div>
877
+ <div className={styles.panelHeader}>
878
+ {tr('printing.editor.dataFields', 'Data fields')}
879
+ </div>
823
880
  <div className={styles.searchBox}>
824
881
  <input
825
882
  className={styles.searchInput}
826
- placeholder="Szukaj..."
883
+ placeholder={tr('printing.editor.search', 'Searchโ€ฆ')}
827
884
  value={search}
828
885
  onChange={(e) => setSearch(e.target.value)}
829
886
  />
@@ -842,7 +899,9 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
842
899
  }}
843
900
  onClick={() => onInsertSchemaField(f)}
844
901
  >
845
- <span className={styles.fieldItemIcon}>{FIELD_TYPE_ICONS[f.type] ?? '๐Ÿ“'}</span>
902
+ <span className={styles.fieldItemIcon}>
903
+ <NiceIcon name={FIELD_TYPE_ICONS[f.type] ?? 'type'} size={14} />
904
+ </span>
846
905
  <div className={styles.fieldItemInfo}>
847
906
  <div className={styles.fieldItemLabel}>{f.label}</div>
848
907
  <div className={styles.fieldItemPath}>{f.path}</div>
@@ -851,12 +910,17 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
851
910
  </div>
852
911
  ))}
853
912
  {filteredFields?.length === 0 && (
854
- <div className={styles.emptyMsg}>Brak wynikรณw</div>
913
+ <div className={styles.emptyMsg}>
914
+ {tr('printing.editor.noMatches', 'No matches')}
915
+ </div>
855
916
  )}
856
917
  </div>
857
918
  ) : (
858
919
  <div className={styles.emptyMsg}>
859
- Brak schematu danych. Przypisz encjฤ™ w zakล‚adce Ustawienia.
920
+ {tr(
921
+ 'printing.editor.noSchema',
922
+ 'No data schema. Assign an entity in the Settings tab.',
923
+ )}
860
924
  </div>
861
925
  )}
862
926
  </>
@@ -865,9 +929,15 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
865
929
  {/* โ”€โ”€ Layers tab โ”€โ”€ */}
866
930
  {activeTab === 'layers' && (
867
931
  <>
868
- <div className={styles.panelHeader}>Warstwy ({allFields.length})</div>
932
+ <div className={styles.panelHeader}>
933
+ {tr('printing.editor.layers', 'Layers')} ({allFields.length})
934
+ </div>
869
935
  <div className={styles.layerList}>
870
- {allFields.length === 0 && <div className={styles.emptyMsg}>Brak elementรณw</div>}
936
+ {allFields.length === 0 && (
937
+ <div className={styles.emptyMsg}>
938
+ {tr('printing.editor.noElements', 'No elements')}
939
+ </div>
940
+ )}
871
941
  {allFields
872
942
  .slice()
873
943
  .reverse()
@@ -877,7 +947,9 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
877
947
  className={`${styles.layerItem} ${selectedIds.has(f.id) ? styles.layerItemSelected : ''}`}
878
948
  onClick={() => onSelectField(f.id)}
879
949
  >
880
- <span className={styles.layerIcon}>{FIELD_TYPE_ICONS[f.type] ?? '๐Ÿ“'}</span>
950
+ <span className={styles.layerIcon}>
951
+ <NiceIcon name={FIELD_TYPE_ICONS[f.type] ?? 'type'} size={14} />
952
+ </span>
881
953
  <span className={styles.layerName}>{f.name || f.label}</span>
882
954
  <div className={styles.layerActions}>
883
955
  <button
@@ -886,9 +958,9 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
886
958
  e.stopPropagation();
887
959
  onReorderField(f.id, -1);
888
960
  }}
889
- title="Do przodu"
961
+ title={tr('printing.editor.bringForward', 'Bring forward')}
890
962
  >
891
- โ†‘
963
+ <NiceIcon name="arrow-up" size={12} />
892
964
  </button>
893
965
  <button
894
966
  className={styles.layerBtn}
@@ -896,9 +968,9 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
896
968
  e.stopPropagation();
897
969
  onReorderField(f.id, 1);
898
970
  }}
899
- title="Do tyล‚u"
971
+ title={tr('printing.editor.sendBackward', 'Send backward')}
900
972
  >
901
- โ†“
973
+ <NiceIcon name="arrow-down" size={12} />
902
974
  </button>
903
975
  </div>
904
976
  </div>
@@ -915,7 +987,7 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
915
987
  {(tpl.watermarks ?? []).map((wm) => (
916
988
  <div key={wm.id} className={styles.wmItem}>
917
989
  <div className={styles.wmRow}>
918
- <span className={styles.wmLabel}>Typ</span>
990
+ <span className={styles.wmLabel}>{tr('printing.editor.type', 'Type')}</span>
919
991
  <select
920
992
  className={styles.wmSelect}
921
993
  value={wm.type}
@@ -927,13 +999,15 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
927
999
  })
928
1000
  }
929
1001
  >
930
- <option value="text">Tekst</option>
931
- <option value="image">Obraz</option>
1002
+ <option value="text">{tr('printing.fieldType.text', 'Text')}</option>
1003
+ <option value="image">{tr('printing.fieldType.image', 'Image')}</option>
932
1004
  </select>
933
1005
  </div>
934
1006
  {wm.type === 'text' && (
935
1007
  <div className={styles.wmRow}>
936
- <span className={styles.wmLabel}>Tekst</span>
1008
+ <span className={styles.wmLabel}>
1009
+ {tr('printing.fieldType.text', 'Text')}
1010
+ </span>
937
1011
  <input
938
1012
  className={styles.wmInput}
939
1013
  value={wm.text ?? ''}
@@ -967,7 +1041,7 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
967
1041
  <span className={styles.wmValue}>{Math.round(wm.opacity * 100)}%</span>
968
1042
  </div>
969
1043
  <div className={styles.wmRow}>
970
- <span className={styles.wmLabel}>Kฤ…t</span>
1044
+ <span className={styles.wmLabel}>{tr('printing.editor.angle', 'Angle')}</span>
971
1045
  <input
972
1046
  type="range"
973
1047
  min={-90}
@@ -986,7 +1060,7 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
986
1060
  <span className={styles.wmValue}>{wm.angle}ยฐ</span>
987
1061
  </div>
988
1062
  <div className={styles.wmRow}>
989
- <span className={styles.wmLabel}>Rozmiar</span>
1063
+ <span className={styles.wmLabel}>{tr('printing.editor.size', 'Size')}</span>
990
1064
  <input
991
1065
  type="number"
992
1066
  min={8}
@@ -1004,11 +1078,11 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
1004
1078
  <span className={styles.wmUnit}>pt</span>
1005
1079
  </div>
1006
1080
  <div className={styles.wmRow}>
1007
- <span className={styles.wmLabel}>Kolor</span>
1081
+ <span className={styles.wmLabel}>{tr('printing.editor.colour', 'Colour')}</span>
1008
1082
  <input
1009
1083
  type="color"
1010
1084
  className={styles.colorPicker}
1011
- value={wm.color ?? 'var(--nice-text-muted, #94a3b8)'}
1085
+ value={wm.color ?? INK_FAINT}
1012
1086
  onChange={(e) =>
1013
1087
  onUpdateTpl({
1014
1088
  watermarks: tpl.watermarks?.map((w) =>
@@ -1024,7 +1098,7 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
1024
1098
  onUpdateTpl({ watermarks: tpl.watermarks?.filter((w) => w.id !== wm.id) })
1025
1099
  }
1026
1100
  >
1027
- Usuล„
1101
+ {tr('printing.action.remove', 'Remove')}
1028
1102
  </button>
1029
1103
  </div>
1030
1104
  ))}
@@ -1037,17 +1111,18 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
1037
1111
  {
1038
1112
  id: uid(),
1039
1113
  type: 'text',
1040
- text: 'KOPIA',
1114
+ text: 'COPY',
1041
1115
  opacity: 0.12,
1042
1116
  angle: -45,
1043
1117
  fontSize: 60,
1044
- color: 'var(--nice-text-muted, #94a3b8)',
1118
+ color: INK_FAINT,
1045
1119
  },
1046
1120
  ],
1047
1121
  })
1048
1122
  }
1049
1123
  >
1050
- + Dodaj znak wodny
1124
+ <NiceIcon name="plus" size={12} />{' '}
1125
+ {tr('printing.editor.addWatermark', 'Add watermark')}
1051
1126
  </button>
1052
1127
  </div>
1053
1128
  </>
@@ -1089,7 +1164,7 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
1089
1164
  />
1090
1165
  </div>
1091
1166
  <div className={styles.wmRow}>
1092
- <span className={styles.wmLabel}>Linia</span>
1167
+ <span className={styles.wmLabel}>{tr('printing.fieldType.line', 'Line')}</span>
1093
1168
  <input
1094
1169
  type="checkbox"
1095
1170
  checked={sig.showLine ?? true}
@@ -1103,7 +1178,9 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
1103
1178
  />
1104
1179
  </div>
1105
1180
  <div className={styles.wmRow}>
1106
- <span className={styles.wmLabel}>Pokaลผ datฤ™</span>
1181
+ <span className={styles.wmLabel}>
1182
+ {tr('printing.editor.showDate', 'Show date')}
1183
+ </span>
1107
1184
  <input
1108
1185
  type="checkbox"
1109
1186
  checked={sig.showDate ?? false}
@@ -1122,7 +1199,7 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
1122
1199
  onUpdateTpl({ signatures: tpl.signatures?.filter((s) => s.id !== sig.id) })
1123
1200
  }
1124
1201
  >
1125
- Usuล„
1202
+ {tr('printing.action.remove', 'Remove')}
1126
1203
  </button>
1127
1204
  </div>
1128
1205
  ))}
@@ -1134,7 +1211,7 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
1134
1211
  ...(tpl.signatures ?? []),
1135
1212
  {
1136
1213
  id: uid(),
1137
- label: 'Podpis',
1214
+ label: tr('printing.editor.signature', 'Signature'),
1138
1215
  position: { x: 10, y: 5, width: 70, height: 22 },
1139
1216
  showLine: true,
1140
1217
  showDate: false,
@@ -1143,7 +1220,8 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
1143
1220
  })
1144
1221
  }
1145
1222
  >
1146
- + Dodaj podpis
1223
+ <NiceIcon name="plus" size={12} />{' '}
1224
+ {tr('printing.editor.addSignature', 'Add signature')}
1147
1225
  </button>
1148
1226
  </div>
1149
1227
  </>
@@ -1152,10 +1230,12 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
1152
1230
  {/* โ”€โ”€ Settings tab โ”€โ”€ */}
1153
1231
  {activeTab === 'settings' && (
1154
1232
  <>
1155
- <div className={styles.panelHeader}>Ustawienia szablonu</div>
1233
+ <div className={styles.panelHeader}>
1234
+ {tr('printing.editor.templateSettings', 'Template settings')}
1235
+ </div>
1156
1236
  <div className={styles.settingsForm}>
1157
1237
  <div className={styles.settingsRow}>
1158
- <label className={styles.settingsLabel}>Nazwa</label>
1238
+ <label className={styles.settingsLabel}>{tr('printing.editor.name', 'Name')}</label>
1159
1239
  <input
1160
1240
  className={styles.settingsInput}
1161
1241
  value={tpl.name}
@@ -1163,7 +1243,9 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
1163
1243
  />
1164
1244
  </div>
1165
1245
  <div className={styles.settingsRow}>
1166
- <label className={styles.settingsLabel}>Opis</label>
1246
+ <label className={styles.settingsLabel}>
1247
+ {tr('printing.editor.description', 'Description')}
1248
+ </label>
1167
1249
  <textarea
1168
1250
  className={styles.settingsTextarea}
1169
1251
  value={tpl.description ?? ''}
@@ -1179,17 +1261,19 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
1179
1261
  onChange={(e) => onUpdateTpl({ category: e.target.value })}
1180
1262
  />
1181
1263
  </div>
1182
- <div className={styles.settingsDivider}>Marginesy (mm)</div>
1264
+ <div className={styles.settingsDivider}>
1265
+ {tr('printing.editor.margins', 'Margins (mm)')}
1266
+ </div>
1183
1267
  {(['top', 'right', 'bottom', 'left'] as const).map((side) => (
1184
1268
  <div key={side} className={styles.settingsRow}>
1185
1269
  <label className={styles.settingsLabel}>
1186
1270
  {side === 'top'
1187
- ? 'Gรณra'
1271
+ ? tr('printing.editor.marginTop', 'Top')
1188
1272
  : side === 'right'
1189
- ? 'Prawo'
1273
+ ? tr('printing.editor.marginRight', 'Right')
1190
1274
  : side === 'bottom'
1191
- ? 'Dรณล‚'
1192
- : 'Lewo'}
1275
+ ? tr('printing.editor.marginBottom', 'Bottom')
1276
+ : tr('printing.editor.marginLeft', 'Left')}
1193
1277
  </label>
1194
1278
  <input
1195
1279
  type="number"
@@ -1231,6 +1315,7 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1231
1315
  paperWidthMm,
1232
1316
  paperHeightMm: _paperHeightMm,
1233
1317
  }) => {
1318
+ const { t: tr } = useNiceTranslation();
1234
1319
  const p = useCallback(
1235
1320
  (patch: Partial<FieldPosition>) =>
1236
1321
  field && onChange({ position: { ...field.position, ...patch } }),
@@ -1245,17 +1330,21 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1245
1330
  if (!field) {
1246
1331
  return (
1247
1332
  <div className={styles.rightPanel}>
1248
- <div className={styles.panelHeader}>Wล‚aล›ciwoล›ci</div>
1333
+ <div className={styles.panelHeader}>{tr('printing.editor.properties', 'Properties')}</div>
1249
1334
  <div className={styles.propsEmpty}>
1250
- <div className={styles.propsEmptyIcon}>โฌš</div>
1251
- <div className={styles.propsEmptyText}>Zaznacz element na szablonie</div>
1335
+ <div className={styles.propsEmptyIcon}>
1336
+ <NiceIcon name="cursor" size={28} />
1337
+ </div>
1338
+ <div className={styles.propsEmptyText}>
1339
+ {tr('printing.editor.selectElement', 'Select an element on the template')}
1340
+ </div>
1252
1341
  <div className={styles.shortcutGrid}>
1253
- <ShortcutChip keys="Del" label="Usuล„" />
1254
- <ShortcutChip keys="Ctrl+D" label="Duplikuj" />
1255
- <ShortcutChip keys="โ†‘โ†“โ†โ†’" label="Przesuล„" />
1256
- <ShortcutChip keys="Ctrl+Z" label="Cofnij" />
1257
- <ShortcutChip keys="Ctrl+A" label="Zaznacz wszystko" />
1258
- <ShortcutChip keys="Shift+โ†‘" label="Przesuล„ 5mm" />
1342
+ <ShortcutChip keys="Del" label={tr('printing.action.remove', 'Remove')} />
1343
+ <ShortcutChip keys="Ctrl+D" label={tr('printing.editor.duplicate', 'Duplicate')} />
1344
+ <ShortcutChip keys="โ†‘โ†“โ†โ†’" label={tr('printing.editor.move', 'Move')} />
1345
+ <ShortcutChip keys="Ctrl+Z" label={tr('printing.editor.undo', 'Undo')} />
1346
+ <ShortcutChip keys="Ctrl+A" label={tr('printing.editor.selectAll', 'Select all')} />
1347
+ <ShortcutChip keys="Shift+โ†‘" label={tr('printing.editor.move5mm', 'Move 5 mm')} />
1259
1348
  </div>
1260
1349
  </div>
1261
1350
  </div>
@@ -1293,7 +1382,7 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1293
1382
  className={`${styles.rightTab} ${activeTab === t.id ? styles.rightTabActive : ''}`}
1294
1383
  onClick={() => onTabChange(t.id)}
1295
1384
  >
1296
- {t.label}
1385
+ {tr(t.key, t.label)}
1297
1386
  </button>
1298
1387
  );
1299
1388
  })}
@@ -1323,7 +1412,9 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1323
1412
  </div>
1324
1413
  ))}
1325
1414
  <div className={styles.posCell}>
1326
- <label className={styles.posLabel}>Obrรณt</label>
1415
+ <label className={styles.posLabel}>
1416
+ {tr('printing.editor.rotation', 'Rotation')}
1417
+ </label>
1327
1418
  <input
1328
1419
  type="number"
1329
1420
  step={5}
@@ -1347,24 +1438,30 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1347
1438
  </div>
1348
1439
 
1349
1440
  <div className={styles.snapSection}>
1350
- <div className={styles.snapTitle}>Wyrรณwnanie do strony</div>
1441
+ <div className={styles.snapTitle}>
1442
+ {tr('printing.editor.snapToPage', 'Snap to page')}
1443
+ </div>
1351
1444
  <div className={styles.snapRow}>
1352
- <button className={styles.snapBtn} onClick={() => p({ x: 0 })} title="Lewa krawฤ™dลบ">
1353
- โซท
1445
+ <button
1446
+ className={styles.snapBtn}
1447
+ onClick={() => p({ x: 0 })}
1448
+ title={tr('printing.editor.leftEdge', 'Left edge')}
1449
+ >
1450
+ <NiceIcon name="align-left" size={14} />
1354
1451
  </button>
1355
1452
  <button
1356
1453
  className={styles.snapBtn}
1357
1454
  onClick={() => p({ x: (paperWidthMm - field.position.width) / 2 })}
1358
- title="ลšrodek poziomy"
1455
+ title={tr('printing.editor.horizontalCentre', 'Horizontal centre')}
1359
1456
  >
1360
1457
  โŠ•
1361
1458
  </button>
1362
1459
  <button
1363
1460
  className={styles.snapBtn}
1364
1461
  onClick={() => p({ x: paperWidthMm - field.position.width })}
1365
- title="Prawa krawฤ™dลบ"
1462
+ title={tr('printing.editor.rightEdge', 'Right edge')}
1366
1463
  >
1367
- โซธ
1464
+ <NiceIcon name="align-right" size={14} />
1368
1465
  </button>
1369
1466
  </div>
1370
1467
  </div>
@@ -1392,7 +1489,7 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1392
1489
  </select>
1393
1490
  </div>
1394
1491
  <div className={styles.styleRow}>
1395
- <label className={styles.styleLabel}>Rozmiar</label>
1492
+ <label className={styles.styleLabel}>{tr('printing.editor.size', 'Size')}</label>
1396
1493
  <select
1397
1494
  className={styles.styleSelectSm}
1398
1495
  value={
@@ -1420,22 +1517,26 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1420
1517
  <span className={styles.styleUnit}>pt</span>
1421
1518
  </div>
1422
1519
  <div className={styles.styleRow}>
1423
- <label className={styles.styleLabel}>Kolor</label>
1520
+ <label className={styles.styleLabel}>
1521
+ {tr('printing.editor.colour', 'Colour')}
1522
+ </label>
1424
1523
  <input
1425
1524
  type="color"
1426
1525
  className={styles.colorPicker}
1427
- value={field.style.color ?? 'var(--nice-text, #000000)'}
1526
+ value={field.style.color ?? INK}
1428
1527
  onChange={(e) => s({ color: e.target.value })}
1429
1528
  />
1430
1529
  <input
1431
1530
  className={styles.colorHex}
1432
- value={field.style.color ?? 'var(--nice-text, #000000)'}
1531
+ value={field.style.color ?? INK}
1433
1532
  onChange={(e) => s({ color: e.target.value })}
1434
1533
  maxLength={7}
1435
1534
  />
1436
1535
  </div>
1437
1536
  <div className={styles.styleRow}>
1438
- <label className={styles.styleLabel}>Styl</label>
1537
+ <label className={styles.styleLabel}>
1538
+ {tr('printing.editor.style', 'Style')}
1539
+ </label>
1439
1540
  <div className={styles.toggleGroup}>
1440
1541
  <button
1441
1542
  className={`${styles.toggleBtn} ${field.style.fontWeight === 'bold' ? styles.toggleBtnActive : ''}`}
@@ -1456,7 +1557,9 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1456
1557
  </div>
1457
1558
  </div>
1458
1559
  <div className={styles.styleRow}>
1459
- <label className={styles.styleLabel}>Wyrรณwn.</label>
1560
+ <label className={styles.styleLabel}>
1561
+ {tr('printing.editor.align', 'Align')}
1562
+ </label>
1460
1563
  <div className={styles.toggleGroup}>
1461
1564
  {(['left', 'center', 'right', 'justify'] as const).map((a) => (
1462
1565
  <button
@@ -1483,7 +1586,9 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1483
1586
  />
1484
1587
  </div>
1485
1588
  <div className={styles.styleRow}>
1486
- <label className={styles.styleLabel}>Odstฤ™py</label>
1589
+ <label className={styles.styleLabel}>
1590
+ {tr('printing.editor.spacing', 'Spacing')}
1591
+ </label>
1487
1592
  <input
1488
1593
  type="number"
1489
1594
  step={0.01}
@@ -1499,9 +1604,11 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1499
1604
  )}
1500
1605
 
1501
1606
  <div className={styles.styleGroup}>
1502
- <div className={styles.styleGroupTitle}>Tล‚o i ramka</div>
1607
+ <div className={styles.styleGroupTitle}>
1608
+ {tr('printing.editor.background', 'Background and border')}
1609
+ </div>
1503
1610
  <div className={styles.styleRow}>
1504
- <label className={styles.styleLabel}>Tล‚o</label>
1611
+ <label className={styles.styleLabel}>{tr('printing.editor.fill', 'Fill')}</label>
1505
1612
  <input
1506
1613
  type="color"
1507
1614
  className={styles.colorPicker}
@@ -1511,9 +1618,9 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1511
1618
  <button
1512
1619
  className={styles.clearBtn}
1513
1620
  onClick={() => s({ backgroundColor: undefined })}
1514
- title="Wyczyล›ฤ‡ tล‚o"
1621
+ title={tr('printing.editor.clearFill', 'Clear fill')}
1515
1622
  >
1516
- โœ•
1623
+ <NiceIcon name="close" size={12} />
1517
1624
  </button>
1518
1625
  </div>
1519
1626
  <div className={styles.styleRow}>
@@ -1530,7 +1637,7 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1530
1637
  <input
1531
1638
  type="color"
1532
1639
  className={styles.colorPicker}
1533
- value={field.style.borderColor ?? 'var(--nice-text, #000000)'}
1640
+ value={field.style.borderColor ?? INK}
1534
1641
  onChange={(e) => s({ borderColor: e.target.value })}
1535
1642
  />
1536
1643
  <select
@@ -1591,9 +1698,11 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1591
1698
  {/* โ”€โ”€ Data โ”€โ”€ */}
1592
1699
  {activeTab === 'data' && hasData && (
1593
1700
  <div className={styles.styleGroup}>
1594
- <div className={styles.styleGroupTitle}>ลนrรณdล‚o danych</div>
1701
+ <div className={styles.styleGroupTitle}>
1702
+ {tr('printing.editor.dataSource', 'Data source')}
1703
+ </div>
1595
1704
  <div className={styles.styleRow}>
1596
- <label className={styles.styleLabel}>ลšcieลผka</label>
1705
+ <label className={styles.styleLabel}>{tr('printing.editor.path', 'Path')}</label>
1597
1706
  <input
1598
1707
  className={styles.styleInputFull}
1599
1708
  value={field.dataPath}
@@ -1602,7 +1711,7 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1602
1711
  />
1603
1712
  </div>
1604
1713
  <div className={styles.styleRow}>
1605
- <label className={styles.styleLabel}>Format</label>
1714
+ <label className={styles.styleLabel}>{tr('printing.editor.format', 'Format')}</label>
1606
1715
  <input
1607
1716
  className={styles.styleInputFull}
1608
1717
  value={field.format ?? ''}
@@ -1611,31 +1720,35 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1611
1720
  />
1612
1721
  </div>
1613
1722
  <div className={styles.styleRow}>
1614
- <label className={styles.styleLabel}>Domyล›lny</label>
1723
+ <label className={styles.styleLabel}>
1724
+ {tr('printing.editor.default', 'Default')}
1725
+ </label>
1615
1726
  <input
1616
1727
  className={styles.styleInputFull}
1617
1728
  value={field.fallback ?? ''}
1618
1729
  onChange={(e) => onChange({ fallback: e.target.value })}
1619
- placeholder="Gdy brak wartoล›ci"
1730
+ placeholder={tr('printing.editor.whenEmpty', 'When there is no value')}
1620
1731
  />
1621
1732
  </div>
1622
1733
  <div className={styles.styleRow}>
1623
- <label className={styles.styleLabel}>Typ</label>
1734
+ <label className={styles.styleLabel}>{tr('printing.editor.type', 'Type')}</label>
1624
1735
  <select
1625
1736
  className={styles.styleSelectFull}
1626
1737
  value={field.type}
1627
1738
  onChange={(e) => onChange({ type: e.target.value as PrintDataField['type'] })}
1628
1739
  >
1629
- {Object.entries(FIELD_TYPE_LABELS).map(([k, v]) => (
1740
+ {Object.keys(FIELD_TYPE_LABELS).map((k) => (
1630
1741
  <option key={k} value={k}>
1631
- {v}
1742
+ {fieldTypeLabel(tr, k)}
1632
1743
  </option>
1633
1744
  ))}
1634
1745
  </select>
1635
1746
  </div>
1636
1747
  {(field.type === 'barcode' || field.type === 'qr') && (
1637
1748
  <div className={styles.styleRow}>
1638
- <label className={styles.styleLabel}>Format kodu</label>
1749
+ <label className={styles.styleLabel}>
1750
+ {tr('printing.editor.codeFormat', 'Code format')}
1751
+ </label>
1639
1752
  <select
1640
1753
  className={styles.styleSelectFull}
1641
1754
  value={field.barcodeType ?? 'code128'}
@@ -1655,12 +1768,19 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1655
1768
  {/* โ”€โ”€ Conditional โ”€โ”€ */}
1656
1769
  {activeTab === 'conditional' && (
1657
1770
  <div className={styles.styleGroup}>
1658
- <div className={styles.styleGroupTitle}>Widocznoล›ฤ‡ warunkowa</div>
1771
+ <div className={styles.styleGroupTitle}>
1772
+ {tr('printing.editor.conditionalVisibility', 'Conditional visibility')}
1773
+ </div>
1659
1774
  <div className={styles.conditionalHint}>
1660
- Element zostanie ukryty gdy warunek nie jest speล‚niony.
1775
+ {tr(
1776
+ 'printing.editor.conditionalHint',
1777
+ 'The element is hidden when the condition is not met.',
1778
+ )}
1661
1779
  </div>
1662
1780
  <div className={styles.styleRow}>
1663
- <label className={styles.styleLabel}>Pole</label>
1781
+ <label className={styles.styleLabel}>
1782
+ {tr('printing.editor.conditionalField', 'Field')}
1783
+ </label>
1664
1784
  <input
1665
1785
  className={styles.styleInputFull}
1666
1786
  value={field.conditional?.field ?? ''}
@@ -1693,15 +1813,15 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1693
1813
  })
1694
1814
  }
1695
1815
  >
1696
- <option value="eq">= r๏ฟฝwne</option>
1697
- <option value="neq">โ‰  rรณลผne</option>
1816
+ <option value="eq">= {tr('printing.editor.opEqual', 'equal')}</option>
1817
+ <option value="neq">โ‰  {tr('printing.editor.opNotEqual', 'not equal')}</option>
1698
1818
  <option value="gt">&gt; wieksze</option>
1699
1819
  <option value="lt">&lt; mniejsze</option>
1700
1820
  <option value="contains">zawiera</option>
1701
1821
  </select>
1702
1822
  </div>
1703
1823
  <div className={styles.styleRow}>
1704
- <label className={styles.styleLabel}>Wartosc</label>
1824
+ <label className={styles.styleLabel}>{tr('printing.editor.value', 'Value')}</label>
1705
1825
  <input
1706
1826
  className={styles.styleInputFull}
1707
1827
  value={String(field.conditional?.value ?? '')}
@@ -1722,7 +1842,7 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1722
1842
  className={styles.clearConditionalBtn}
1723
1843
  onClick={() => onChange({ conditional: undefined })}
1724
1844
  >
1725
- Wyczyล›ฤ‡ warunek
1845
+ {tr('printing.editor.clearCondition', 'Clear condition')}
1726
1846
  </button>
1727
1847
  )}
1728
1848
  </div>
@@ -1736,7 +1856,7 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1736
1856
  function defaultTemplate(): PrintTemplate {
1737
1857
  return {
1738
1858
  id: `tpl-${Date.now()}`,
1739
- name: 'Nowy szablon',
1859
+ name: 'New template',
1740
1860
  description: '',
1741
1861
  paperSize: 'A4',
1742
1862
  orientation: 'portrait',
@@ -1768,6 +1888,7 @@ export const NiceTemplateEditor: React.FC<NiceTemplateEditorProps> = ({
1768
1888
  present: initialTemplate ?? defaultTemplate(),
1769
1889
  future: [],
1770
1890
  });
1891
+ const { t: tr } = useNiceTranslation();
1771
1892
  const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
1772
1893
  const [activeSection, _setActiveSection] = useState('sec-body');
1773
1894
  const [zoom, setZoom] = useState(1);
@@ -1840,11 +1961,11 @@ export const NiceTemplateEditor: React.FC<NiceTemplateEditorProps> = ({
1840
1961
  {
1841
1962
  id: uid(),
1842
1963
  type: 'text',
1843
- text: 'KOPIA',
1964
+ text: 'COPY',
1844
1965
  opacity: 0.12,
1845
1966
  angle: -45,
1846
1967
  fontSize: 60,
1847
- color: 'var(--nice-text-muted, #94a3b8)',
1968
+ color: INK_FAINT,
1848
1969
  },
1849
1970
  ],
1850
1971
  });
@@ -1857,7 +1978,7 @@ export const NiceTemplateEditor: React.FC<NiceTemplateEditorProps> = ({
1857
1978
  ...(tpl.signatures ?? []),
1858
1979
  {
1859
1980
  id: uid(),
1860
- label: 'Podpis',
1981
+ label: tr('printing.editor.signature', 'Signature'),
1861
1982
  position: { x: 10, y: 5, width: 70, height: 22 },
1862
1983
  showLine: true,
1863
1984
  showDate: false,
@@ -2313,7 +2434,9 @@ export const NiceTemplateEditor: React.FC<NiceTemplateEditorProps> = ({
2313
2434
  {/* Test data bar */}
2314
2435
  {Array.isArray(sampleData) && sampleData.length > 0 && (
2315
2436
  <div className={styles.testDataBar}>
2316
- <span className={styles.testDataLabel}>Dane testowe:</span>
2437
+ <span className={styles.testDataLabel}>
2438
+ {tr('printing.editor.testData', 'Sample data')}:
2439
+ </span>
2317
2440
  <select
2318
2441
  className={styles.testDataSelect}
2319
2442
  value={testDataIdx}
@@ -2321,7 +2444,7 @@ export const NiceTemplateEditor: React.FC<NiceTemplateEditorProps> = ({
2321
2444
  >
2322
2445
  {sampleData.map((_, i) => (
2323
2446
  <option key={i} value={i}>
2324
- Rekord {i + 1}
2447
+ {tr('printing.editor.record', 'Record')} {i + 1}
2325
2448
  </option>
2326
2449
  ))}
2327
2450
  </select>
@@ -2358,12 +2481,16 @@ export const NiceTemplateEditor: React.FC<NiceTemplateEditorProps> = ({
2358
2481
  className={styles.watermark}
2359
2482
  style={{
2360
2483
  fontSize: (wm.fontSize ?? 60) * zoom,
2361
- color: wm.color ?? 'var(--nice-text-muted, #94a3b8)',
2484
+ color: wm.color ?? INK_FAINT,
2362
2485
  opacity: wm.opacity,
2363
2486
  transform: `translate(-50%, -50%) rotate(${wm.angle}deg)`,
2364
2487
  }}
2365
2488
  >
2366
- {wm.type === 'text' ? wm.text : '??'}
2489
+ {wm.type === 'text' ? (
2490
+ wm.text
2491
+ ) : (
2492
+ <NiceIcon name="image" size={(wm.fontSize ?? 60) * zoom} />
2493
+ )}
2367
2494
  </div>
2368
2495
  ))}
2369
2496