+
setDetail(null)}
>
- All Projects
+ {t("sidebar.allProjects")}
@@ -645,15 +644,16 @@ function App() {
{detail.project.name || fileName(detail.project.sourcePath)}
+
setShowSettings(!showSettings)}
- title="API Settings"
+ title={t("topbar.apiSettings")}
>
- API Settings
+ {t("topbar.apiSettings")}
- void refresh(detail.project.id)} title="Refresh">
+ void refresh(detail.project.id)} title={t("topbar.refreshTitle")}>
@@ -663,89 +663,89 @@ function App() {
- Transcription Engine
+ {t("settings.transcriptionEngine")}
setTranscriptionEngine(event.target.value as "deepgram" | "local")}
>
- Local Whisper (Offline)
- Deepgram (Cloud)
+ {t("settings.transcriptionOptions.local")}
+ {t("settings.transcriptionOptions.deepgram")}
- LLM Engine
+ {t("settings.llmEngine")}
setLlmEngine(event.target.value as any)}
>
- Ollama (Offline Local)
- Claude (Cloud)
- DeepSeek (Cloud)
- Google Gemini (Cloud)
- OpenAI (Cloud)
- OpenRouter (Cloud)
+ {t("settings.llmOptions.local")}
+ {t("settings.llmOptions.claude")}
+ {t("settings.llmOptions.deepseek")}
+ {t("settings.llmOptions.gemini")}
+ {t("settings.llmOptions.openai")}
+ {t("settings.llmOptions.openrouter")}
{transcriptionEngine === "deepgram" && (
- Deepgram API Key
+ {t("settings.deepgramKey")}
setDeepgramKey(event.target.value)}
- placeholder={environment?.hasDeepgramKey ? "Loaded from env" : "Optional (Deepgram API Key)"}
+ placeholder={environment?.hasDeepgramKey ? t("settings.loadedFromEnv") : t("settings.optionalKey", { label: t("settings.deepgramKey") })}
type="password"
/>
)}
{llmEngine === "claude" && (
- Claude API Key
+ {t("settings.claudeKey")}
setAnthropicKey(event.target.value)}
- placeholder={environment?.hasAnthropicKey ? "Loaded from env" : "Optional (Claude API Key)"}
+ placeholder={environment?.hasAnthropicKey ? t("settings.loadedFromEnv") : t("settings.optionalKey", { label: t("settings.claudeKey") })}
type="password"
/>
)}
{llmEngine === "deepseek" && (
- DeepSeek API Key
+ {t("settings.deepseekKey")}
setDeepseekKey(event.target.value)}
- placeholder={environment?.hasDeepseekKey ? "Loaded from env" : "Optional (DeepSeek API Key)"}
+ placeholder={environment?.hasDeepseekKey ? t("settings.loadedFromEnv") : t("settings.optionalKey", { label: t("settings.deepseekKey") })}
type="password"
/>
)}
{llmEngine === "gemini" && (
- Gemini API Key
+ {t("settings.geminiKey")}
setGeminiKey(event.target.value)}
- placeholder={environment?.hasGeminiKey ? "Loaded from env" : "Optional (Gemini API Key)"}
+ placeholder={environment?.hasGeminiKey ? t("settings.loadedFromEnv") : t("settings.optionalKey", { label: t("settings.geminiKey") })}
type="password"
/>
)}
{llmEngine === "openai" && (
- OpenAI API Key
+ {t("settings.openaiKey")}
setOpenaiKey(event.target.value)}
- placeholder={environment?.hasOpenaiKey ? "Loaded from env" : "Optional (OpenAI API Key)"}
+ placeholder={environment?.hasOpenaiKey ? t("settings.loadedFromEnv") : t("settings.optionalKey", { label: t("settings.openaiKey") })}
type="password"
/>
)}
{llmEngine === "openrouter" && (
- OpenRouter API Key
+ {t("settings.openrouterKey")}
setOpenrouterKey(event.target.value)}
- placeholder={environment?.hasOpenrouterKey ? "Loaded from env" : "Optional (OpenRouter API Key)"}
+ placeholder={environment?.hasOpenrouterKey ? t("settings.loadedFromEnv") : t("settings.optionalKey", { label: t("settings.openrouterKey") })}
type="password"
/>
@@ -753,12 +753,12 @@ function App() {
{llmEngine === "local" && (
- Ollama Model Name
+ {t("settings.ollamaModelName")}
setLocalLlmModel(event.target.value)}
- placeholder="e.g. llama3.2, qwen2.5:7b"
+ placeholder={t("settings.ollamaModelPlaceholder")}
type="text"
/>
pullModelDirectly(localLlmModel)}
>
- Pull
+ {t("settings.pull")}
@@ -779,13 +779,13 @@ function App() {
className="icon-button"
style={{ background: 'rgba(239, 68, 68, 0.08)', borderColor: 'rgba(239, 68, 68, 0.2)', color: '#f87171' }}
onClick={() => {
- if (window.confirm("Are you sure you want to reset your configuration and restart onboarding from scratch?")) {
+ if (window.confirm(t("settings.resetConfirm"))) {
localStorage.clear();
window.location.reload();
}
}}
>
- Reset App Configuration & Onboarding
+ {t("settings.resetConfig")}
@@ -794,24 +794,24 @@ function App() {
{error && {error}
}
-
} label="Transcript" done={Boolean(detail.transcript)} />
-
} label="Moments" done={detail.candidates.length > 0} />
-
} label="Cut" done={selectedCount > 0 && selectedCutCount === selectedCount} />
-
} label="Captions" done={selectedCount > 0 && selectedCaptionsCount === selectedCount} />
-
} label="Export" done={selectedCount > 0 && selectedCutCount === selectedCount} />
+
} label={t("pipeline.transcript")} done={Boolean(detail.transcript)} />
+
} label={t("pipeline.moments")} done={detail.candidates.length > 0} />
+
} label={t("pipeline.cut")} done={selectedCount > 0 && selectedCutCount === selectedCount} />
+
} label={t("pipeline.captions")} done={selectedCount > 0 && selectedCaptionsCount === selectedCount} />
+
} label={t("pipeline.export")} done={selectedCount > 0 && selectedCutCount === selectedCount} />
-
Transcript
-
{transcript ? `${transcript.segments.length} segments` : "No transcript"}
+
{t("transcript.title")}
+
{transcript ? t("transcript.segments", { count: transcript.segments.length }) : t("transcript.empty")}
{busy === "transcribe" ? : }
- Transcribe
+ {t("transcript.transcribe")}
@@ -819,8 +819,8 @@ function App() {
{!canTranscribe && (
{transcriptionEngine === "local"
- ? `⚠️ Local Whisper (Python package 'openai-whisper') is not installed. Run 'pip3 install openai-whisper' in your terminal.`
- : "⚠️ Deepgram API Key is missing. Transcribing will not work. Please add your key in API Settings."}
+ ? t("transcript.warningLocal")
+ : t("transcript.warningCloud")}
)}
@@ -830,24 +830,24 @@ function App() {
{formatTime(segment.start)}
{segment.text}
- )) ?? } label="Transcript pending" />}
+ )) ?? } label={t("transcript.pending")} />}
-
Clip Candidates
-
{detail.candidates.length ? `${selectedCount} selected` : "No candidates"}
+
{t("candidates.title")}
+
{detail.candidates.length ? t("candidates.selected", { count: selectedCount }) : t("candidates.empty")}
{busy === "cut" ? : }
- Cut
+ {t("candidates.cut")}
void moments(false)} disabled={busy !== "idle" || !detail.transcript || !canUseActiveLlm}>
{busy === "moments" ? : }
- Find Viral Moments
+ {t("candidates.findMoments")}
@@ -855,13 +855,8 @@ function App() {
{!canUseActiveLlm && (
{llmEngine === "local"
- ? "⚠️ Ollama local server is not running at http://localhost:11434. Moment detection will not work."
- : `⚠️ ${llmEngine === "claude" ? "Claude" :
- llmEngine === "deepseek" ? "DeepSeek" :
- llmEngine === "gemini" ? "Gemini" :
- llmEngine === "openai" ? "OpenAI" :
- llmEngine === "openrouter" ? "OpenRouter" : "LLM"
- } API Key is missing. Viral moment identification will not work. Please add your key in API Settings.`}
+ ? t("candidates.warningLocal")
+ : t("candidates.warningCloud", { engine: t(`status.${llmEngine === "claude" ? "claude" : llmEngine === "deepseek" ? "deepseek" : llmEngine === "gemini" ? "gemini" : llmEngine === "openai" ? "openai" : llmEngine === "openrouter" ? "openrouter" : "llm"}`) })}
)}
@@ -907,14 +902,14 @@ function App() {
{formatTime(candidate.startSec)} - {formatTime(candidate.endSec)}
- {Math.round(candidate.score * 100)}% Match
+ {Math.round(candidate.score * 100)}% {t("candidates.match")}
{candidate.hook}
{candidate.rationale}
- {isCut ? "Cut ready" : clip?.status === "error" ? "Cut failed" : clip?.status ?? "Pending"}
+ {isCut ? t("candidates.cutReady") : clip?.status === "error" ? t("candidates.cutFailed") : clip?.status ?? t("candidates.pending")}
)}
- {renderingCandidateId === candidate.id ? "Cutting..." : isCut ? "Re-cut" : "Cut"}
+ {renderingCandidateId === candidate.id ? t("candidates.cutting") : isCut ? t("candidates.reCut") : t("candidates.cut")}
{clip?.outputPath &&
{clip.outputPath}
}
{clip?.captionAssPath && (
- Subtitles: {clip.captionAssPath}
+ {t("candidates.subtitles", { path: clip.captionAssPath })}
)}
{clip?.renderLog &&
{clip.renderLog}
}
@@ -940,7 +935,7 @@ function App() {
);
})}
- {detail.candidates.length === 0 &&
} label="Moments pending" />}
+ {detail.candidates.length === 0 &&
} label={t("candidates.empty")} />}
@@ -949,13 +944,16 @@ function App() {
{projects.length > 0 ? (
@@ -970,18 +968,18 @@ function App() {
{name}
- Duration: {project.sourceDuration ? formatTime(project.sourceDuration) : "Probing..."}
- Created: {new Date(project.createdAt).toLocaleDateString()}
+ {t("home.duration")} {project.sourceDuration ? formatTime(project.sourceDuration) : t("common.probing")}
+ {t("home.created")} {new Date(project.createdAt).toLocaleDateString()}
void selectProject(project.id)}>
- Open
+ {t("home.open")}
void renameProject(project.id)}>
- Rename
+ {t("home.rename")}
void deleteProject(project.id)}>
- Delete
+ {t("home.delete")}
@@ -991,8 +989,8 @@ function App() {
) : (
-
No projects found
-
Import your first recording to begin creating shorts.
+
{t("home.noProjects")}
+
{t("home.emptyHint")}
)}
@@ -1002,17 +1000,17 @@ function App() {
@@ -1021,8 +1019,8 @@ function App() {
-
Choose Caption Style
-
Select how your automated captions should look on the portrait short-form video clips.
+
{t("styleModal.title")}
+
{t("styleModal.subtitle")}
@@ -1033,8 +1031,8 @@ function App() {
BRAINFOOD BECAUSE
-
Modern Box
-
Sleek white text inside a semi-transparent black background padding box. Highly readable.
+
{t("styleModal.styles.modernBox.name")}
+
{t("styleModal.styles.modernBox.description")}
BRAINFOOD BECAUSE
-
Classic Outline
-
Vibrant bold yellow text with a clean black outline. High-energy CapCut formatting.
+
{t("styleModal.styles.classicOutline.name")}
+
{t("styleModal.styles.classicOutline.description")}
BRAINFOOD BECAUSE
-
Minimal Shadow
-
Pure white text with a soft, elegant drop shadow. Unobtrusive and modern.
+
{t("styleModal.styles.minimalShadow.name")}
+
{t("styleModal.styles.minimalShadow.description")}
BRAINFOOD BECAUSE
- Vibrant Cyan
- Vibrant tech cyan text with a black drop shadow for a clean look.
+ {t("styleModal.styles.vibrantCyan.name")}
+ {t("styleModal.styles.vibrantCyan.description")}
BRAINFOOD BECAUSE
- Vibrant Yellow Box
- Bold black text inside a solid yellow padding box. Punchy and high visibility.
+ {t("styleModal.styles.vibrantYellowBox.name")}
+ {t("styleModal.styles.vibrantYellowBox.description")}
BRAINFOOD BECAUSE
- Vibrant Green
- High-energy neon green text with black borders and a drop shadow (Hormozi style).
+ {t("styleModal.styles.vibrantGreen.name")}
+ {t("styleModal.styles.vibrantGreen.description")}
BRAINFOOD BECAUSE
- Vibrant Red
- Dramatic neon crimson text with outline and drop shadow (gaming/action style).
+ {t("styleModal.styles.vibrantRed.name")}
+ {t("styleModal.styles.vibrantRed.description")}
{ setShowStyleModal(false); setMediaPathToImport(null); }}>
- Cancel
+ {t("styleModal.cancel")}
confirmImport(selectedStyle)}>
- Confirm & Import
+ {t("styleModal.confirm")}
@@ -1119,8 +1117,8 @@ function App() {
-
Downloading Ollama Model
-
Downloading model weights for "{downloadingModelName}". Please do not close the app.
+
{t("modelDownload.title")}
+
{t("modelDownload.description", { modelName: downloadingModelName })}
@@ -1210,6 +1208,7 @@ function Onboarding({
deepseekKey: initialDeepseekKey,
refreshEnv,
}: OnboardingProps) {
+ const { t } = useTranslation();
const [setupMode, setSetupMode] = useState<"choose" | "local" | "cloud" | "downloading">("choose");
const [selectedModel, setSelectedModel] = useState
("llama3.2");
@@ -1217,7 +1216,7 @@ function Onboarding({
const [antKey, setAntKey] = useState(initialAnthropicKey);
const [dsKey, setDsKey] = useState(initialDeepseekKey);
- const [downloadStatus, setDownloadStatus] = useState("Initializing download...");
+ const [downloadStatus, setDownloadStatus] = useState(t("common.initializingDownload"));
const [downloadProgress, setDownloadProgress] = useState(0);
const [error, setError] = useState(null);
const [checkingOllama, setCheckingOllama] = useState(false);
@@ -1232,11 +1231,11 @@ function Onboarding({
const handleCloudSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!dgKey.trim()) {
- setError("Deepgram API Key is required for cloud mode.");
+ setError(t("onboarding.errors.deepgramRequired"));
return;
}
if (!antKey.trim() && !dsKey.trim()) {
- setError("Please provide at least one LLM Key (Claude or DeepSeek).");
+ setError(t("onboarding.errors.llmRequired"));
return;
}
@@ -1280,7 +1279,7 @@ function Onboarding({
if (!isOllamaRunning) {
setSetupMode("downloading");
- setDownloadStatus("Ollama not found. Starting automatic installer...");
+ setDownloadStatus(t("onboarding.errors.ollamaNotFound"));
try {
const unlistenInstall = await listen("ollama-install-status", (event) => {
@@ -1290,14 +1289,14 @@ function Onboarding({
await invoke("install_ollama");
unlistenInstall();
} catch (err) {
- setError("Automatic installation failed: " + String(err) + ". Please install it manually from ollama.com.");
+ setError(t("onboarding.errors.installFailed", { error: String(err) }));
setSetupMode("local");
return;
}
}
setSetupMode("downloading");
- setDownloadStatus("Ollama connected. Initiating model download...");
+ setDownloadStatus(t("onboarding.errors.ollamaConnected"));
try {
const unlisten = await listen<{
@@ -1342,8 +1341,8 @@ function Onboarding({
- Welcome to AutoShorts
- Long recording in. Short clips out. Select how you would like to run the studio.
+ {t("onboarding.welcome")}
+ {t("onboarding.tagline")}
@@ -1351,18 +1350,18 @@ function Onboarding({
-
Fully Offline & Private
-
Process everything locally on your computer. Private, secure, and completely free.
-
Offline (Ollama)
+
{t("onboarding.offline.title")}
+
{t("onboarding.offline.description")}
+
{t("onboarding.offline.badge")}
setSetupMode("cloud")}>
-
Cloud APIs
-
Use high-speed cloud services for transcription and analysis. No local GPU needed.
-
API Keys Required
+
{t("onboarding.cloud.title")}
+
{t("onboarding.cloud.description")}
+
{t("onboarding.cloud.badge")}
>
@@ -1371,8 +1370,8 @@ function Onboarding({
{setupMode === "local" && (
-
Configure Offline Mode
-
Follow these steps to set up your local studio.
+
{t("onboarding.localSetup.title")}
+
{t("onboarding.localSetup.subtitle")}
{error &&
{error}
}
@@ -1381,19 +1380,19 @@ function Onboarding({
1
-
Install Python Whisper
-
Open your terminal and run the following command to install the transcription engine:
+
{t("onboarding.localSetup.step1.title")}
+
{t("onboarding.localSetup.step1.description")}
- pip3 install -U openai-whisper
+ {t("onboarding.localSetup.step1.command")}
{copied ? : }
- {copied ? "Copied!" : "Copy"}
+ {copied ? t("onboarding.localSetup.step1.copied") : t("onboarding.localSetup.step1.copy")}
{environment?.hasLocalWhisperModel ? (
-
Whisper installed in Python!
+
{t("onboarding.localSetup.step1.installed")}
) : (
-
⚠️ Python package 'whisper' not detected yet. Run the command above.
+
⚠️ {t("onboarding.localSetup.step1.notDetected")}
)}
@@ -1401,12 +1400,9 @@ function Onboarding({
2
-
Set up local LLM (Ollama)
-
- Ollama must be installed and running on your machine.
- If you don't have it installed, you can download it from ollama.com .
-
-
Select a model to download:
+
{t("onboarding.localSetup.step2.title")}
+
+
{t("onboarding.localSetup.step2.selectModel")}
setSelectedModel("llama3.2")}
>
-
LLaMA 3.2 3B
- 1.9 GB
+ {t("onboarding.localSetup.step2.models.llama32.name")}
+ {t("onboarding.localSetup.step2.models.llama32.size")}
-
Requires 8GB+ RAM. Recommended for standard setups. Fast and efficient.
+
{t("onboarding.localSetup.step2.models.llama32.description")}
setSelectedModel("qwen2.5:3b")}
>
-
Qwen 2.5 3B
- 2.0 GB
+ {t("onboarding.localSetup.step2.models.qwen3b.name")}
+ {t("onboarding.localSetup.step2.models.qwen3b.size")}
-
Requires 8GB+ RAM. Excellent coding and logical reasoning abilities.
+
{t("onboarding.localSetup.step2.models.qwen3b.description")}
setSelectedModel("qwen2.5:7b")}
>
-
Qwen 2.5 7B
- 4.7 GB
+ {t("onboarding.localSetup.step2.models.qwen7b.name")}
+ {t("onboarding.localSetup.step2.models.qwen7b.size")}
-
Requires 16GB+ RAM. High-quality moment detection and hook precision.
+
{t("onboarding.localSetup.step2.models.qwen7b.description")}
@@ -1447,7 +1443,7 @@ function Onboarding({
- setSetupMode("choose")}>Back
+ setSetupMode("choose")}>{t("onboarding.localSetup.back")}
{checkingOllama ? : null}
- {checkingOllama ? "Checking Ollama..." : "Download & Start Setup"}
+ {checkingOllama ? t("onboarding.localSetup.checkingOllama") : t("onboarding.localSetup.downloadStart")}
@@ -1464,48 +1460,48 @@ function Onboarding({
{setupMode === "cloud" && (
)}
@@ -1513,8 +1509,8 @@ function Onboarding({
{setupMode === "downloading" && (
-
Downloading Local Model
-
Please wait while your local environment is downloaded. Do not close the application.
+
{t("onboarding.downloading.title")}
+
{t("onboarding.downloading.subtitle")}
diff --git a/src/styles.css b/src/styles.css
index a856eef..b66e779 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -1714,3 +1714,23 @@ button:disabled {
font-weight: 700;
color: var(--accent-primary);
}
+
+.language-selector {
+ background: var(--bg-input);
+ border: 1px solid var(--border-color);
+ border-radius: 6px;
+ color: var(--text-primary);
+ font-size: 13px;
+ padding: 6px 10px;
+ cursor: pointer;
+ outline: none;
+}
+
+.language-selector:focus {
+ border-color: var(--border-color-focus);
+}
+
+.language-selector option {
+ background: var(--bg-panel);
+ color: var(--text-primary);
+}