fix(bulk-upload): i18n hardcoded strings in BulkDropZone and FileSwitcherStrip

- Add bulk_drop_desc, bulk_select_files, bulk_drop_zone_label, bulk_remove_file
  keys to de/en/es message files
- BulkDropZone: use m.bulk_drop_zone_label(), m.bulk_drop_desc(),
  m.bulk_select_files() — removes all hardcoded German
- FileSwitcherStrip: use m.bulk_remove_file() on × button; move aria-live
  from <ul> to a dedicated visually-hidden region above the strip (screen
  readers now announce changes without coupling the live region to the list)
- Spec: import FileEntry from component instead of re-declaring; use
  data-remove-id selector instead of hardcoded German aria-label

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-24 20:38:39 +02:00
parent f90d4b282e
commit 43122c20cb
7 changed files with 24 additions and 22 deletions

View File

@@ -868,5 +868,9 @@
"bulk_file_error_chip_label": "Fehler beim Hochladen",
"bulk_upload_progress": "{done} von {total} hochgeladen",
"bulk_partial_success": "{created} erstellt, {failed} fehlgeschlagen",
"bulk_all_failed": "Alle Uploads fehlgeschlagen"
"bulk_all_failed": "Alle Uploads fehlgeschlagen",
"bulk_drop_desc": "Für jede Datei wird ein eigenes Dokument erstellt. Der Titel wird aus dem Dateinamen vorausgefüllt — alle anderen Felder gelten für alle gemeinsam.",
"bulk_select_files": "Dateien auswählen",
"bulk_drop_zone_label": "Dateien ablegen",
"bulk_remove_file": "Entfernen"
}

View File

@@ -868,5 +868,9 @@
"bulk_file_error_chip_label": "Upload failed",
"bulk_upload_progress": "{done} of {total} uploaded",
"bulk_partial_success": "{created} created, {failed} failed",
"bulk_all_failed": "All uploads failed"
"bulk_all_failed": "All uploads failed",
"bulk_drop_desc": "A separate document is created for each file. The title is pre-filled from the filename — all other fields apply to all documents.",
"bulk_select_files": "Select files",
"bulk_drop_zone_label": "Drop files here",
"bulk_remove_file": "Remove"
}

View File

@@ -868,5 +868,9 @@
"bulk_file_error_chip_label": "Error al subir",
"bulk_upload_progress": "{done} de {total} subidos",
"bulk_partial_success": "{created} creados, {failed} fallidos",
"bulk_all_failed": "Todos los uploads fallaron"
"bulk_all_failed": "Todos los uploads fallaron",
"bulk_drop_desc": "Se crea un documento separado por archivo. El título se rellena desde el nombre del archivo — el resto de campos se aplican a todos.",
"bulk_select_files": "Seleccionar archivos",
"bulk_drop_zone_label": "Soltar archivos aquí",
"bulk_remove_file": "Eliminar"
}

View File

@@ -52,9 +52,9 @@ describe('BulkDocumentEditLayout', () => {
makeFile('file2.pdf')
]);
// Remove the chip for file1 via its "Entfernen" remove button (second × button)
// Remove the chip for file1 via its remove button (identified by data-remove-id)
const removeButtons = container.querySelectorAll<HTMLButtonElement>(
'[data-testid="file-switcher-strip"] button[aria-label="Entfernen"]'
'[data-testid="file-switcher-strip"] button[data-remove-id]'
);
expect(removeButtons.length).toBe(3);
removeButtons[1].click(); // remove file1

View File

@@ -12,7 +12,7 @@ let isDragging = $state(false);
<div
role="region"
aria-label="Dateien ablegen"
aria-label={m.bulk_drop_zone_label()}
data-testid="bulk-drop-zone"
class="flex flex-1 flex-col items-center justify-center p-6"
ondragover={(e) => {
@@ -54,17 +54,13 @@ let isDragging = $state(false);
<p class="font-serif text-base font-bold text-ink">{m.bulk_drop_hint()}</p>
<!-- Sub description -->
<p class="text-sm leading-relaxed text-ink-2">
Für jede Datei wird ein eigenes Dokument erstellt.<br />
<strong class="text-ink">Der Titel</strong> wird aus dem Dateinamen vorausgefüllt —
<strong class="text-ink">alle anderen Felder</strong> gelten für alle gemeinsam.
</p>
<p class="text-sm leading-relaxed text-ink-2">{m.bulk_drop_desc()}</p>
<!-- CTA button -->
<label
class="flex min-h-[44px] cursor-pointer items-center rounded-sm bg-primary px-6 py-2 text-xs font-bold tracking-widest text-primary-fg uppercase transition-opacity hover:opacity-90"
>
Dateien auswählen
{m.bulk_select_files()}
<input
type="file"
multiple

View File

@@ -58,6 +58,7 @@ $effect(() => {
});
</script>
<div aria-live="polite" aria-atomic="true" class="sr-only"></div>
<div
data-testid="file-switcher-strip"
class="flex h-11 shrink-0 items-center gap-1 border-t border-line bg-pdf-ctrl px-2"
@@ -71,7 +72,7 @@ $effect(() => {
>
<div bind:this={trackEl} class="flex flex-1 gap-1 overflow-x-auto" style="scrollbar-width:none">
<ul bind:this={listEl} aria-live="polite" role="list" class="flex flex-row gap-1 py-1">
<ul bind:this={listEl} role="list" class="flex flex-row gap-1 py-1">
{#each files as entry, i (entry.id)}
<li role="listitem" class="inline-flex shrink-0 items-center">
<button
@@ -102,7 +103,7 @@ $effect(() => {
</button>
<button
type="button"
aria-label="Entfernen"
aria-label={m.bulk_remove_file()}
data-remove-id={entry.id}
onclick={() => onRemove(entry.id)}
class="ml-0.5 flex h-[44px] w-[44px] items-center justify-center text-base text-ink-3 hover:text-ink focus:outline-none focus-visible:ring-2 focus-visible:ring-accent"

View File

@@ -2,17 +2,10 @@ import { describe, it, expect, vi, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page, userEvent } from 'vitest/browser';
import FileSwitcherStrip from './FileSwitcherStrip.svelte';
import type { FileEntry } from './FileSwitcherStrip.svelte';
afterEach(cleanup);
export interface FileEntry {
id: string;
file: File;
title: string;
status: 'idle' | 'error';
previewUrl: string;
}
function makeFiles(n: number): FileEntry[] {
return Array.from({ length: n }, (_, i) => ({
id: `id-${i}`,