@nice2dev/ui-printing 1.1.6 โ†’ 1.1.9

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 (32) hide show
  1. package/dist/index.cjs +2 -2
  2. package/dist/index.d.ts +5 -5
  3. package/dist/index.mjs +1894 -1728
  4. package/dist/ui-printing.css +1 -1
  5. package/package.json +3 -2
  6. package/src/index.ts +5 -24
  7. package/src/print-button/NicePrintButton.tsx +44 -18
  8. package/src/print-button/PrintButton.module.css +147 -43
  9. package/src/print-preview/NicePrintPreview.tsx +19 -17
  10. package/src/print-preview/PrintPreview.module.css +60 -18
  11. package/src/print-queue/NicePrintQueue.tsx +76 -42
  12. package/src/print-queue/PrintQueue.module.css +207 -57
  13. package/src/template-browser/NiceTemplateBrowser.tsx +44 -19
  14. package/src/template-browser/TemplateBrowser.module.css +70 -25
  15. package/src/template-editor/NiceTemplateEditor.tsx +307 -182
  16. package/src/template-editor/TemplateEditor.module.css +184 -128
  17. package/src/types/printingTypes.ts +1 -1
  18. package/dist/index.d.ts.map +0 -1
  19. package/dist/print-button/NicePrintButton.d.ts.map +0 -1
  20. package/dist/print-button/index.d.ts.map +0 -1
  21. package/dist/print-preview/NicePrintPreview.d.ts.map +0 -1
  22. package/dist/print-preview/index.d.ts.map +0 -1
  23. package/dist/print-queue/NicePrintQueue.d.ts.map +0 -1
  24. package/dist/print-queue/index.d.ts.map +0 -1
  25. package/dist/style.css +0 -1
  26. package/dist/template-browser/NiceTemplateBrowser.d.ts.map +0 -1
  27. package/dist/template-browser/index.d.ts.map +0 -1
  28. package/dist/template-editor/NiceTemplateEditor.d.ts.map +0 -1
  29. package/dist/template-editor/index.d.ts.map +0 -1
  30. package/dist/types/printingTypes.d.ts.map +0 -1
  31. package/src/template-editor/NiceTemplateEditor.tsx.first +0 -1011
  32. 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,52 +56,55 @@ 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 =
101
- | { type: 'UPDATE'; payload: PrintTemplate }
102
- | { type: 'UNDO' }
103
- | { type: 'REDO' };
107
+ { type: 'UPDATE'; payload: PrintTemplate } | { type: 'UNDO' } | { type: 'REDO' };
104
108
 
105
109
  const MAX_HISTORY = 50;
106
110
 
@@ -136,6 +140,17 @@ function historyReducer(state: HistoryState, action: HistoryAction): HistoryStat
136
140
  }
137
141
 
138
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
+
139
154
  function uid() {
140
155
  return `f-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
141
156
  }
@@ -147,7 +162,7 @@ function makeField(type: string, schemaField?: DataSourceField): PrintDataField
147
162
  label: schemaField?.label ?? FIELD_TYPE_LABELS[type] ?? type,
148
163
  type: type as PrintDataField['type'],
149
164
  dataPath: schemaField?.path ?? '',
150
- fallback: type === 'static_text' ? 'Tekst statyczny' : undefined,
165
+ fallback: type === 'static_text' ? 'Static text' : undefined,
151
166
  format: type === 'date' || type === 'datetime' ? 'DD.MM.YYYY' : undefined,
152
167
  position: {
153
168
  x: 15,
@@ -155,7 +170,7 @@ function makeField(type: string, schemaField?: DataSourceField): PrintDataField
155
170
  width: type === 'line' ? 80 : 60,
156
171
  height: type === 'line' ? 1 : type === 'image' ? 30 : 8,
157
172
  },
158
- style: { fontSize: 10, color: 'var(--nice-text, #000000)', fontFamily: 'sans-serif' },
173
+ style: { fontSize: 10, color: INK, fontFamily: 'sans-serif' },
159
174
  };
160
175
  if (type === 'rect') {
161
176
  base.style.backgroundColor = 'var(--nice-bg-secondary, #f1f5f9)';
@@ -164,7 +179,7 @@ function makeField(type: string, schemaField?: DataSourceField): PrintDataField
164
179
  base.style = {
165
180
  ...base.style,
166
181
  borderWidth: 0.5,
167
- borderColor: 'var(--nice-text, #000000)',
182
+ borderColor: INK,
168
183
  borderStyle: 'solid',
169
184
  };
170
185
  }
@@ -235,7 +250,7 @@ interface FieldElementProps {
235
250
 
236
251
  function resolveFieldPreview(field: PrintDataField, data?: Record<string, unknown>): string {
237
252
  if (field.type === 'static_text') {
238
- return field.fallback ?? 'Tekst';
253
+ return field.fallback ?? 'Text';
239
254
  }
240
255
  if (field.type === 'page_number') {
241
256
  return '1';
@@ -295,7 +310,7 @@ const FieldElement: React.FC<FieldElementProps> = ({
295
310
  fontFamily: st.fontFamily ?? 'sans-serif',
296
311
  fontWeight: st.fontWeight ?? 'normal',
297
312
  fontStyle: st.fontStyle ?? 'normal',
298
- color: st.color ?? 'var(--nice-text, #000)',
313
+ color: st.color ?? INK,
299
314
  textAlign: st.textAlign ?? 'left',
300
315
  lineHeight: st.lineHeight ?? 1.4,
301
316
  letterSpacing: st.letterSpacing ? `${st.letterSpacing}em` : undefined,
@@ -319,17 +334,22 @@ const FieldElement: React.FC<FieldElementProps> = ({
319
334
  <div
320
335
  style={{
321
336
  width: '100%',
322
- 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}`,
323
338
  }}
324
339
  />
