feat(i18n): convert TranscriptNotes chrome to t() (Batch C)

This commit is contained in:
iamdoubz committed 2026-07-12 11:12:01 -05:00
1 parent 1da127a61c
commit a112b81d6e
1 file changed
+81 -64
+81 -64
View File
@@ -7,6 +7,7 @@
import { settings } from "../stores/settings.svelte";
import { player } from "../stores/player.svelte";
import { api, type SpeakerInfo } from "../api";
import { t } from "../i18n/index.svelte";
import { renderMarkdown } from "../markdown";
import { save, open } from "@tauri-apps/plugin-dialog";
import { layout, clamp } from "../stores/layout.svelte";
@@ -51,7 +52,7 @@
// just the raw ISO code — falls back to the code itself if it's not in
// the (curated) catalog, and to "auto-detecting…" before any is known.
function languageLabel(code: string | null): string {
if (!code) return "auto-detecting…";
if (!code) return t("transcript.lang_auto");
return settings.languages.find((l) => l.code === code)?.label ?? code;
}
@@ -162,7 +163,7 @@
if (!m) return;
const path = await save({
defaultPath: `${m.title}.md`,
filters: [{ name: "Markdown", extensions: ["md"] }],
filters: [{ name: t("transcript.filter_md"), extensions: ["md"] }],
});
if (path) await api.exportMeeting(m.id, path, "md");
}
@@ -179,7 +180,7 @@
if (!m) return;
const path = await save({
defaultPath: `${m.title}.pdf`,
filters: [{ name: "PDF", extensions: ["pdf"] }],
filters: [{ name: t("transcript.filter_pdf"), extensions: ["pdf"] }],
});
if (path) await api.exportMeeting(m.id, path, "pdf");
}
@@ -189,7 +190,7 @@
if (!m) return;
const path = await save({
defaultPath: `${m.title}.docx`,
filters: [{ name: "Word document", extensions: ["docx"] }],
filters: [{ name: t("transcript.filter_docx"), extensions: ["docx"] }],
});
if (path) await api.exportMeeting(m.id, path, "docx");
}
@@ -202,7 +203,7 @@
if (!m) return;
const path = await save({
defaultPath: `${m.title}.md`,
filters: [{ name: "Obsidian note", extensions: ["md"] }],
filters: [{ name: t("transcript.filter_obsidian"), extensions: ["md"] }],
});
if (path) await api.exportMeeting(m.id, path, "obsidian");
}
@@ -259,7 +260,7 @@
class="meeting-title"
value={m.title}
onchange={onTitleChange}
aria-label="Meeting title"
aria-label={t("transcript.title_aria")}
/>
<div
class="split"
@@ -280,22 +281,23 @@
<button
class="pane-toggle"
onclick={toggleTranscript}
title="Show transcript"
aria-label="Show transcript"
title={t("transcript.show")}
aria-label={t("transcript.show")}
>
<PanelLeftOpen size={16} aria-hidden="true" />
</button>
{:else}
<h4>
<MessageSquareText size={14} aria-hidden="true" /> Transcript
<span class="badge lang" title="Transcription language"
<MessageSquareText size={14} aria-hidden="true" />
{t("transcript.heading")}
<span class="badge lang" title={t("transcript.lang_title")}
>{languageLabel(m.language)}</span
>
<button
class="pane-toggle inline"
onclick={toggleTranscript}
title="Hide transcript"
aria-label="Hide transcript"
title={t("transcript.hide")}
aria-label={t("transcript.hide")}
>
<PanelLeftClose size={13} aria-hidden="true" />
</button>
@@ -304,15 +306,18 @@
{@const reprocessModelInfo = settings.models.find((mo) => mo.id === reprocessModel)}
<div class="reprocess">
<select bind:value={reprocessModel}>
<option value="">Re-transcribe with…</option>
<option value="">{t("transcript.reprocess_placeholder")}</option>
{#each settings.models.filter((mo) => mo.installed) as mo (mo.id)}
<option value={mo.id}>{mo.label}</option>
{/each}
</select>
{#if reprocessModelInfo?.multilingual}
<select bind:value={reprocessLanguage} aria-label="Reprocess language">
<option value="">Keep current language</option>
<option value="auto">Auto-detect</option>
<select
bind:value={reprocessLanguage}
aria-label={t("transcript.reprocess_lang_aria")}
>
<option value="">{t("transcript.reprocess_keep_lang")}</option>
<option value="auto">{t("settings.transcription.auto")}</option>
{#each settings.languages as l (l.code)}
<option value={l.code}>{l.label}</option>
{/each}
@@ -320,12 +325,12 @@
{/if}
<button disabled={!reprocessModel || reprocessing} onclick={reprocess}>
<RefreshCw size={13} aria-hidden="true" class={reprocessing ? "spin" : ""} />
{reprocessing ? "Re-transcribing…" : "Go"}
{reprocessing ? t("transcript.retranscribing") : t("transcript.reprocess_go")}
</button>
</div>
{/if}
{#if m.segments.length === 0}
<p class="muted">No transcript for this meeting.</p>
<p class="muted">{t("transcript.empty")}</p>
{:else}
{#each m.segments as s (s.id)}
<button
@@ -333,7 +338,7 @@
class="seg"
class:active={s.id === activeSegId}
onclick={() => player.seek(s.start_ms)}
title="Play from here"
title={t("transcript.play_from_here")}
>
<span class="ts">{fmtTs(s.start_ms)}</span>
<strong>{speakerName(s.speaker, m.speakers)}:</strong>
@@ -345,7 +350,7 @@
</div>
{#if !layout.transcriptCollapsed && !layout.notesCollapsed}
<Splitter
label="Resize transcript and notes"
label={t("transcript.resize")}
onResize={onSplitResize}
onResizeEnd={() => layout.persist()}
/>
@@ -357,89 +362,99 @@
<button
class="pane-toggle"
onclick={toggleNotes}
title="Show notes"
aria-label="Show notes"
title={t("notes.show")}
aria-label={t("notes.show")}
>
<PanelRightOpen size={16} aria-hidden="true" />
</button>
{:else}
<h4>
<NotebookPen size={14} aria-hidden="true" /> Notes
<NotebookPen size={14} aria-hidden="true" />
{t("notes.heading")}
<button
class="pane-toggle inline"
onclick={toggleNotes}
title="Hide notes"
aria-label="Hide notes"
title={t("notes.hide")}
aria-label={t("notes.hide")}
>
<PanelRightClose size={13} aria-hidden="true" />
</button>
</h4>
<div class="toolbar" role="toolbar" aria-label="Notes formatting">
<button onclick={() => wrapSelection("**")} title="Bold" aria-label="Bold">
<div class="toolbar" role="toolbar" aria-label={t("notes.toolbar_aria")}>
<button
onclick={() => wrapSelection("**")}
title={t("notes.bold")}
aria-label={t("notes.bold")}
>
<Bold size={14} aria-hidden="true" />
</button>
<button onclick={() => wrapSelection("_")} title="Italic" aria-label="Italic">
<button
onclick={() => wrapSelection("_")}
title={t("notes.italic")}
aria-label={t("notes.italic")}
>
<Italic size={14} aria-hidden="true" />
</button>
<button onclick={() => insertLinePrefix("# ")} title="Heading 1" aria-label="Heading 1">
<button
onclick={() => insertLinePrefix("# ")}
title={t("notes.h1")}
aria-label={t("notes.h1")}
>
<Heading1 size={14} aria-hidden="true" />
</button>
<button
onclick={() => insertLinePrefix("## ")}
title="Heading 2"
aria-label="Heading 2"
title={t("notes.h2")}
aria-label={t("notes.h2")}
>
<Heading2 size={14} aria-hidden="true" />
</button>
<button
onclick={() => insertLinePrefix("- ")}
title="Bullet list"
aria-label="Bullet list"
title={t("notes.bullet")}
aria-label={t("notes.bullet")}
>
<List size={14} aria-hidden="true" />
</button>
<button
onclick={() => insertLinePrefix("- [ ] ")}
title="Checkbox"
aria-label="Checkbox list item"
title={t("notes.checkbox_title")}
aria-label={t("notes.checkbox_aria")}
>
<ListChecks size={14} aria-hidden="true" />
</button>
<button
class="toggle"
onclick={() => (notesPreview = !notesPreview)}
title={notesPreview ? "Edit the raw markdown" : "Render the markdown"}
title={notesPreview ? t("notes.edit_raw") : t("notes.render")}
aria-pressed={notesPreview}
>
{#if notesPreview}
<Pencil size={14} aria-hidden="true" />
Editor
{t("notes.editor")}
{:else}
<Eye size={14} aria-hidden="true" />
Preview
{t("notes.preview")}
{/if}
</button>
<span class="spacer"></span>
<button onclick={exportMd} title="Export notes as .md">
<button onclick={exportMd} title={t("notes.export_md_title")}>
<FileText size={13} aria-hidden="true" />
.md
</button>
<button onclick={exportPdf} title="Export notes as .pdf">
<button onclick={exportPdf} title={t("notes.export_pdf_title")}>
<FileDown size={13} aria-hidden="true" />
PDF
</button>
<button onclick={exportDocx} title="Export notes as .docx">
<button onclick={exportDocx} title={t("notes.export_docx_title")}>
<FileDown size={13} aria-hidden="true" />
Word
</button>
<button onclick={exportBundle} title="Export audio + transcript + notes to a folder">
<button onclick={exportBundle} title={t("notes.export_bundle_title")}>
<FolderOutput size={13} aria-hidden="true" />
Bundle
</button>
<button
onclick={exportObsidian}
title="Export one Obsidian note (notes + summary + transcript, no audio)"
>
<button onclick={exportObsidian} title={t("notes.export_obsidian_title")}>
<NotebookPen size={13} aria-hidden="true" />
Obsidian
</button>
@@ -453,7 +468,7 @@
bind:this={editorEl}
bind:value={notesText}
oninput={scheduleSave}
placeholder="Notes…"
placeholder={t("notes.placeholder")}
></textarea>
{/if}
</div>
@@ -461,7 +476,7 @@
</div>
</div>
{:else if recording.state === "idle"}
<p class="muted pad">Transcript will appear here as you record.</p>
<p class="muted pad">{t("transcript.will_appear")}</p>
{:else}
<!-- Granola-style redesign: the Notes pane is open and typable while
recording, and clicking a transcript line attaches a note to that
@@ -476,25 +491,26 @@
<button
class="pane-toggle"
onclick={toggleTranscript}
title="Show transcript"
aria-label="Show transcript"
title={t("transcript.show")}
aria-label={t("transcript.show")}
>
<PanelLeftOpen size={16} aria-hidden="true" />
</button>
{:else}
<h4>
<MessageSquareText size={14} aria-hidden="true" /> Transcript
<MessageSquareText size={14} aria-hidden="true" />
{t("transcript.heading")}
<button
class="pane-toggle inline"
onclick={toggleTranscript}
title="Hide transcript"
aria-label="Hide transcript"
title={t("transcript.hide")}
aria-label={t("transcript.hide")}
>
<PanelLeftClose size={13} aria-hidden="true" />
</button>
</h4>
{#if recording.segments.length === 0}
<p class="muted">Transcript will appear here as you record.</p>
<p class="muted">{t("transcript.will_appear")}</p>
{:else}
{#each recording.segments as s (s.id)}
{@const hasNote = recording.segmentNotes.has(s.start_ms)}
@@ -511,18 +527,18 @@
<strong>{speakerName(s.speaker)}:</strong>
{s.text}
{#if hasNote}
<span class="note-badge" title="Has a note">📝</span>
<span class="note-badge" title={t("transcript.has_note")}>📝</span>
{/if}
</button>
{#if open}
<input
type="text"
class="segment-note-input"
placeholder="Add a note for this moment…"
placeholder={t("transcript.note_placeholder")}
value={recording.segmentNotes.get(s.start_ms) ?? ""}
onchange={(e) =>
recording.setSegmentNote(s.start_ms, (e.target as HTMLInputElement).value)}
aria-label="Note for this transcript line"
aria-label={t("transcript.note_aria")}
/>
{/if}
</div>
@@ -532,7 +548,7 @@
</div>
{#if !layout.transcriptCollapsed && !layout.notesCollapsed}
<Splitter
label="Resize transcript and notes"
label={t("transcript.resize")}
onResize={onSplitResize}
onResizeEnd={() => layout.persist()}
/>
@@ -544,19 +560,20 @@
<button
class="pane-toggle"
onclick={toggleNotes}
title="Show notes"
aria-label="Show notes"
title={t("notes.show")}
aria-label={t("notes.show")}
>
<PanelRightOpen size={16} aria-hidden="true" />
</button>
{:else}
<h4>
<NotebookPen size={14} aria-hidden="true" /> Notes
<NotebookPen size={14} aria-hidden="true" />
{t("notes.heading")}
<button
class="pane-toggle inline"
onclick={toggleNotes}
title="Hide notes"
aria-label="Hide notes"
title={t("notes.hide")}
aria-label={t("notes.hide")}
>
<PanelRightClose size={13} aria-hidden="true" />
</button>
@@ -565,7 +582,7 @@
class="live-notes"
value={recording.notesText}
oninput={(e) => recording.setNotesText((e.target as HTMLTextAreaElement).value)}
placeholder="Type notes while you talk…"
placeholder={t("notes.live_placeholder")}
></textarea>
{/if}
</div>