mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-18 11:56:01 +02:00
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { screen, waitFor, fireEvent } from '@testing-library/react';
|
||||
import { screen, waitFor, fireEvent, within } from '@testing-library/react';
|
||||
import { render } from '../utils';
|
||||
import { LabelTemplatePickerModal } from '../../components/LabelTemplatePickerModal';
|
||||
import { api } from '../../api/client';
|
||||
@@ -67,6 +67,22 @@ describe('LabelTemplatePickerModal', () => {
|
||||
expect(screen.getByText(/Ivory · Polymaker/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('keeps the panel from becoming a programmatically scrollable clipping container', () => {
|
||||
render(
|
||||
<LabelTemplatePickerModal
|
||||
isOpen={true}
|
||||
onClose={vi.fn()}
|
||||
availableSpools={SPOOLS}
|
||||
initialSelectedIds={[1, 2, 3, 4]}
|
||||
spoolmanMode={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
const panel = screen.getByTestId('label-template-picker-panel');
|
||||
expect(panel).toHaveClass('overflow-clip');
|
||||
expect(panel).not.toHaveClass('overflow-hidden');
|
||||
});
|
||||
|
||||
it('shows the live selected count in the header', () => {
|
||||
render(
|
||||
<LabelTemplatePickerModal
|
||||
@@ -208,6 +224,7 @@ describe('LabelTemplatePickerModal', () => {
|
||||
spool_ids: [1, 3],
|
||||
template: 'box_62x29',
|
||||
monochrome: false,
|
||||
starting_position: 1,
|
||||
});
|
||||
});
|
||||
await waitFor(() => expect(onClose).toHaveBeenCalled());
|
||||
@@ -234,6 +251,7 @@ describe('LabelTemplatePickerModal', () => {
|
||||
spool_ids: [1],
|
||||
template: 'ams_holder_75x55',
|
||||
monochrome: false,
|
||||
starting_position: 1,
|
||||
});
|
||||
});
|
||||
expect(api.printSpoolLabels).not.toHaveBeenCalled();
|
||||
@@ -364,6 +382,7 @@ describe('LabelTemplatePickerModal', () => {
|
||||
spool_ids: [1, 4, 2, 3],
|
||||
template: 'box_62x29',
|
||||
monochrome: false,
|
||||
starting_position: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -389,6 +408,7 @@ describe('LabelTemplatePickerModal', () => {
|
||||
spool_ids: [1, 2, 3, 4],
|
||||
template: 'box_40x30',
|
||||
monochrome: false,
|
||||
starting_position: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -413,7 +433,69 @@ describe('LabelTemplatePickerModal', () => {
|
||||
spool_ids: [1],
|
||||
template: 'box_40x30',
|
||||
monochrome: true,
|
||||
starting_position: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('sends the selected starting position for an Avery sheet', async () => {
|
||||
vi.mocked(api.printSpoolLabels).mockResolvedValue(PDF_BLOB);
|
||||
render(
|
||||
<LabelTemplatePickerModal
|
||||
isOpen={true}
|
||||
onClose={vi.fn()}
|
||||
availableSpools={SPOOLS}
|
||||
initialSelectedIds={[1]}
|
||||
spoolmanMode={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.change(screen.getByTestId('label-starting-position'), { target: { value: '8' } });
|
||||
expect(screen.getByTestId('label-starting-position-status')).toHaveTextContent(/Positions 1 through 7/i);
|
||||
fireEvent.click(screen.getByTestId('print-labels-avery_5160'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(api.printSpoolLabels).toHaveBeenCalledWith({
|
||||
spool_ids: [1],
|
||||
template: 'avery_5160',
|
||||
monochrome: false,
|
||||
starting_position: 8,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('renders a single skipped position without treating the value as a pluralization key', () => {
|
||||
render(
|
||||
<LabelTemplatePickerModal
|
||||
isOpen={true}
|
||||
onClose={vi.fn()}
|
||||
availableSpools={SPOOLS}
|
||||
initialSelectedIds={[1]}
|
||||
spoolmanMode={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.change(screen.getByTestId('label-starting-position'), { target: { value: '2' } });
|
||||
expect(screen.getByTestId('label-starting-position-status')).toHaveTextContent(
|
||||
'Positions 1 through 1 will be left blank on the first sheet.',
|
||||
);
|
||||
});
|
||||
|
||||
it('explains each Avery template capacity when disabling it', () => {
|
||||
render(
|
||||
<LabelTemplatePickerModal
|
||||
isOpen={true}
|
||||
onClose={vi.fn()}
|
||||
availableSpools={SPOOLS}
|
||||
initialSelectedIds={[1]}
|
||||
spoolmanMode={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.change(screen.getByTestId('label-starting-position'), { target: { value: '25' } });
|
||||
const l7160Button = screen.getByTestId('print-labels-avery_l7160');
|
||||
expect(l7160Button).toBeDisabled();
|
||||
expect(within(l7160Button).getByText(/between 1 and 21/)).toBeInTheDocument();
|
||||
expect(screen.getByTestId('print-labels-avery_5160')).toBeEnabled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3465,6 +3465,13 @@ export type SpoolLabelTemplate =
|
||||
| 'avery_5160'
|
||||
| 'avery_l7160';
|
||||
|
||||
export interface PrintSpoolLabelsRequest {
|
||||
spool_ids: number[];
|
||||
template: SpoolLabelTemplate;
|
||||
monochrome: boolean;
|
||||
starting_position: number;
|
||||
}
|
||||
|
||||
export interface InventorySpool {
|
||||
id: number;
|
||||
material: string;
|
||||
@@ -6360,7 +6367,7 @@ export const api = {
|
||||
// ── Spool label printing (#809) ──────────────────────────────────────────
|
||||
// Both endpoints return application/pdf. Frontend opens the resulting Blob
|
||||
// in a new tab so the user can print or save from the browser's PDF viewer.
|
||||
printSpoolLabels: async (data: { spool_ids: number[]; template: SpoolLabelTemplate; monochrome?: boolean }): Promise<Blob> => {
|
||||
printSpoolLabels: async (data: PrintSpoolLabelsRequest): Promise<Blob> => {
|
||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
||||
if (authToken) headers['Authorization'] = `Bearer ${authToken}`;
|
||||
const response = await fetch(`${API_BASE}/inventory/labels`, {
|
||||
@@ -6374,7 +6381,7 @@ export const api = {
|
||||
}
|
||||
return response.blob();
|
||||
},
|
||||
printSpoolmanSpoolLabels: async (data: { spool_ids: number[]; template: SpoolLabelTemplate; monochrome?: boolean }): Promise<Blob> => {
|
||||
printSpoolmanSpoolLabels: async (data: PrintSpoolLabelsRequest): Promise<Blob> => {
|
||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
||||
if (authToken) headers['Authorization'] = `Bearer ${authToken}`;
|
||||
const response = await fetch(`${API_BASE}/spoolman/labels`, {
|
||||
|
||||
@@ -71,6 +71,13 @@ const TEMPLATE_OPTIONS: TemplateOption[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const SHEET_CAPACITIES: Partial<Record<SpoolLabelTemplate, number>> = {
|
||||
avery_l7160: 21,
|
||||
avery_5160: 30,
|
||||
};
|
||||
|
||||
const MAX_SHEET_CAPACITY = Math.max(...Object.values(SHEET_CAPACITIES));
|
||||
|
||||
function openBlobInNewTab(blob: Blob): void {
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
// Do NOT pass `noopener,noreferrer`: per the WindowFeatures spec, `noopener`
|
||||
@@ -175,6 +182,7 @@ export function LabelTemplatePickerModal({
|
||||
const [materialFilter, setMaterialFilter] = useState<string>('');
|
||||
const [sortMode, setSortMode] = useState<SortMode>('id');
|
||||
const [monochrome, setMonochrome] = useState(false);
|
||||
const [startingPositionInput, setStartingPositionInput] = useState('1');
|
||||
|
||||
// Sync from caller and reset transient state on open. Intentionally not
|
||||
// reactive to props while open — once the user starts editing we don't want
|
||||
@@ -187,6 +195,7 @@ export function LabelTemplatePickerModal({
|
||||
setMaterialFilter('');
|
||||
setSortMode('id');
|
||||
setMonochrome(false);
|
||||
setStartingPositionInput('1');
|
||||
setPending(null);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -236,6 +245,11 @@ export function LabelTemplatePickerModal({
|
||||
|
||||
const selectedCount = selectedIds.size;
|
||||
const noSelection = selectedCount === 0;
|
||||
const startingPosition = Number(startingPositionInput);
|
||||
const startingPositionIsValid =
|
||||
Number.isInteger(startingPosition) &&
|
||||
startingPosition >= 1 &&
|
||||
startingPosition <= MAX_SHEET_CAPACITY;
|
||||
|
||||
function toggleOne(id: number) {
|
||||
setSelectedIds((prev) => {
|
||||
@@ -268,6 +282,21 @@ export function LabelTemplatePickerModal({
|
||||
|
||||
async function handlePick(template: SpoolLabelTemplate) {
|
||||
if (noSelection || pending) return;
|
||||
const sheetCapacity = SHEET_CAPACITIES[template];
|
||||
if (
|
||||
sheetCapacity !== undefined &&
|
||||
(!startingPositionIsValid || startingPosition > sheetCapacity)
|
||||
) {
|
||||
showToast(
|
||||
t(
|
||||
'inventory.labels.startingPositionRangeError',
|
||||
'Starting position must be between 1 and {{capacity}} for this sheet.',
|
||||
{ capacity: sheetCapacity },
|
||||
),
|
||||
'error',
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Order matters: the backend (labels.py) prints labels in the same order
|
||||
// we send IDs. Use the sorted list so a "by colour" sort flows through to
|
||||
// the PDF instead of being clobbered by an ascending-ID re-sort.
|
||||
@@ -275,8 +304,18 @@ export function LabelTemplatePickerModal({
|
||||
setPending(template);
|
||||
try {
|
||||
const blob = spoolmanMode
|
||||
? await api.printSpoolmanSpoolLabels({ spool_ids: ids, template, monochrome })
|
||||
: await api.printSpoolLabels({ spool_ids: ids, template, monochrome });
|
||||
? await api.printSpoolmanSpoolLabels({
|
||||
spool_ids: ids,
|
||||
template,
|
||||
monochrome,
|
||||
starting_position: sheetCapacity === undefined ? 1 : startingPosition,
|
||||
})
|
||||
: await api.printSpoolLabels({
|
||||
spool_ids: ids,
|
||||
template,
|
||||
monochrome,
|
||||
starting_position: sheetCapacity === undefined ? 1 : startingPosition,
|
||||
});
|
||||
openBlobInNewTab(blob);
|
||||
onClose();
|
||||
} catch (err) {
|
||||
@@ -297,7 +336,10 @@ export function LabelTemplatePickerModal({
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
<div className="relative w-full max-w-3xl bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-xl shadow-2xl max-h-[90vh] overflow-hidden flex flex-col my-auto">
|
||||
<div
|
||||
data-testid="label-template-picker-panel"
|
||||
className="relative w-full max-w-3xl bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-xl shadow-2xl max-h-[90vh] overflow-clip flex flex-col my-auto"
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-4 border-b border-bambu-dark-tertiary">
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -467,7 +509,7 @@ export function LabelTemplatePickerModal({
|
||||
</div>
|
||||
|
||||
{/* Print options */}
|
||||
<div className="px-4 pt-2 pb-1 border-t border-bambu-dark-tertiary">
|
||||
<div className="px-4 pt-2 pb-1 border-t border-bambu-dark-tertiary space-y-2">
|
||||
<label className="inline-flex items-center gap-2 cursor-pointer select-none">
|
||||
{monochrome ? (
|
||||
<CheckSquare className="w-4 h-4 text-bambu-green shrink-0" />
|
||||
@@ -487,6 +529,52 @@ export function LabelTemplatePickerModal({
|
||||
{t('inventory.labels.monochromeHint', 'Drops the colour swatch and widens the text')}
|
||||
</span>
|
||||
</label>
|
||||
<div className="flex items-start gap-3">
|
||||
<label
|
||||
htmlFor="label-starting-position"
|
||||
className="text-sm text-white whitespace-nowrap pt-1.5"
|
||||
>
|
||||
{t('inventory.labels.startingPosition', 'Starting label position')}
|
||||
</label>
|
||||
<input
|
||||
id="label-starting-position"
|
||||
data-testid="label-starting-position"
|
||||
type="number"
|
||||
min={1}
|
||||
max={MAX_SHEET_CAPACITY}
|
||||
step={1}
|
||||
value={startingPositionInput}
|
||||
onChange={(event) => setStartingPositionInput(event.target.value)}
|
||||
aria-describedby="label-starting-position-help"
|
||||
className="w-20 px-2 py-1 bg-bambu-dark border border-bambu-dark-tertiary rounded text-white text-sm focus:outline-none focus:border-bambu-green"
|
||||
/>
|
||||
<div id="label-starting-position-help" className="text-xs text-bambu-gray pt-1.5">
|
||||
<div>
|
||||
{t(
|
||||
'inventory.labels.startingPositionRange',
|
||||
'Sheet templates only: L7160 supports 1–21; 5160 supports 1–30.',
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
data-testid="label-starting-position-status"
|
||||
className={startingPositionIsValid ? '' : 'text-red-400'}
|
||||
>
|
||||
{!startingPositionIsValid
|
||||
? t(
|
||||
'inventory.labels.startingPositionInvalid',
|
||||
'Enter a whole number from 1 to {{capacity}}.',
|
||||
{ capacity: MAX_SHEET_CAPACITY },
|
||||
)
|
||||
: startingPosition === 1
|
||||
? t('inventory.labels.startingPositionFirst', 'Printing starts at position 1.')
|
||||
: t(
|
||||
'inventory.labels.startingPositionSkipped',
|
||||
'Positions 1 through {{lastPosition}} will be left blank on the first sheet.',
|
||||
{ lastPosition: startingPosition - 1 },
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Templates — 2x2 grid on >= sm so all 4 plus the Cancel footer fit
|
||||
@@ -495,12 +583,23 @@ export function LabelTemplatePickerModal({
|
||||
<div className="px-3 pt-1 pb-2 grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||
{TEMPLATE_OPTIONS.map((opt) => {
|
||||
const isPending = pending === opt.value;
|
||||
const sheetCapacity = SHEET_CAPACITIES[opt.value];
|
||||
const startingPositionExceedsSheet =
|
||||
sheetCapacity !== undefined &&
|
||||
(!startingPositionIsValid || startingPosition > sheetCapacity);
|
||||
const label = t(`inventory.labels.templates.${opt.i18nKey}.label`, opt.fallbackLabel);
|
||||
const hint = t(`inventory.labels.templates.${opt.i18nKey}.hint`, opt.fallbackHint);
|
||||
const hint = startingPositionExceedsSheet
|
||||
? t(
|
||||
'inventory.labels.startingPositionRangeError',
|
||||
'Starting position must be between 1 and {{capacity}} for this sheet.',
|
||||
{ capacity: sheetCapacity },
|
||||
)
|
||||
: t(`inventory.labels.templates.${opt.i18nKey}.hint`, opt.fallbackHint);
|
||||
return (
|
||||
<button
|
||||
key={opt.value}
|
||||
disabled={noSelection || pending !== null}
|
||||
data-testid={`print-labels-${opt.value}`}
|
||||
disabled={noSelection || pending !== null || startingPositionExceedsSheet}
|
||||
onClick={() => handlePick(opt.value)}
|
||||
title={`${label} — ${hint}`}
|
||||
className="w-full text-left p-2.5 rounded-lg border border-bambu-dark-tertiary bg-bambu-dark hover:border-bambu-green hover:bg-bambu-green/10 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:border-bambu-dark-tertiary disabled:hover:bg-bambu-dark transition flex items-center gap-3"
|
||||
|
||||
@@ -4589,6 +4589,12 @@ export default {
|
||||
pickSpools: 'Wählen Sie, für welche Spulen Etiketten gedruckt werden sollen:',
|
||||
monochrome: 'Monochrom (Schwarz-Weiß-Drucker)',
|
||||
monochromeHint: 'Entfernt das Farbfeld und verbreitert den Text',
|
||||
startingPosition: 'Startposition des Etiketts',
|
||||
startingPositionRange: 'Nur Bogenvorlagen: L7160 unterstützt 1–21; 5160 unterstützt 1–30.',
|
||||
startingPositionInvalid: 'Gib eine ganze Zahl zwischen 1 und {{capacity}} ein.',
|
||||
startingPositionFirst: 'Der Druck beginnt bei Position 1.',
|
||||
startingPositionSkipped: 'Die Positionen 1 bis {{lastPosition}} bleiben auf dem ersten Bogen leer.',
|
||||
startingPositionRangeError: 'Die Startposition muss für diesen Bogen zwischen 1 und {{capacity}} liegen.',
|
||||
searchPlaceholder: 'Name, Marke oder #ID suchen',
|
||||
filterByMaterial: 'Material:',
|
||||
allMaterials: 'Alle',
|
||||
|
||||
@@ -4625,6 +4625,12 @@ export default {
|
||||
pickSpools: 'Pick which spools to print labels for:',
|
||||
monochrome: 'Monochrome (black & white printer)',
|
||||
monochromeHint: 'Drops the colour swatch and widens the text',
|
||||
startingPosition: 'Starting label position',
|
||||
startingPositionRange: 'Sheet templates only: L7160 supports 1–21; 5160 supports 1–30.',
|
||||
startingPositionInvalid: 'Enter a whole number from 1 to {{capacity}}.',
|
||||
startingPositionFirst: 'Printing starts at position 1.',
|
||||
startingPositionSkipped: 'Positions 1 through {{lastPosition}} will be left blank on the first sheet.',
|
||||
startingPositionRangeError: 'Starting position must be between 1 and {{capacity}} for this sheet.',
|
||||
searchPlaceholder: 'Search name, brand, or #ID',
|
||||
filterByMaterial: 'Material:',
|
||||
allMaterials: 'All',
|
||||
|
||||
@@ -4591,6 +4591,12 @@ export default {
|
||||
pickSpools: 'Elija para qué bobinas imprimir etiquetas:',
|
||||
monochrome: 'Monocromo (impresora en blanco y negro)',
|
||||
monochromeHint: 'Elimina la muestra de color y amplía el texto',
|
||||
startingPosition: 'Posición inicial de la etiqueta',
|
||||
startingPositionRange: 'Solo plantillas de hoja: L7160 admite 1–21; 5160 admite 1–30.',
|
||||
startingPositionInvalid: 'Introduce un número entero del 1 al {{capacity}}.',
|
||||
startingPositionFirst: 'La impresión comienza en la posición 1.',
|
||||
startingPositionSkipped: 'Las posiciones 1 a {{lastPosition}} quedarán vacías en la primera hoja.',
|
||||
startingPositionRangeError: 'La posición inicial debe estar entre 1 y {{capacity}} para esta hoja.',
|
||||
searchPlaceholder: 'Buscar nombre, marca o n.º de ID',
|
||||
filterByMaterial: 'Material:',
|
||||
allMaterials: 'Todos',
|
||||
|
||||
@@ -4578,6 +4578,12 @@ export default {
|
||||
pickSpools: 'Choisissez les bobines pour lesquelles imprimer des étiquettes :',
|
||||
monochrome: 'Monochrome (imprimante noir et blanc)',
|
||||
monochromeHint: 'Supprime la pastille de couleur et élargit le texte',
|
||||
startingPosition: 'Position de départ de l’étiquette',
|
||||
startingPositionRange: 'Modèles en feuille uniquement : L7160 accepte 1–21 ; 5160 accepte 1–30.',
|
||||
startingPositionInvalid: 'Saisissez un nombre entier de 1 à {{capacity}}.',
|
||||
startingPositionFirst: 'L’impression commence à la position 1.',
|
||||
startingPositionSkipped: 'Les positions 1 à {{lastPosition}} resteront vides sur la première feuille.',
|
||||
startingPositionRangeError: 'La position de départ doit être comprise entre 1 et {{capacity}} pour cette feuille.',
|
||||
searchPlaceholder: 'Rechercher par nom, marque ou #ID',
|
||||
filterByMaterial: 'Matériau :',
|
||||
allMaterials: 'Tout',
|
||||
|
||||
@@ -4577,6 +4577,12 @@ export default {
|
||||
pickSpools: 'Scegli per quali bobine stampare le etichette:',
|
||||
monochrome: 'Monocromatico (stampante in bianco e nero)',
|
||||
monochromeHint: 'Rimuove il campione di colore e allarga il testo',
|
||||
startingPosition: 'Posizione iniziale dell’etichetta',
|
||||
startingPositionRange: 'Solo modelli a foglio: L7160 supporta 1–21; 5160 supporta 1–30.',
|
||||
startingPositionInvalid: 'Inserisci un numero intero da 1 a {{capacity}}.',
|
||||
startingPositionFirst: 'La stampa inizia dalla posizione 1.',
|
||||
startingPositionSkipped: 'Le posizioni da 1 a {{lastPosition}} resteranno vuote sul primo foglio.',
|
||||
startingPositionRangeError: 'La posizione iniziale deve essere compresa tra 1 e {{capacity}} per questo foglio.',
|
||||
searchPlaceholder: 'Cerca per nome, marca o #ID',
|
||||
filterByMaterial: 'Materiale:',
|
||||
allMaterials: 'Tutto',
|
||||
|
||||
@@ -4589,6 +4589,12 @@ export default {
|
||||
pickSpools: 'ラベルを印刷するスプールを選択:',
|
||||
monochrome: 'モノクロ(白黒プリンター)',
|
||||
monochromeHint: 'カラースウォッチを削除してテキストを広げます',
|
||||
startingPosition: 'ラベルの開始位置',
|
||||
startingPositionRange: 'シートテンプレートのみ:L7160 は1~21、5160 は1~30に対応します。',
|
||||
startingPositionInvalid: '1~{{capacity}}の整数を入力してください。',
|
||||
startingPositionFirst: '位置1から印刷を開始します。',
|
||||
startingPositionSkipped: '最初のシートでは位置1~{{lastPosition}}を空白にします。',
|
||||
startingPositionRangeError: 'このシートの開始位置は1~{{capacity}}で指定してください。',
|
||||
searchPlaceholder: '名前、ブランド、#IDで検索',
|
||||
filterByMaterial: '素材:',
|
||||
allMaterials: 'すべて',
|
||||
|
||||
@@ -4376,6 +4376,12 @@ export default {
|
||||
pickSpools: '라벨을 인쇄할 스풀 선택:',
|
||||
monochrome: '흑백 (흑백 프린터)',
|
||||
monochromeHint: '색상 견본을 제거하고 텍스트를 넓힙니다',
|
||||
startingPosition: '라벨 시작 위치',
|
||||
startingPositionRange: '시트 템플릿만 해당: L7160은 1–21, 5160은 1–30을 지원합니다.',
|
||||
startingPositionInvalid: '1에서 {{capacity}} 사이의 정수를 입력하세요.',
|
||||
startingPositionFirst: '위치 1부터 인쇄를 시작합니다.',
|
||||
startingPositionSkipped: '첫 번째 시트의 위치 1부터 {{lastPosition}}까지는 비워 둡니다.',
|
||||
startingPositionRangeError: '이 시트의 시작 위치는 1에서 {{capacity}} 사이여야 합니다.',
|
||||
searchPlaceholder: '이름, 브랜드 또는 #ID 검색',
|
||||
filterByMaterial: '재료:',
|
||||
allMaterials: '전체',
|
||||
|
||||
@@ -4577,6 +4577,12 @@ export default {
|
||||
pickSpools: 'Escolha para quais bobinas imprimir etiquetas:',
|
||||
monochrome: 'Monocromático (impressora preto e branco)',
|
||||
monochromeHint: 'Remove a amostra de cor e amplia o texto',
|
||||
startingPosition: 'Posição inicial da etiqueta',
|
||||
startingPositionRange: 'Somente modelos em folha: L7160 aceita 1–21; 5160 aceita 1–30.',
|
||||
startingPositionInvalid: 'Digite um número inteiro de 1 a {{capacity}}.',
|
||||
startingPositionFirst: 'A impressão começa na posição 1.',
|
||||
startingPositionSkipped: 'As posições de 1 a {{lastPosition}} ficarão vazias na primeira folha.',
|
||||
startingPositionRangeError: 'A posição inicial deve estar entre 1 e {{capacity}} para esta folha.',
|
||||
searchPlaceholder: 'Buscar por nome, marca ou #ID',
|
||||
filterByMaterial: 'Material:',
|
||||
allMaterials: 'Tudo',
|
||||
|
||||
@@ -4366,6 +4366,12 @@ export default {
|
||||
pickSpools: "Выберите катушки для печати этикеток:",
|
||||
monochrome: "Монохромный режим (чёрно-белый принтер)",
|
||||
monochromeHint: "Убирает образец цвета и расширяет область текста",
|
||||
startingPosition: "Начальная позиция этикетки",
|
||||
startingPositionRange: "Только для листовых шаблонов: L7160 — 1–21; 5160 — 1–30.",
|
||||
startingPositionInvalid: "Введите целое число от 1 до {{capacity}}.",
|
||||
startingPositionFirst: "Печать начинается с позиции 1.",
|
||||
startingPositionSkipped: "Позиции с 1 по {{lastPosition}} останутся пустыми на первом листе.",
|
||||
startingPositionRangeError: "Начальная позиция для этого листа должна быть от 1 до {{capacity}}.",
|
||||
searchPlaceholder: "Поиск по названию, бренду или #ID",
|
||||
filterByMaterial: "Материал:",
|
||||
allMaterials: "Все",
|
||||
|
||||
@@ -4578,6 +4578,12 @@ export default {
|
||||
pickSpools: 'Hangi makaralar için etiket yazdırılacağını seçin:',
|
||||
monochrome: 'Tek renk (siyah beyaz yazıcı)',
|
||||
monochromeHint: 'Renk örneğini kaldırır ve metni genişletir',
|
||||
startingPosition: 'Başlangıç etiket konumu',
|
||||
startingPositionRange: 'Yalnızca sayfa şablonları: L7160 için 1–21; 5160 için 1–30.',
|
||||
startingPositionInvalid: '1 ile {{capacity}} arasında bir tam sayı girin.',
|
||||
startingPositionFirst: 'Yazdırma 1. konumdan başlar.',
|
||||
startingPositionSkipped: 'İlk sayfada 1 ile {{lastPosition}} arasındaki konumlar boş bırakılır.',
|
||||
startingPositionRangeError: 'Bu sayfanın başlangıç konumu 1 ile {{capacity}} arasında olmalıdır.',
|
||||
searchPlaceholder: 'Ad, marka veya #ID ara',
|
||||
filterByMaterial: 'Malzeme:',
|
||||
allMaterials: 'Tümü',
|
||||
|
||||
@@ -4622,6 +4622,12 @@ export default {
|
||||
pickSpools: "Виберіть котушки для друку етикеток:",
|
||||
monochrome: "Монохромний (чорно-білий принтер)",
|
||||
monochromeHint: "Прибирає зразок кольору та розширює область тексту",
|
||||
startingPosition: "Початкова позиція етикетки",
|
||||
startingPositionRange: "Лише для аркушів: L7160 підтримує 1–21; 5160 підтримує 1–30.",
|
||||
startingPositionInvalid: "Введіть ціле число від 1 до {{capacity}}.",
|
||||
startingPositionFirst: "Друк починається з позиції 1.",
|
||||
startingPositionSkipped: "Позиції від 1 до {{lastPosition}} залишаться порожніми на першому аркуші.",
|
||||
startingPositionRangeError: "Початкова позиція для цього аркуша має бути від 1 до {{capacity}}.",
|
||||
searchPlaceholder: "Шукайте назву, бренд або #ID",
|
||||
filterByMaterial: "матеріал:",
|
||||
allMaterials: "все",
|
||||
|
||||
@@ -4577,6 +4577,12 @@ export default {
|
||||
pickSpools: '选择要打印标签的线材:',
|
||||
monochrome: '单色(黑白打印机)',
|
||||
monochromeHint: '移除颜色色块并加宽文本',
|
||||
startingPosition: '标签起始位置',
|
||||
startingPositionRange: '仅适用于标签纸模板:L7160 支持 1–21;5160 支持 1–30。',
|
||||
startingPositionInvalid: '请输入 1 到 {{capacity}} 之间的整数。',
|
||||
startingPositionFirst: '从位置 1 开始打印。',
|
||||
startingPositionSkipped: '第一张标签纸的位置 1 到 {{lastPosition}} 将留空。',
|
||||
startingPositionRangeError: '此标签纸的起始位置必须在 1 到 {{capacity}} 之间。',
|
||||
searchPlaceholder: '按名称、品牌或 #ID 搜索',
|
||||
filterByMaterial: '材料:',
|
||||
allMaterials: '全部',
|
||||
|
||||
@@ -4577,6 +4577,12 @@ export default {
|
||||
pickSpools: '選擇要列印標籤的線材:',
|
||||
monochrome: '單色(黑白印表機)',
|
||||
monochromeHint: '移除顏色色塊並加寬文字',
|
||||
startingPosition: '標籤起始位置',
|
||||
startingPositionRange: '僅適用於標籤紙範本:L7160 支援 1–21;5160 支援 1–30。',
|
||||
startingPositionInvalid: '請輸入 1 到 {{capacity}} 之間的整數。',
|
||||
startingPositionFirst: '從位置 1 開始列印。',
|
||||
startingPositionSkipped: '第一張標籤紙的位置 1 到 {{lastPosition}} 將留空。',
|
||||
startingPositionRangeError: '此標籤紙的起始位置必須在 1 到 {{capacity}} 之間。',
|
||||
searchPlaceholder: '按名稱、品牌或 #ID 搜尋',
|
||||
filterByMaterial: '材料:',
|
||||
allMaterials: '全部',
|
||||
|
||||
Reference in New Issue
Block a user