325
340
  ) : isRect ? null : isImage ? (
326
- <div className={styles.fieldImagePlaceholder}>๐Ÿ–ผ</div>
341
+ <div className={styles.fieldImagePlaceholder}>
342
+ <NiceIcon name="image" size={24} />
343
+ </div>
327
344
  ) : isBarcode ? (
328
345
  <div className={styles.fieldBarcodePlaceholder}>
329
- {field.type === 'qr' ? 'โฌ› QR' : 'โ–ฎโ–Œโ–ฎ Barcode'}
346
+ <NiceIcon name={field.type === 'qr' ? 'qr-code' : 'barcode'} size={14} />{' '}
347
+ {field.type === 'qr' ? 'QR' : 'Barcode'}
330
348
  </div>
331
349
  ) : field.type === 'signature' ? (
332
- <div className={styles.fieldSignaturePlaceholder}>โœ {field.label}</div>
350
+ <div className={styles.fieldSignaturePlaceholder}>
351
+ <NiceIcon name="pen" size={14} /> {field.label}
352
+ </div>
333
353
  ) : (
334
354
  <span style={{ display: 'block', width: '100%' }}>
335
355
  {resolveFieldPreview(field, sampleData)}
@@ -469,21 +489,21 @@ const ShortcutChip: React.FC<{ keys: string; label: string }> = ({ keys, label }
469
489
 
470
490
  // โ”€โ”€โ”€ Left panel tabs โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
471
491
  type LeftTab = 'fields' | 'layers' | 'watermarks' | 'signatures' | 'settings';
472
- const LEFT_TABS: { id: LeftTab; icon: string; label: string }[] = [
473
- { id: 'fields', icon: '๐Ÿ“', label: 'Pola' },
474
- { id: 'layers', icon: 'โ—ง', label: 'Warstwy' },
475
- { id: 'watermarks', icon: '๐Ÿ’ง', label: 'Znaki' },
476
- { id: 'signatures', icon: 'โœ', label: 'Podpisy' },
477
- { 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' },
478
498
  ];
479
499
 
480
500
  // โ”€โ”€โ”€ Right panel tabs โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
481
501
  type RightTab = 'position' | 'style' | 'data' | 'conditional';
482
- const RIGHT_TABS: { id: RightTab; label: string }[] = [
483
- { id: 'position', label: 'Pozycja' },
484
- { id: 'style', label: 'Styl' },
485
- { id: 'data', label: 'Dane' },
486
- { 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' },
487
507
  ];
488
508
 
489
509
  // โ”€โ”€โ”€ Toolbar โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -536,6 +556,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
536
556
  onSendBackward,
537
557
  onSave,
538
558
  }) => {
559
+ const { t: tr } = useNiceTranslation();
539
560
  const [insertOpen, setInsertOpen] = useState(false);
540
561
  const ZOOM_PRESETS = [0.5, 0.75, 1, 1.25, 1.5, 2];
541
562
 
@@ -559,14 +580,14 @@ const Toolbar: React.FC<ToolbarProps> = ({
559
580
  onClick={() => onPaperChange(tpl.paperSize, 'portrait')}
560
581
  title="Pion"
561
582
  >
562
- โฌœ
583
+ <NiceIcon name="file" size={14} />
563
584
  </button>
564
585
  <button
565
586
  className={`${styles.toolbarBtn} ${tpl.orientation === 'landscape' ? styles.toolbarBtnActive : ''}`}
566
587
  onClick={() => onPaperChange(tpl.paperSize, 'landscape')}
567
588
  title="Poziom"
568
589
  >
569
- โ–ญ
590
+ <NiceIcon name="file" size={14} style={{ transform: 'rotate(90deg)' }} />
570
591
  </button>
571
592
  </div>
572
593
 
@@ -577,17 +598,17 @@ const Toolbar: React.FC<ToolbarProps> = ({
577
598
  className={styles.toolbarBtn}
578
599
  disabled={!canUndo}
579
600
  onClick={onUndo}
580
- title="Cofnij (Ctrl+Z)"
601
+ title={`${tr('printing.editor.undo', 'Undo')} (Ctrl+Z)`}
581
602
  >
582
- โ†ฉ
603
+ <NiceIcon name="undo" size={14} />
583
604
  </button>
584
605
  <button
585
606
  className={styles.toolbarBtn}
586
607
  disabled={!canRedo}
587
608
  onClick={onRedo}
588
- title="Ponรณw (Ctrl+Y)"
609
+ title={`${tr('printing.editor.redo', 'Redo')} (Ctrl+Y)`}
589
610
  >
590
- โ†ช
611
+ <NiceIcon name="redo" size={14} />
591
612
  </button>
592
613
  </div>
593
614
  )}
@@ -599,11 +620,14 @@ const Toolbar: React.FC<ToolbarProps> = ({
599
620
  className={`${styles.toolbarBtn} ${styles.insertBtn}`}
600
621
  onClick={() => setInsertOpen((o) => !o)}
601
622
  >
602
- ๏ผ‹ Wstaw โ–พ
623
+ <NiceIcon name="plus" size={14} /> {tr('printing.editor.insert', 'Insert')}{' '}
624
+ <NiceIcon name="chevron-down" size={12} />
603
625
  </button>
604
626
  {insertOpen && (
605
627
  <div className={styles.insertDropdown} onMouseLeave={() => setInsertOpen(false)}>
606
- <div className={styles.insertCategory}>Pola danych</div>
628
+ <div className={styles.insertCategory}>
629
+ {tr('printing.editor.dataFields', 'Data fields')}
630
+ </div>
607
631
  {['text', 'number', 'currency', 'date', 'image', 'barcode', 'qr'].map((t) => (
608
632
  <button
609
633
  key={t}
@@ -614,7 +638,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
614
638
  }}
615
639
  >
616
640
  <span className={styles.insertIcon}>{FIELD_TYPE_ICONS[t]}</span>
617
- {FIELD_TYPE_LABELS[t]}
641
+ {fieldTypeLabel(tr, t)}
618
642
  </button>
619
643
  ))}
620
644
  <div className={styles.insertCategory}>Elementy statyczne</div>
@@ -636,7 +660,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
636
660
  }}
637
661
  >
638
662
  <span className={styles.insertIcon}>{FIELD_TYPE_ICONS[t]}</span>
639
- {FIELD_TYPE_LABELS[t]}
663
+ {fieldTypeLabel(tr, t)}
640
664
  </button>
641
665
  ))}
642
666
  <div className={styles.insertCategory}>Specjalne</div>
@@ -647,7 +671,10 @@ const Toolbar: React.FC<ToolbarProps> = ({
647
671
  setInsertOpen(false);
648
672
  }}
649
673
  >
650
- <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')}
651
678
  </button>
652
679
  <button
653
680
  className={styles.insertItem}
@@ -656,7 +683,10 @@ const Toolbar: React.FC<ToolbarProps> = ({
656
683
  setInsertOpen(false);
657
684
  }}
658
685
  >
659
- <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')}
660
690
  </button>
661
691
  </div>
662
692
  )}
