i18n(transcription): add reader read-label and panel title strings (#697)
transcription_read_label ("Transkription lesen") for the read-only entry
control and transcription_panel_title ("Transkription") for the plain
header readers see instead of the Lesen/Bearbeiten toggle.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -603,6 +603,8 @@
|
|||||||
"doc_details_no_tags": "Keine Schlagwörter zugeordnet",
|
"doc_details_no_tags": "Keine Schlagwörter zugeordnet",
|
||||||
"doc_details_more_receivers": "+{count} weitere",
|
"doc_details_more_receivers": "+{count} weitere",
|
||||||
"transcription_mode_label": "Transkribieren",
|
"transcription_mode_label": "Transkribieren",
|
||||||
|
"transcription_read_label": "Transkription lesen",
|
||||||
|
"transcription_panel_title": "Transkription",
|
||||||
"transcription_mode_stop": "Fertig",
|
"transcription_mode_stop": "Fertig",
|
||||||
"transcription_block_placeholder": "Text eingeben — mit @Name eine Person aus dem Archiv verknüpfen",
|
"transcription_block_placeholder": "Text eingeben — mit @Name eine Person aus dem Archiv verknüpfen",
|
||||||
"transcription_block_save_saving": "Speichere...",
|
"transcription_block_save_saving": "Speichere...",
|
||||||
|
|||||||
@@ -603,6 +603,8 @@
|
|||||||
"doc_details_no_tags": "No tags assigned",
|
"doc_details_no_tags": "No tags assigned",
|
||||||
"doc_details_more_receivers": "+{count} more",
|
"doc_details_more_receivers": "+{count} more",
|
||||||
"transcription_mode_label": "Transcribe",
|
"transcription_mode_label": "Transcribe",
|
||||||
|
"transcription_read_label": "Read transcription",
|
||||||
|
"transcription_panel_title": "Transcription",
|
||||||
"transcription_mode_stop": "Done",
|
"transcription_mode_stop": "Done",
|
||||||
"transcription_block_placeholder": "Type text — use @name to link a person from the archive",
|
"transcription_block_placeholder": "Type text — use @name to link a person from the archive",
|
||||||
"transcription_block_save_saving": "Saving...",
|
"transcription_block_save_saving": "Saving...",
|
||||||
|
|||||||
@@ -603,6 +603,8 @@
|
|||||||
"doc_details_no_tags": "No hay etiquetas asignadas",
|
"doc_details_no_tags": "No hay etiquetas asignadas",
|
||||||
"doc_details_more_receivers": "+{count} más",
|
"doc_details_more_receivers": "+{count} más",
|
||||||
"transcription_mode_label": "Transcribir",
|
"transcription_mode_label": "Transcribir",
|
||||||
|
"transcription_read_label": "Leer transcripción",
|
||||||
|
"transcription_panel_title": "Transcripción",
|
||||||
"transcription_mode_stop": "Listo",
|
"transcription_mode_stop": "Listo",
|
||||||
"transcription_block_placeholder": "Escriba el texto — use @nombre para vincular a una persona del archivo",
|
"transcription_block_placeholder": "Escriba el texto — use @nombre para vincular a una persona del archivo",
|
||||||
"transcription_block_save_saving": "Guardando...",
|
"transcription_block_save_saving": "Guardando...",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { clickOutside } from '$lib/shared/actions/clickOutside';
|
|||||||
type Props = {
|
type Props = {
|
||||||
canWrite: boolean;
|
canWrite: boolean;
|
||||||
isPdf: boolean;
|
isPdf: boolean;
|
||||||
|
hasTranscription: boolean;
|
||||||
transcribeMode: boolean;
|
transcribeMode: boolean;
|
||||||
filePath?: string | null;
|
filePath?: string | null;
|
||||||
originalFilename?: string | null;
|
originalFilename?: string | null;
|
||||||
@@ -14,12 +15,18 @@ type Props = {
|
|||||||
let {
|
let {
|
||||||
canWrite,
|
canWrite,
|
||||||
isPdf,
|
isPdf,
|
||||||
|
hasTranscription,
|
||||||
transcribeMode = $bindable(),
|
transcribeMode = $bindable(),
|
||||||
filePath = null,
|
filePath = null,
|
||||||
originalFilename = null,
|
originalFilename = null,
|
||||||
fileUrl
|
fileUrl
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
|
const canOpenTranscription = $derived((canWrite || hasTranscription) && isPdf);
|
||||||
|
const transcriptionLabel = $derived(
|
||||||
|
canWrite ? m.transcription_mode_label() : m.transcription_read_label()
|
||||||
|
);
|
||||||
|
|
||||||
let mobileMenuOpen = $state(false);
|
let mobileMenuOpen = $state(false);
|
||||||
|
|
||||||
function startTranscribe() {
|
function startTranscribe() {
|
||||||
@@ -50,10 +57,10 @@ function startTranscribe() {
|
|||||||
role="menu"
|
role="menu"
|
||||||
class="absolute top-full right-0 z-50 mt-1 min-w-[200px] rounded-md border border-line bg-surface p-2 shadow-lg"
|
class="absolute top-full right-0 z-50 mt-1 min-w-[200px] rounded-md border border-line bg-surface p-2 shadow-lg"
|
||||||
>
|
>
|
||||||
{#if canWrite && isPdf && !transcribeMode}
|
{#if canOpenTranscription && !transcribeMode}
|
||||||
<button
|
<button
|
||||||
onclick={startTranscribe}
|
onclick={startTranscribe}
|
||||||
aria-label={m.transcription_mode_label()}
|
aria-label={transcriptionLabel}
|
||||||
aria-pressed={false}
|
aria-pressed={false}
|
||||||
class="flex w-full items-center gap-2 rounded px-3 py-2 text-left text-[16px] text-ink transition hover:bg-muted focus-visible:ring-2 focus-visible:ring-primary"
|
class="flex w-full items-center gap-2 rounded px-3 py-2 text-left text-[16px] text-ink transition hover:bg-muted focus-visible:ring-2 focus-visible:ring-primary"
|
||||||
>
|
>
|
||||||
@@ -70,7 +77,7 @@ function startTranscribe() {
|
|||||||
d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"
|
d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
{m.transcription_mode_label()}
|
{transcriptionLabel}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ afterEach(cleanup);
|
|||||||
const baseProps = {
|
const baseProps = {
|
||||||
canWrite: false,
|
canWrite: false,
|
||||||
isPdf: false,
|
isPdf: false,
|
||||||
|
hasTranscription: false,
|
||||||
transcribeMode: false,
|
transcribeMode: false,
|
||||||
filePath: null as string | null,
|
filePath: null as string | null,
|
||||||
originalFilename: 'brief.pdf' as string | null,
|
originalFilename: 'brief.pdf' as string | null,
|
||||||
@@ -49,6 +50,43 @@ describe('DocumentMobileMenu', () => {
|
|||||||
await expect.element(page.getByRole('button', { name: /transkribieren/i })).toBeVisible();
|
await expect.element(page.getByRole('button', { name: /transkribieren/i })).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('shows no transcription action for a read-only user when no transcription exists', async () => {
|
||||||
|
render(DocumentMobileMenu, {
|
||||||
|
props: {
|
||||||
|
...baseProps,
|
||||||
|
canWrite: false,
|
||||||
|
isPdf: true,
|
||||||
|
hasTranscription: false,
|
||||||
|
filePath: 'docs/x.pdf'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: /weitere aktionen/i }).click();
|
||||||
|
|
||||||
|
await expect
|
||||||
|
.element(page.getByRole('button', { name: /transkribieren/i }))
|
||||||
|
.not.toBeInTheDocument();
|
||||||
|
await expect
|
||||||
|
.element(page.getByRole('button', { name: /transkription lesen/i }))
|
||||||
|
.not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows the read-transcription action for a read-only user when a transcription exists', async () => {
|
||||||
|
render(DocumentMobileMenu, {
|
||||||
|
props: {
|
||||||
|
...baseProps,
|
||||||
|
canWrite: false,
|
||||||
|
isPdf: true,
|
||||||
|
hasTranscription: true,
|
||||||
|
filePath: 'docs/x.pdf'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: /weitere aktionen/i }).click();
|
||||||
|
|
||||||
|
await expect.element(page.getByRole('button', { name: /transkription lesen/i })).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
it('hides the transcribe action when already in transcribeMode', async () => {
|
it('hides the transcribe action when already in transcribeMode', async () => {
|
||||||
render(DocumentMobileMenu, {
|
render(DocumentMobileMenu, {
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ type Doc = {
|
|||||||
location?: string | null;
|
location?: string | null;
|
||||||
status?: string | null;
|
status?: string | null;
|
||||||
tags?: Tag[] | null;
|
tags?: Tag[] | null;
|
||||||
|
hasTranscription?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type GeschichteSummary = {
|
type GeschichteSummary = {
|
||||||
@@ -132,6 +133,7 @@ const overflowPersons = $derived(receivers.slice(2));
|
|||||||
documentId={doc.id}
|
documentId={doc.id}
|
||||||
canWrite={canWrite}
|
canWrite={canWrite}
|
||||||
isPdf={!!isPdf}
|
isPdf={!!isPdf}
|
||||||
|
hasTranscription={!!doc.hasTranscription}
|
||||||
bind:transcribeMode={transcribeMode}
|
bind:transcribeMode={transcribeMode}
|
||||||
filePath={doc.filePath}
|
filePath={doc.filePath}
|
||||||
originalFilename={doc.originalFilename}
|
originalFilename={doc.originalFilename}
|
||||||
@@ -143,6 +145,7 @@ const overflowPersons = $derived(receivers.slice(2));
|
|||||||
<DocumentMobileMenu
|
<DocumentMobileMenu
|
||||||
canWrite={canWrite}
|
canWrite={canWrite}
|
||||||
isPdf={!!isPdf}
|
isPdf={!!isPdf}
|
||||||
|
hasTranscription={!!doc.hasTranscription}
|
||||||
bind:transcribeMode={transcribeMode}
|
bind:transcribeMode={transcribeMode}
|
||||||
filePath={doc.filePath}
|
filePath={doc.filePath}
|
||||||
originalFilename={doc.originalFilename}
|
originalFilename={doc.originalFilename}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ type Props = {
|
|||||||
documentId: string;
|
documentId: string;
|
||||||
canWrite: boolean;
|
canWrite: boolean;
|
||||||
isPdf: boolean;
|
isPdf: boolean;
|
||||||
|
hasTranscription: boolean;
|
||||||
transcribeMode: boolean;
|
transcribeMode: boolean;
|
||||||
filePath?: string | null;
|
filePath?: string | null;
|
||||||
originalFilename?: string | null;
|
originalFilename?: string | null;
|
||||||
@@ -15,17 +16,23 @@ let {
|
|||||||
documentId,
|
documentId,
|
||||||
canWrite,
|
canWrite,
|
||||||
isPdf,
|
isPdf,
|
||||||
|
hasTranscription,
|
||||||
transcribeMode = $bindable(),
|
transcribeMode = $bindable(),
|
||||||
filePath = null,
|
filePath = null,
|
||||||
originalFilename = null,
|
originalFilename = null,
|
||||||
fileUrl
|
fileUrl
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
|
const canOpenTranscription = $derived((canWrite || hasTranscription) && isPdf);
|
||||||
|
const transcriptionLabel = $derived(
|
||||||
|
canWrite ? m.transcription_mode_label() : m.transcription_read_label()
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if canWrite && isPdf && !transcribeMode}
|
{#if canOpenTranscription && !transcribeMode}
|
||||||
<button
|
<button
|
||||||
onclick={() => (transcribeMode = true)}
|
onclick={() => (transcribeMode = true)}
|
||||||
aria-label={m.transcription_mode_label()}
|
aria-label={transcriptionLabel}
|
||||||
aria-pressed={false}
|
aria-pressed={false}
|
||||||
class="hidden items-center gap-1.5 rounded border border-primary px-3 py-1.5 font-sans text-[16px] font-medium text-ink transition hover:bg-primary hover:text-primary-fg focus-visible:ring-2 focus-visible:ring-primary md:flex"
|
class="hidden items-center gap-1.5 rounded border border-primary px-3 py-1.5 font-sans text-[16px] font-medium text-ink transition hover:bg-primary hover:text-primary-fg focus-visible:ring-2 focus-visible:ring-primary md:flex"
|
||||||
>
|
>
|
||||||
@@ -42,7 +49,7 @@ let {
|
|||||||
d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"
|
d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
{m.transcription_mode_label()}
|
{transcriptionLabel}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const baseProps = {
|
|||||||
documentId: 'd1',
|
documentId: 'd1',
|
||||||
canWrite: false,
|
canWrite: false,
|
||||||
isPdf: false,
|
isPdf: false,
|
||||||
|
hasTranscription: false,
|
||||||
transcribeMode: false,
|
transcribeMode: false,
|
||||||
filePath: null as string | null,
|
filePath: null as string | null,
|
||||||
originalFilename: 'brief.pdf' as string | null,
|
originalFilename: 'brief.pdf' as string | null,
|
||||||
@@ -34,6 +35,56 @@ describe('DocumentTopBarActions', () => {
|
|||||||
await expect.element(page.getByRole('button', { name: /transkribieren/i })).toBeVisible();
|
await expect.element(page.getByRole('button', { name: /transkribieren/i })).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('shows no transcription control for a read-only user when no transcription exists', async () => {
|
||||||
|
render(DocumentTopBarActions, {
|
||||||
|
props: {
|
||||||
|
...baseProps,
|
||||||
|
canWrite: false,
|
||||||
|
isPdf: true,
|
||||||
|
hasTranscription: false,
|
||||||
|
filePath: 'docs/x.pdf'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect
|
||||||
|
.element(page.getByRole('button', { name: /transkribieren/i }))
|
||||||
|
.not.toBeInTheDocument();
|
||||||
|
await expect
|
||||||
|
.element(page.getByRole('button', { name: /transkription lesen/i }))
|
||||||
|
.not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows the read-transcription button for a read-only user when a transcription exists', async () => {
|
||||||
|
render(DocumentTopBarActions, {
|
||||||
|
props: {
|
||||||
|
...baseProps,
|
||||||
|
canWrite: false,
|
||||||
|
isPdf: true,
|
||||||
|
hasTranscription: true,
|
||||||
|
filePath: 'docs/x.pdf'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect.element(page.getByRole('button', { name: /transkription lesen/i })).toBeVisible();
|
||||||
|
await expect
|
||||||
|
.element(page.getByRole('button', { name: /^transkribieren$/i }))
|
||||||
|
.not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows the transcribe (edit) label for a writer regardless of hasTranscription', async () => {
|
||||||
|
render(DocumentTopBarActions, {
|
||||||
|
props: {
|
||||||
|
...baseProps,
|
||||||
|
canWrite: true,
|
||||||
|
isPdf: true,
|
||||||
|
hasTranscription: false,
|
||||||
|
filePath: 'docs/x.pdf'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect.element(page.getByRole('button', { name: /transkribieren/i })).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
it('omits the transcribe button when not a PDF', async () => {
|
it('omits the transcribe button when not a PDF', async () => {
|
||||||
render(DocumentTopBarActions, {
|
render(DocumentTopBarActions, {
|
||||||
props: { ...baseProps, canWrite: true, isPdf: false, filePath: 'docs/x.jpg' }
|
props: { ...baseProps, canWrite: true, isPdf: false, filePath: 'docs/x.jpg' }
|
||||||
|
|||||||
Reference in New Issue
Block a user