@@ -666,53 +696,73 @@ const Toolbar: React.FC<ToolbarProps> = ({
666
696
  {/* Selection actions */}
667
697
  {!readOnly && selectedCount > 0 && (
668
698
  <div className={styles.toolbarGroup}>
669
- <button className={styles.toolbarBtn} onClick={onDuplicate} title="Duplikuj (Ctrl+D)">
670
- โง‰
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} />
671
705
  </button>
672
706
  <button
673
707
  className={`${styles.toolbarBtn} ${styles.toolbarBtnDanger}`}
674
708
  onClick={onDelete}
675
- title="Usuล„ (Del)"
709
+ title={`${tr('printing.action.remove', 'Remove')} (Del)`}
676
710
  >
677
- ๐Ÿ—‘
711
+ <NiceIcon name="trash" size={14} />
678
712
  </button>
679
713
  <span className={styles.toolbarSep} />
680
714
  <button
681
715
  className={styles.toolbarBtn}
682
716
  onClick={() => onAlignH('left')}
683
- title="Wyrรณwnaj lewo"
717
+ title={tr('printing.editor.alignLeft', 'Align left')}
684
718
  >
685
- โฌ›โ–กโ–ก
719
+ <NiceIcon name="align-left" size={14} />
686
720
  </button>
687
721
  <button
688
722
  className={styles.toolbarBtn}
689
723
  onClick={() => onAlignH('center')}
690
- title="Wyล›rodkuj"
724
+ title={tr('printing.editor.alignCenter', 'Align centre')}
691
725
  >
692
- โ–กโฌ›โ–ก
726
+ <NiceIcon name="align-center" size={14} />
693
727
  </button>
694
728
  <button
695
729
  className={styles.toolbarBtn}
696
730
  onClick={() => onAlignH('right')}
697
- title="Wyrรณwnaj prawo"
731
+ title={tr('printing.editor.alignRight', 'Align right')}
698
732
  >
699
- โ–กโ–กโฌ›
733
+ <NiceIcon name="align-right" size={14} />
700
734
  </button>
701
- <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
+ >
702
740
  โŠค
703
741
  </button>
704
- <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
+ >
705
747
  โŠ•
706
748
  </button>
707
- <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
+ >
708
754
  โŠฅ
709
755
  </button>
710
756
  <span className={styles.toolbarSep} />
711
757
  <button className={styles.toolbarBtn} onClick={onBringForward} title="Do przodu">
712
- โ–ฒ
758
+ <NiceIcon name="arrow-up" size={14} />
713
759
  </button>
714
- <button className={styles.toolbarBtn} onClick={onSendBackward} title="Do tyล‚u">
715
- โ–ผ
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} />
716
766
  </button>
717
767
  </div>
718
768
  )}
@@ -722,16 +772,16 @@ const Toolbar: React.FC<ToolbarProps> = ({
722
772
  <button
723
773
  className={`${styles.toolbarBtn} ${showGrid ? styles.toolbarBtnActive : ''}`}
724
774
  onClick={onToggleGrid}
725
- title="Siatka"
775
+ title={tr('printing.editor.grid', 'Grid')}
726
776
  >
727
777
  โŠž
728
778
  </button>
729
779
  <button
730
780
  className={`${styles.toolbarBtn} ${showRulers ? styles.toolbarBtnActive : ''}`}
731
781
  onClick={onToggleRulers}
732
- title="Linijki"
782
+ title={tr('printing.editor.rulers', 'Rulers')}
733
783
  >
734
- โŠข
784
+ <NiceIcon name="ruler" size={14} />
735
785
  </button>
736
786
  </div>
737
787
 
@@ -761,7 +811,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
761
811
 
762
812
  {!readOnly && onSave && (
763
813
  <button className={styles.saveBtn} onClick={onSave}>
764
- ๐Ÿ’พ Zapisz
814
+ <NiceIcon name="save" size={14} /> {tr('printing.editor.save', 'Save')}
765
815
  </button>
766
816
  )}
767
817
  </div>
@@ -794,6 +844,7 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
794
844
  onUpdateTpl,
795
845
  onReorderField,
796
846
  }) => {
847
+ const { t: tr } = useNiceTranslation();
797
848
  const [search, setSearch] = useState('');
798
849
  const filteredFields = schema?.fields.filter(
799
850
  (f) =>
@@ -809,10 +860,12 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
809
860
  key={t.id}
810
861
  className={`${styles.leftTab} ${activeTab === t.id ? styles.leftTabActive : ''}`}
811
862
  onClick={() => onTabChange(t.id)}
812
- title={t.label}
863
+ title={tr(t.key, t.label)}
813
864
  >
814
- <span className={styles.leftTabIcon}>{t.icon}</span>
815
- <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>
816
869
  </button>
817
870
  ))}
818
871
  </div>
@@ -821,11 +874,13 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
821
874
  {/* โ”€โ”€ Fields tab โ”€โ”€ */}
822
875
  {activeTab === 'fields' && (
823
876
  <>
824
- <div className={styles.panelHeader}>Pola danych</div>
877
+ <div className={styles.panelHeader}>
878
+ {tr('printing.editor.dataFields', 'Data fields')}
879
+ </div>
825
880
  <div className={styles.searchBox}>
826
881
  <input
827
882
  className={styles.searchInput}
828
- placeholder="Szukaj..."
883
+ placeholder={tr('printing.editor.search', 'Searchโ€ฆ')}
829
884
  value={search}
830
885
  onChange={(e) => setSearch(e.target.value)}
831
886
  />
@@ -844,7 +899,9 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
844
899
  }}
845
900
  onClick={() => onInsertSchemaField(f)}
846
901
  >
847
- <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>
848
905
  <div className={styles.fieldItemInfo}>
849
906
  <div className={styles.fieldItemLabel}>{f.label}</div>
850
907
  <div className={styles.fieldItemPath}>{f.path}</div>
@@ -853,12 +910,17 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
853
910
  </div>
854
911
  ))}
855
912
  {filteredFields?.length === 0 && (
856
- <div className={styles.emptyMsg}>Brak wynikรณw</div>
913
+ <div className={styles.emptyMsg}>
914
+ {tr('printing.editor.noMatches', 'No matches')}
915
+ </div>
857
916
  )}
858
917
  </div>
859
918
  ) : (
860
919
  <div className={styles.emptyMsg}>
861
- 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
+ )}
862
924
  </div>
863
925
  )}
864
926
  </>
@@ -867,9 +929,15 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
867
929
  {/* โ”€โ”€ Layers tab โ”€โ”€ */}
868
930
  {activeTab === 'layers' && (
869
931
  <>
870
- <div className={styles.panelHeader}>Warstwy ({allFields.length})</div>
932
+ <div className={styles.panelHeader}>
933
+ {tr('printing.editor.layers', 'Layers')} ({allFields.length})
934
+ </div>
871
935
  <div className={styles.layerList}>
872
- {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
+ )}
873
941
  {allFields
874
942
  .slice()
875
943
  .reverse()
@@ -879,7 +947,9 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
879
947
  className={`${styles.layerItem} ${selectedIds.has(f.id) ? styles.layerItemSelected : ''}`}
880
948
  onClick={() => onSelectField(f.id)}
881
949
  >
882
- <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>
883
953
  <span className={styles.layerName}>{f.name || f.label}</span>
884
954
  <div className={styles.layerActions}>
885
955
  <button
@@ -888,9 +958,9 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
888
958
  e.stopPropagation();
889
959
  onReorderField(f.id, -1);
890
960
  }}
891
- title="Do przodu"
961
+ title={tr('printing.editor.bringForward', 'Bring forward')}
892
962
  >
893
- โ†‘
963
+ <NiceIcon name="arrow-up" size={12} />
894
964
  </button>
895
965
  <button
896
966
  className={styles.layerBtn}
@@ -898,9 +968,9 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
898
968
  e.stopPropagation();
899
969
  onReorderField(f.id, 1);
900
970
  }}
901
- title="Do tyล‚u"
971
+ title={tr('printing.editor.sendBackward', 'Send backward')}
902
972
  >
903
- โ†“
973
+ <NiceIcon name="arrow-down" size={12} />
904
974
  </button>
905
975
  </div>
906
976
  </div>
@@ -917,7 +987,7 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
917
987
  {(tpl.watermarks ?? []).map((wm) => (
918
988
  <div key={wm.id} className={styles.wmItem}>
919
989
  <div className={styles.wmRow}>
920
- <span className={styles.wmLabel}>Typ</span>
990
+ <span className={styles.wmLabel}>{tr('printing.editor.type', 'Type')}</span>
921
991
  <select
922
992
  className={styles.wmSelect}
923
993
  value={wm.type}
@@ -929,13 +999,15 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
929
999
  })
930
1000
  }
931
1001
  >
932
- <option value="text">Tekst</option>
933
- <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>
934
1004
  </select>
935
1005
  </div>
936
1006
  {wm.type === 'text' && (
937
1007
  <div className={styles.wmRow}>
938
- <span className={styles.wmLabel}>Tekst</span>
1008
+ <span className={styles.wmLabel}>
1009
+ {tr('printing.fieldType.text', 'Text')}
1010
+ </span>
939
1011
  <input
940
1012
  className={styles.wmInput}
941
1013
  value={wm.text ?? ''}
@@ -969,7 +1041,7 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
969
1041
  <span className={styles.wmValue}>{Math.round(wm.opacity * 100)}%</span>
970
1042
  </div>
971
1043
  <div className={styles.wmRow}>
972
- <span className={styles.wmLabel}>Kฤ…t</span>
1044
+ <span className={styles.wmLabel}>{tr('printing.editor.angle', 'Angle')}</span>
973
1045
  <input
974
1046
  type="range"
975
1047
  min={-90}
@@ -988,7 +1060,7 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
988
1060
  <span className={styles.wmValue}>{wm.angle}ยฐ</span>
989
1061
  </div>
990
1062
  <div className={styles.wmRow}>
991
- <span className={styles.wmLabel}>Rozmiar</span>
1063
+ <span className={styles.wmLabel}>{tr('printing.editor.size', 'Size')}</span>
992
1064
  <input
993
1065
  type="number"
994
1066
  min={8}
@@ -1006,11 +1078,11 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
1006
1078
  <span className={styles.wmUnit}>pt</span>
1007
1079
  </div>
1008
1080
  <div className={styles.wmRow}>
1009
- <span className={styles.wmLabel}>Kolor</span>
1081
+ <span className={styles.wmLabel}>{tr('printing.editor.colour', 'Colour')}</span>
1010
1082
  <input
1011
1083
  type="color"
1012
1084
  className={styles.colorPicker}
1013
- value={wm.color ?? 'var(--nice-text-muted, #94a3b8)'}
1085
+ value={wm.color ?? INK_FAINT}
1014
1086
  onChange={(e) =>
1015
1087
  onUpdateTpl({
1016
1088
  watermarks: tpl.watermarks?.map((w) =>
@@ -1026,7 +1098,7 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
1026
1098
  onUpdateTpl({ watermarks: tpl.watermarks?.filter((w) => w.id !== wm.id) })
1027
1099
  }
1028
1100
  >
1029
- Usuล„
1101
+ {tr('printing.action.remove', 'Remove')}
1030
1102
  </button>
1031
1103
  </div>
1032
1104
  ))}
@@ -1039,17 +1111,18 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
1039
1111
  {
1040
1112
  id: uid(),
1041
1113
  type: 'text',
1042
- text: 'KOPIA',
1114
+ text: 'COPY',
1043
1115
  opacity: 0.12,
1044
1116
  angle: -45,
1045
1117
  fontSize: 60,
1046
- color: 'var(--nice-text-muted, #94a3b8)',
1118
+ color: INK_FAINT,
1047
1119
  },
1048
1120
  ],
1049
1121
  })
1050
1122
  }
1051
1123
  >
1052
- + Dodaj znak wodny
1124
+ <NiceIcon name="plus" size={12} />{' '}
1125
+ {tr('printing.editor.addWatermark', 'Add watermark')}
1053
1126
  </button>
1054
1127
  </div>
1055
1128
  </>
@@ -1091,7 +1164,7 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
1091
1164
  />
1092
1165
  </div>
1093
1166
  <div className={styles.wmRow}>
1094
- <span className={styles.wmLabel}>Linia</span>
1167
+ <span className={styles.wmLabel}>{tr('printing.fieldType.line', 'Line')}</span>
1095
1168
  <input
1096
1169
  type="checkbox"
1097
1170
  checked={sig.showLine ?? true}
@@ -1105,7 +1178,9 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
1105
1178
  />
1106
1179
  </div>
1107
1180
  <div className={styles.wmRow}>
1108
- <span className={styles.wmLabel}>Pokaลผ datฤ™</span>
1181
+ <span className={styles.wmLabel}>
1182
+ {tr('printing.editor.showDate', 'Show date')}
1183
+ </span>
1109
1184
  <input
1110
1185
  type="checkbox"
1111
1186
  checked={sig.showDate ?? false}
@@ -1124,7 +1199,7 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
1124
1199
  onUpdateTpl({ signatures: tpl.signatures?.filter((s) => s.id !== sig.id) })
1125
1200
  }
1126
1201
  >
1127
- Usuล„
1202
+ {tr('printing.action.remove', 'Remove')}
1128
1203
  </button>
1129
1204
  </div>
1130
1205
  ))}
@@ -1136,7 +1211,7 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
1136
1211
  ...(tpl.signatures ?? []),
1137
1212
  {
1138
1213
  id: uid(),
1139
- label: 'Podpis',
1214
+ label: tr('printing.editor.signature', 'Signature'),
1140
1215
  position: { x: 10, y: 5, width: 70, height: 22 },
1141
1216
  showLine: true,
1142
1217
  showDate: false,
@@ -1145,7 +1220,8 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
1145
1220
  })
1146
1221
  }
1147
1222
  >
1148
- + Dodaj podpis
1223
+ <NiceIcon name="plus" size={12} />{' '}
1224
+ {tr('printing.editor.addSignature', 'Add signature')}
1149
1225
  </button>
1150
1226
  </div>
1151
1227
  </>
@@ -1154,10 +1230,12 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
1154
1230
  {/* โ”€โ”€ Settings tab โ”€โ”€ */}
1155
1231
  {activeTab === 'settings' && (
1156
1232
  <>
1157
- <div className={styles.panelHeader}>Ustawienia szablonu</div>
1233
+ <div className={styles.panelHeader}>
1234
+ {tr('printing.editor.templateSettings', 'Template settings')}
1235
+ </div>
1158
1236
  <div className={styles.settingsForm}>
1159
1237
  <div className={styles.settingsRow}>
1160
- <label className={styles.settingsLabel}>Nazwa</label>
1238
+ <label className={styles.settingsLabel}>{tr('printing.editor.name', 'Name')}</label>
1161
1239
  <input
1162
1240
  className={styles.settingsInput}
1163
1241
  value={tpl.name}
@@ -1165,7 +1243,9 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
1165
1243
  />
1166
1244
  </div>
1167
1245
  <div className={styles.settingsRow}>
1168
- <label className={styles.settingsLabel}>Opis</label>
1246
+ <label className={styles.settingsLabel}>
1247
+ {tr('printing.editor.description', 'Description')}
1248
+ </label>
1169
1249
  <textarea
1170
1250
  className={styles.settingsTextarea}
1171
1251
  value={tpl.description ?? ''}
@@ -1181,17 +1261,19 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
1181
1261
  onChange={(e) => onUpdateTpl({ category: e.target.value })}
1182
1262
  />
1183
1263
  </div>
1184
- <div className={styles.settingsDivider}>Marginesy (mm)</div>
1264
+ <div className={styles.settingsDivider}>
1265
+ {tr('printing.editor.margins', 'Margins (mm)')}
1266
+ </div>
1185
1267
  {(['top', 'right', 'bottom', 'left'] as const).map((side) => (
1186
1268
  <div key={side} className={styles.settingsRow}>
1187
1269
  <label className={styles.settingsLabel}>
1188
1270
  {side === 'top'
1189
- ? 'Gรณra'
1271
+ ? tr('printing.editor.marginTop', 'Top')
1190
1272
  : side === 'right'
1191
- ? 'Prawo'
1273
+ ? tr('printing.editor.marginRight', 'Right')
1192
1274
  : side === 'bottom'
1193
- ? 'Dรณล‚'
1194
- : 'Lewo'}
1275
+ ? tr('printing.editor.marginBottom', 'Bottom')
1276
+ : tr('printing.editor.marginLeft', 'Left')}
1195
1277
  </label>
1196
1278
  <input
1197
1279
  type="number"
@@ -1233,6 +1315,7 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1233
1315
  paperWidthMm,
1234
1316
  paperHeightMm: _paperHeightMm,
1235
1317
  }) => {
1318
+ const { t: tr } = useNiceTranslation();
1236
1319
  const p = useCallback(
1237
1320
  (patch: Partial<FieldPosition>) =>
1238
1321
  field && onChange({ position: { ...field.position, ...patch } }),
@@ -1247,17 +1330,21 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1247
1330
  if (!field) {
1248
1331
  return (
1249
1332
  <div className={styles.rightPanel}>
1250
- <div className={styles.panelHeader}>Wล‚aล›ciwoล›ci</div>
1333
+ <div className={styles.panelHeader}>{tr('printing.editor.properties', 'Properties')}</div>
1251
1334
  <div className={styles.propsEmpty}>
1252
- <div className={styles.propsEmptyIcon}>โฌš</div>
1253
- <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>
1254
1341
  <div className={styles.shortcutGrid}>
1255
- <ShortcutChip keys="Del" label="Usuล„" />
1256
- <ShortcutChip keys="Ctrl+D" label="Duplikuj" />
1257
- <ShortcutChip keys="โ†‘โ†“โ†โ†’" label="Przesuล„" />
1258
- <ShortcutChip keys="Ctrl+Z" label="Cofnij" />
1259
- <ShortcutChip keys="Ctrl+A" label="Zaznacz wszystko" />
1260
- <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')} />
1261
1348
  </div>
1262
1349
  </div>
1263
1350
  </div>
@@ -1295,7 +1382,7 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1295
1382
  className={`${styles.rightTab} ${activeTab === t.id ? styles.rightTabActive : ''}`}
1296
1383
  onClick={() => onTabChange(t.id)}
1297
1384
  >
1298
- {t.label}
1385
+ {tr(t.key, t.label)}
1299
1386
  </button>
1300
1387
  );
1301
1388
  })}
@@ -1325,7 +1412,9 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1325
1412
  </div>
1326
1413
  ))}
1327
1414
  <div className={styles.posCell}>
1328
- <label className={styles.posLabel}>Obrรณt</label>
1415
+ <label className={styles.posLabel}>
1416
+ {tr('printing.editor.rotation', 'Rotation')}
1417
+ </label>
1329
1418
  <input
1330
1419
  type="number"
1331
1420
  step={5}
@@ -1349,24 +1438,30 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1349
1438
  </div>
1350
1439
 
1351
1440
  <div className={styles.snapSection}>
1352
- <div className={styles.snapTitle}>Wyrรณwnanie do strony</div>
1441
+ <div className={styles.snapTitle}>
1442
+ {tr('printing.editor.snapToPage', 'Snap to page')}
1443
+ </div>
1353
1444
  <div className={styles.snapRow}>
1354
- <button className={styles.snapBtn} onClick={() => p({ x: 0 })} title="Lewa krawฤ™dลบ">
1355
- โซท
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} />
1356
1451
  </button>
1357
1452
  <button
1358
1453
  className={styles.snapBtn}
1359
1454
  onClick={() => p({ x: (paperWidthMm - field.position.width) / 2 })}
1360
- title="ลšrodek poziomy"
1455
+ title={tr('printing.editor.horizontalCentre', 'Horizontal centre')}
1361
1456
  >
1362
1457
  โŠ•
1363
1458
  </button>
1364
1459
  <button
1365
1460
  className={styles.snapBtn}
1366
1461
  onClick={() => p({ x: paperWidthMm - field.position.width })}
1367
- title="Prawa krawฤ™dลบ"
1462
+ title={tr('printing.editor.rightEdge', 'Right edge')}
1368
1463
  >
1369
- โซธ
1464
+ <NiceIcon name="align-right" size={14} />
1370
1465
  </button>
1371
1466
  </div>
1372
1467
  </div>
@@ -1394,7 +1489,7 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1394
1489
  </select>
1395
1490
  </div>
1396
1491
  <div className={styles.styleRow}>
1397
- <label className={styles.styleLabel}>Rozmiar</label>
1492
+ <label className={styles.styleLabel}>{tr('printing.editor.size', 'Size')}</label>
1398
1493
  <select
1399
1494
  className={styles.styleSelectSm}
1400
1495
  value={
@@ -1422,22 +1517,26 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1422
1517
  <span className={styles.styleUnit}>pt</span>
1423
1518
  </div>
1424
1519
  <div className={styles.styleRow}>
1425
- <label className={styles.styleLabel}>Kolor</label>
1520
+ <label className={styles.styleLabel}>
1521
+ {tr('printing.editor.colour', 'Colour')}
1522
+ </label>
1426
1523
  <input
1427
1524
  type="color"
1428
1525
  className={styles.colorPicker}
1429
- value={field.style.color ?? 'var(--nice-text, #000000)'}
1526
+ value={field.style.color ?? INK}
1430
1527
  onChange={(e) => s({ color: e.target.value })}
1431
1528
  />
1432
1529
  <input
1433
1530
  className={styles.colorHex}
1434
- value={field.style.color ?? 'var(--nice-text, #000000)'}
1531
+ value={field.style.color ?? INK}
1435
1532
  onChange={(e) => s({ color: e.target.value })}
1436
1533
  maxLength={7}
1437
1534
  />
1438
1535
  </div>
1439
1536
  <div className={styles.styleRow}>
1440
- <label className={styles.styleLabel}>Styl</label>
1537
+ <label className={styles.styleLabel}>
1538
+ {tr('printing.editor.style', 'Style')}
1539
+ </label>
1441
1540
  <div className={styles.toggleGroup}>
1442
1541
  <button
1443
1542
  className={`${styles.toggleBtn} ${field.style.fontWeight === 'bold' ? styles.toggleBtnActive : ''}`}
@@ -1458,7 +1557,9 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1458
1557
  </div>
1459
1558
  </div>
1460
1559
  <div className={styles.styleRow}>
1461
- <label className={styles.styleLabel}>Wyrรณwn.</label>
1560
+ <label className={styles.styleLabel}>
1561
+ {tr('printing.editor.align', 'Align')}
1562
+ </label>
1462
1563
  <div className={styles.toggleGroup}>
1463
1564
  {(['left', 'center', 'right', 'justify'] as const).map((a) => (
1464
1565
  <button
@@ -1485,7 +1586,9 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1485
1586
  />
1486
1587
  </div>
1487
1588
  <div className={styles.styleRow}>
1488
- <label className={styles.styleLabel}>Odstฤ™py</label>
1589
+ <label className={styles.styleLabel}>
1590
+ {tr('printing.editor.spacing', 'Spacing')}
1591
+ </label>
1489
1592
  <input
1490
1593
  type="number"
1491
1594
  step={0.01}
@@ -1501,9 +1604,11 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1501
1604
  )}
1502
1605
 
1503
1606
  <div className={styles.styleGroup}>
1504
- <div className={styles.styleGroupTitle}>Tล‚o i ramka</div>
1607
+ <div className={styles.styleGroupTitle}>
1608
+ {tr('printing.editor.background', 'Background and border')}
1609
+ </div>
1505
1610
  <div className={styles.styleRow}>
1506
- <label className={styles.styleLabel}>Tล‚o</label>
1611
+ <label className={styles.styleLabel}>{tr('printing.editor.fill', 'Fill')}</label>
1507
1612
  <input
1508
1613
  type="color"
1509
1614
  className={styles.colorPicker}
@@ -1513,9 +1618,9 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1513
1618
  <button
1514
1619
  className={styles.clearBtn}
1515
1620
  onClick={() => s({ backgroundColor: undefined })}
1516
- title="Wyczyล›ฤ‡ tล‚o"
1621
+ title={tr('printing.editor.clearFill', 'Clear fill')}
1517
1622
  >
1518
- โœ•
1623
+ <NiceIcon name="close" size={12} />
1519
1624
  </button>
1520
1625
  </div>
1521
1626
  <div className={styles.styleRow}>
@@ -1532,7 +1637,7 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1532
1637
  <input
1533
1638
  type="color"
1534
1639
  className={styles.colorPicker}
1535
- value={field.style.borderColor ?? 'var(--nice-text, #000000)'}
1640
+ value={field.style.borderColor ?? INK}
1536
1641
  onChange={(e) => s({ borderColor: e.target.value })}
1537
1642
  />
1538
1643
  <select
@@ -1593,9 +1698,11 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1593
1698
  {/* โ”€โ”€ Data โ”€โ”€ */}
1594
1699
  {activeTab === 'data' && hasData && (
1595
1700
  <div className={styles.styleGroup}>
1596
- <div className={styles.styleGroupTitle}>ลนrรณdล‚o danych</div>
1701
+ <div className={styles.styleGroupTitle}>
1702
+ {tr('printing.editor.dataSource', 'Data source')}
1703
+ </div>
1597
1704
  <div className={styles.styleRow}>
1598
- <label className={styles.styleLabel}>ลšcieลผka</label>
1705
+ <label className={styles.styleLabel}>{tr('printing.editor.path', 'Path')}</label>
1599
1706
  <input
1600
1707
  className={styles.styleInputFull}
1601
1708
  value={field.dataPath}
@@ -1604,7 +1711,7 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1604
1711
  />
1605
1712
  </div>
1606
1713
  <div className={styles.styleRow}>
1607
- <label className={styles.styleLabel}>Format</label>
1714
+ <label className={styles.styleLabel}>{tr('printing.editor.format', 'Format')}</label>
1608
1715
  <input
1609
1716
  className={styles.styleInputFull}
1610
1717
  value={field.format ?? ''}
@@ -1613,31 +1720,35 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1613
1720
  />
1614
1721
  </div>
1615
1722
  <div className={styles.styleRow}>
1616
- <label className={styles.styleLabel}>Domyล›lny</label>
1723
+ <label className={styles.styleLabel}>
1724
+ {tr('printing.editor.default', 'Default')}
1725
+ </label>
1617
1726
  <input
1618
1727
  className={styles.styleInputFull}
1619
1728
  value={field.fallback ?? ''}
1620
1729
  onChange={(e) => onChange({ fallback: e.target.value })}
1621
- placeholder="Gdy brak wartoล›ci"
1730
+ placeholder={tr('printing.editor.whenEmpty', 'When there is no value')}
1622
1731
  />
1623
1732
  </div>
1624
1733
  <div className={styles.styleRow}>
1625
- <label className={styles.styleLabel}>Typ</label>
1734
+ <label className={styles.styleLabel}>{tr('printing.editor.type', 'Type')}</label>
1626
1735
  <select
1627
1736
  className={styles.styleSelectFull}
1628
1737
  value={field.type}
1629
1738
  onChange={(e) => onChange({ type: e.target.value as PrintDataField['type'] })}
1630
1739
  >
1631
- {Object.entries(FIELD_TYPE_LABELS).map(([k, v]) => (
1740
+ {Object.keys(FIELD_TYPE_LABELS).map((k) => (
1632
1741
  <option key={k} value={k}>
1633
- {v}
1742
+ {fieldTypeLabel(tr, k)}
1634
1743
  </option>
1635
1744
  ))}
1636
1745
  </select>
1637
1746
  </div>
1638
1747
  {(field.type === 'barcode' || field.type === 'qr') && (
1639
1748
  <div className={styles.styleRow}>
1640
- <label className={styles.styleLabel}>Format kodu</label>
1749
+ <label className={styles.styleLabel}>
1750
+ {tr('printing.editor.codeFormat', 'Code format')}
1751
+ </label>
1641
1752
  <select
1642
1753
  className={styles.styleSelectFull}
1643
1754
  value={field.barcodeType ?? 'code128'}
@@ -1657,12 +1768,19 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1657
1768
  {/* โ”€โ”€ Conditional โ”€โ”€ */}
1658
1769
  {activeTab === 'conditional' && (
1659
1770
  <div className={styles.styleGroup}>
1660
- <div className={styles.styleGroupTitle}>Widocznoล›ฤ‡ warunkowa</div>
1771
+ <div className={styles.styleGroupTitle}>
1772
+ {tr('printing.editor.conditionalVisibility', 'Conditional visibility')}
1773
+ </div>
1661
1774
  <div className={styles.conditionalHint}>
1662
- 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
+ )}
1663
1779
  </div>
1664
1780
  <div className={styles.styleRow}>
1665
- <label className={styles.styleLabel}>Pole</label>
1781
+ <label className={styles.styleLabel}>
1782
+ {tr('printing.editor.conditionalField', 'Field')}
1783
+ </label>
1666
1784
  <input
1667
1785
  className={styles.styleInputFull}
1668
1786
  value={field.conditional?.field ?? ''}
@@ -1695,15 +1813,15 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1695
1813
  })
1696
1814
  }
1697
1815
  >
1698
- <option value="eq">= r๏ฟฝwne</option>
1699
- <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>
1700
1818
  <option value="gt">&gt; wieksze</option>
1701
1819
  <option value="lt">&lt; mniejsze</option>
1702
1820
  <option value="contains">zawiera</option>
1703
1821
  </select>
1704
1822
  </div>
1705
1823
  <div className={styles.styleRow}>
1706
- <label className={styles.styleLabel}>Wartosc</label>
1824
+ <label className={styles.styleLabel}>{tr('printing.editor.value', 'Value')}</label>
1707
1825
  <input
1708
1826
  className={styles.styleInputFull}
1709
1827
  value={String(field.conditional?.value ?? '')}
@@ -1724,7 +1842,7 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1724
1842
  className={styles.clearConditionalBtn}
1725
1843
  onClick={() => onChange({ conditional: undefined })}
1726
1844
  >
1727
- Wyczyล›ฤ‡ warunek
1845
+ {tr('printing.editor.clearCondition', 'Clear condition')}
1728
1846
  </button>
1729
1847
  )}
1730
1848
  </div>
@@ -1738,7 +1856,7 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
1738
1856
  function defaultTemplate(): PrintTemplate {
1739
1857
  return {
1740
1858
  id: `tpl-${Date.now()}`,
1741
- name: 'Nowy szablon',
1859
+ name: 'New template',
1742
1860
  description: '',
1743
1861
  paperSize: 'A4',
1744
1862
  orientation: 'portrait',
@@ -1770,6 +1888,7 @@ export const NiceTemplateEditor: React.FC<NiceTemplateEditorProps> = ({
1770
1888
  present: initialTemplate ?? defaultTemplate(),
1771
1889
  future: [],
1772
1890
  });
1891
+ const { t: tr } = useNiceTranslation();
1773
1892
  const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
1774
1893
  const [activeSection, _setActiveSection] = useState('sec-body');
1775
1894
  const [zoom, setZoom] = useState(1);
@@ -1842,11 +1961,11 @@ export const NiceTemplateEditor: React.FC<NiceTemplateEditorProps> = ({
1842
1961
  {
1843
1962
  id: uid(),
1844
1963
  type: 'text',
1845
- text: 'KOPIA',
1964
+ text: 'COPY',
1846
1965
  opacity: 0.12,
1847
1966
  angle: -45,
1848
1967
  fontSize: 60,
1849
- color: 'var(--nice-text-muted, #94a3b8)',
1968
+ color: INK_FAINT,
1850
1969
  },
1851
1970
  ],
1852
1971
  });
@@ -1859,7 +1978,7 @@ export const NiceTemplateEditor: React.FC<NiceTemplateEditorProps> = ({
1859
1978
  ...(tpl.signatures ?? []),
1860
1979
  {
1861
1980
  id: uid(),
1862
- label: 'Podpis',
1981
+ label: tr('printing.editor.signature', 'Signature'),
1863
1982
  position: { x: 10, y: 5, width: 70, height: 22 },
1864
1983
  showLine: true,
1865
1984
  showDate: false,
@@ -2315,7 +2434,9 @@ export const NiceTemplateEditor: React.FC<NiceTemplateEditorProps> = ({
2315
2434
  {/* Test data bar */}
2316
2435
  {Array.isArray(sampleData) && sampleData.length > 0 && (
2317
2436
  <div className={styles.testDataBar}>
2318
- <span className={styles.testDataLabel}>Dane testowe:</span>
2437
+ <span className={styles.testDataLabel}>
2438
+ {tr('printing.editor.testData', 'Sample data')}:
2439
+ </span>
2319
2440
  <select
2320
2441
  className={styles.testDataSelect}
2321
2442
  value={testDataIdx}
@@ -2323,7 +2444,7 @@ export const NiceTemplateEditor: React.FC<NiceTemplateEditorProps> = ({
2323
2444
  >
2324
2445
  {sampleData.map((_, i) => (
2325
2446
  <option key={i} value={i}>
2326
- Rekord {i + 1}
2447
+ {tr('printing.editor.record', 'Record')} {i + 1}
2327
2448
  </option>
2328
2449
  ))}
2329
2450
  </select>
@@ -2360,12 +2481,16 @@ export const NiceTemplateEditor: React.FC<NiceTemplateEditorProps> = ({
2360
2481
  className={styles.watermark}
2361
2482
  style={{
2362
2483
  fontSize: (wm.fontSize ?? 60) * zoom,
2363
- color: wm.color ?? 'var(--nice-text-muted, #94a3b8)',
2484
+ color: wm.color ?? INK_FAINT,
2364
2485
  opacity: wm.opacity,
2365
2486
  transform: `translate(-50%, -50%) rotate(${wm.angle}deg)`,
2366
2487
  }}
2367
2488
  >
2368
- {wm.type === 'text' ? wm.text : '??'}
2489
+ {wm.type === 'text' ? (
2490
+ wm.text
2491
+ ) : (
2492
+ <NiceIcon name="image" size={(wm.fontSize ?? 60) * zoom} />
2493
+ )}
2369
2494
  </div>
2370
2495
  ))}
2371
2496