Compare commits
@@ -1,16 +1,30 @@
|
||||
# Publish Docker images to GitHub Container Registry (GHCR).
|
||||
#
|
||||
# Triggers:
|
||||
# - push of a tag matching `v*` (e.g. `v0.2.7`) → pushed as :0.2.7 + :latest
|
||||
# - workflow_dispatch → pushed as :sha-<short> (for testing)
|
||||
# - push of a tag matching `v*` (e.g. `v0.3.0`) → :0.3.0, :0.3, :latest, :sha-
|
||||
# - push to main branch → :main, :sha- (rolling "edge" build)
|
||||
# - workflow_dispatch → :sha- only (ad-hoc test build)
|
||||
#
|
||||
# Tag ↔ image mapping
|
||||
# :latest — always the most recent versioned release (set on every v* tag push)
|
||||
# :0.3.0 — exact version from the git tag
|
||||
# :0.3 — major.minor floating tag (updated on every patch within the minor)
|
||||
# :main — latest commit on main; may be ahead of the last tagged release
|
||||
# :sha-xxxx — specific commit SHA; produced by workflow_dispatch
|
||||
#
|
||||
# Images land at: ghcr.io/debpalash/omnivoice-studio
|
||||
#
|
||||
# NOTE: the Docker image is the headless web-server build of OmniVoice (FastAPI
|
||||
# backend + pre-built React frontend served over HTTP). The Tauri desktop
|
||||
# auto-updater and its update-channel toggle are desktop-only features; they do
|
||||
# NOT apply to the Docker image.
|
||||
|
||||
name: Docker (GHCR)
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ['v*']
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@@ -42,19 +56,30 @@ jobs:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Extracts semver tags from the git ref:
|
||||
# v0.2.7 → 0.2.7, latest
|
||||
# manual dispatch → sha-abc1234
|
||||
# Tag strategy (`:sha-<short>` is emitted on every trigger):
|
||||
# v0.3.0 tag push → :0.3.0, :0.3, :latest, :sha-
|
||||
# main branch push → :main, :sha-
|
||||
# workflow_dispatch → :sha- only
|
||||
#
|
||||
# Fix for stale :latest (issues #249, #251):
|
||||
# The previous rule used `enable={{is_default_branch}}`, which evaluates
|
||||
# to false on tag pushes (detached HEAD) — so :latest was never updated
|
||||
# when a release tag was pushed. The version / :latest / :main rules are
|
||||
# gated on `github.event_name == 'push'` so a manual workflow_dispatch can
|
||||
# only ever produce a throwaway `:sha-` tag (never republish a mutable
|
||||
# tag), and :latest additionally excludes prerelease tags (those contain a
|
||||
# `-`, e.g. v1.0.0-rc.1) so a prerelease can't clobber :latest.
|
||||
- name: Extract metadata (tags, labels)
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{version}},enable=${{ github.event_name == 'push' }}
|
||||
type=semver,pattern={{major}}.{{minor}},enable=${{ github.event_name == 'push' }}
|
||||
type=raw,value=latest,enable=${{ github.event_name == 'push' && github.ref_type == 'tag' && !contains(github.ref, '-') }}
|
||||
type=raw,value=main,enable=${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
type=sha,prefix=sha-,format=short
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
|
||||
+159
-69
@@ -144,16 +144,18 @@ jobs:
|
||||
rust_target: x86_64-pc-windows-msvc
|
||||
bundles: "msi,updater"
|
||||
|
||||
# Linux: ship .deb + .AppImage. AppImage is universal (no distro
|
||||
# package-manager dep), runs on any glibc-2.31+ host. Now viable
|
||||
# because the thin uv-venv installer is ~10 MB (vs the prior ~2 GB
|
||||
# PyInstaller payload that exceeded linuxdeploy limits). FUSE
|
||||
# unavailability on GH runners handled via APPIMAGE_EXTRACT_AND_RUN=1.
|
||||
# Linux: ship .AppImage only. AppImage is universal (no distro
|
||||
# package-manager dep), runs on any glibc-2.31+ host, and is the
|
||||
# Linux auto-update target. The .deb target was dropped: tauri-bundler
|
||||
# fails it with "Failed to create control scripts: No such file or
|
||||
# directory" (no custom deb config of ours is at fault) — revisit on a
|
||||
# tauri-cli bump. FUSE unavailability on GH runners is handled via
|
||||
# APPIMAGE_EXTRACT_AND_RUN=1.
|
||||
- os: ubuntu-22.04
|
||||
arch: x86_64-unknown-linux-gnu
|
||||
label: "Linux x64"
|
||||
rust_target: x86_64-unknown-linux-gnu
|
||||
bundles: "deb,appimage,updater"
|
||||
bundles: "appimage,updater"
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: ${{ matrix.label }}
|
||||
@@ -359,25 +361,72 @@ jobs:
|
||||
echo 'RELEASE_BODY_EOF'
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Apple code-signing is OPT-IN and OFF by default. Export the APPLE_*
|
||||
# secrets to $GITHUB_ENV ONLY for a stable `v*` release with the
|
||||
# MACOS_SIGNING_ENABLED repo variable set. On every other path (preview,
|
||||
# or stable without the var) the APPLE_* vars stay ABSENT — NOT empty.
|
||||
# This matters: Tauri's macOS bundler runs `security import` whenever
|
||||
# APPLE_CERTIFICATE is *present* (even ""), which fails the whole build;
|
||||
# absence makes it skip cert import and bundle unsigned (users clear
|
||||
# quarantine via `xattr -cr`, see docs/install/macos.md). A static `env:`
|
||||
# on the build step can't express "absent", so signing lives here.
|
||||
# To enable signed stable releases: fix the signing secrets, then set the
|
||||
# repo variable MACOS_SIGNING_ENABLED = true.
|
||||
- name: Configure Apple signing (stable, opt-in)
|
||||
if: runner.os == 'macOS' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && vars.MACOS_SIGNING_ENABLED == 'true'
|
||||
env:
|
||||
C: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
CP: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
SI: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
AID: ${{ secrets.APPLE_ID }}
|
||||
AP: ${{ secrets.APPLE_PASSWORD }}
|
||||
TID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
run: |
|
||||
{
|
||||
echo "APPLE_CERTIFICATE<<__OV_EOF__"
|
||||
echo "$C"
|
||||
echo "__OV_EOF__"
|
||||
echo "APPLE_CERTIFICATE_PASSWORD=$CP"
|
||||
echo "APPLE_SIGNING_IDENTITY=$SI"
|
||||
echo "APPLE_ID=$AID"
|
||||
echo "APPLE_PASSWORD=$AP"
|
||||
echo "APPLE_TEAM_ID=$TID"
|
||||
} >> "$GITHUB_ENV"
|
||||
|
||||
# Stamp each preview build with a unique, monotonically increasing semver
|
||||
# PRERELEASE so the updater actually offers it (a rolling preview that
|
||||
# always reported the static 0.3.0 never looked "newer", so no update was
|
||||
# ever delivered). Ephemeral, CI-only — never committed. Tauri reads the
|
||||
# bundle + updater version from tauri.conf.json, so rewriting it here
|
||||
# stamps the artifacts + latest.json. `0.3.0-preview.N` is a prerelease of
|
||||
# the current target, so previews converge to stable when 0.3.0 ships
|
||||
# (0.3.0 > 0.3.0-preview.N). NOTE: the Windows MSI ProductVersion strips
|
||||
# the prerelease (→ 0.3.0), a wrinkle to verify for win preview→preview
|
||||
# upgrades; mac/linux replace the bundle wholesale and are unaffected.
|
||||
- name: Stamp preview version
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.publish_preview
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
CONF=frontend/src-tauri/tauri.conf.json
|
||||
BASE=$(jq -r .version "$CONF")
|
||||
PREVIEW_VERSION="${BASE}-preview.${{ github.run_number }}"
|
||||
tmp=$(mktemp)
|
||||
jq --arg v "$PREVIEW_VERSION" '.version = $v' "$CONF" > "$tmp"
|
||||
mv "$tmp" "$CONF"
|
||||
echo "Stamped preview version: $PREVIEW_VERSION"
|
||||
|
||||
- name: Build + release (Tauri)
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
# macOS Developer-ID code-signing + notarization (#134 / #72).
|
||||
# tauri-action signs + notarizes the .app/.dmg ONLY when these are
|
||||
# non-empty; with the secrets unset they resolve to empty strings and
|
||||
# the build stays unsigned (today's behavior — users clear quarantine
|
||||
# via `xattr -cr`, see docs/install/macos.md). To enable, add the repo
|
||||
# secrets documented in docs/install/macos.md → "For maintainers".
|
||||
# No-op on the Windows/Linux matrix legs.
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
# macOS Apple signing (#134 / #72) is configured by the preceding
|
||||
# "Configure Apple signing" step — it exports APPLE_* to $GITHUB_ENV
|
||||
# only on the opt-in stable path, leaving them ABSENT (not "") on
|
||||
# preview/unsigned paths so Tauri's bundler skips cert import. A static
|
||||
# env: here would always set them to "" and break the mac build.
|
||||
# GH runners disable FUSE, so linuxdeploy's AppImage can't mount
|
||||
# itself at bundle time. This env tells linuxdeploy to extract-and-run
|
||||
# instead, which works without FUSE.
|
||||
@@ -398,10 +447,13 @@ jobs:
|
||||
includeUpdaterJson: true
|
||||
|
||||
# ── Installer smoke (Phase 0 GATE-03) ─────────────────────────────
|
||||
# Boot the just-built bundle on this matrix leg, poll /health, fail
|
||||
# the release if it doesn't come up. Catches bundle-only regressions
|
||||
# (PyInstaller missing-module, Tauri sidecar path mismatch, etc.)
|
||||
# that the in-process smoke matrix on ci.yml cannot see.
|
||||
# Structural verification of the installed/extracted bundle. The thin
|
||||
# uv-venv installer ships NO frozen backend binary (the venv is built on
|
||||
# first launch via the bundled `uv`), so there is nothing to boot with
|
||||
# `--health-check` here. Instead assert the bundle carries the shell
|
||||
# binary, the bundled `uv` sidecar, and the backend source resources
|
||||
# (pyproject.toml + backend/main.py) — the real "is the bundle complete"
|
||||
# regression that ci.yml's in-process smoke can't catch.
|
||||
- name: Installer smoke (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
timeout-minutes: 5
|
||||
@@ -410,27 +462,20 @@ jobs:
|
||||
set -euo pipefail
|
||||
DMG=$(find frontend/src-tauri/target/${{ matrix.rust_target }}/release/bundle/dmg -name "*.dmg" | head -1)
|
||||
echo "Smoke-testing DMG: $DMG"
|
||||
MOUNT=$(hdiutil attach -nobrowse -readonly "$DMG" | tail -1 | awk '{print $3}')
|
||||
# Grab the full mount path — the volume name has a space ("OmniVoice
|
||||
# Studio"), so `awk '{print $3}'` would truncate it to /Volumes/OmniVoice.
|
||||
MOUNT=$(hdiutil attach -nobrowse -readonly "$DMG" | tail -1 | grep -oE '/Volumes/.*$')
|
||||
APP=$(find "$MOUNT" -maxdepth 2 -name "*.app" | head -1)
|
||||
# RESEARCH Pitfall #5: do NOT launch the Tauri WebView shell on a headless runner — it hangs.
|
||||
# The `--health-check` flag lives in backend/main.py (Python), not the Rust WebView main.
|
||||
# Strategy: invoke the bundled Python backend directly, bypassing Tauri's window code.
|
||||
BACKEND=$(find "$APP/Contents" -type f \( -name 'backend' -o -name 'backend.app' -o -name 'main.py' \) -perm +111 2>/dev/null | head -1)
|
||||
if [ -z "$BACKEND" ]; then
|
||||
# Fallback: try the PyInstaller sidecar location Tauri uses.
|
||||
BACKEND=$(find "$APP/Contents/Resources" -type f \( -name 'backend*' -o -name 'omnivoice*' \) -perm +111 2>/dev/null | head -1)
|
||||
fi
|
||||
if [ -z "$BACKEND" ]; then
|
||||
echo "FAIL — could not locate bundled backend binary in $APP. Contents:"
|
||||
find "$APP/Contents" -type f -perm +111 | head -30
|
||||
hdiutil detach "$MOUNT" || true
|
||||
exit 1
|
||||
fi
|
||||
echo "Launching bundled backend: $BACKEND --health-check"
|
||||
"$BACKEND" --health-check
|
||||
EXIT=$?
|
||||
fail() { echo "FAIL — $1"; find "$APP/Contents" -maxdepth 4 -type f 2>/dev/null | head -40; hdiutil detach "$MOUNT" || true; exit 1; }
|
||||
[ -n "$APP" ] || { echo "FAIL — no .app inside DMG"; hdiutil detach "$MOUNT" || true; exit 1; }
|
||||
# Thin uv-venv installer ships no frozen backend to boot — verify the
|
||||
# bundle is complete: shell binary + bundled uv sidecar + backend source.
|
||||
ls "$APP/Contents/MacOS"/* >/dev/null 2>&1 || fail "no shell binary in Contents/MacOS"
|
||||
find "$APP/Contents" -type f -name 'uv' | grep -q . || fail "bundled uv sidecar missing"
|
||||
find "$APP/Contents" -type f -name 'pyproject.toml' | grep -q . || fail "backend resource pyproject.toml missing"
|
||||
find "$APP/Contents" -type f -path '*/backend/main.py' | grep -q . || fail "backend source backend/main.py missing"
|
||||
echo "OK — bundle has shell + uv + backend resources"
|
||||
hdiutil detach "$MOUNT" || true
|
||||
exit $EXIT
|
||||
|
||||
- name: Installer smoke (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
@@ -442,24 +487,15 @@ jobs:
|
||||
echo "Smoke-testing MSI: $MSI"
|
||||
# /quiet = no UI, /norestart = don't reboot the runner if a dep asks
|
||||
msiexec.exe //i "$(cygpath -w "$MSI")" //quiet //norestart
|
||||
# Tauri installs to "Program Files\OmniVoice Studio\..." by default. Backend is a sidecar binary
|
||||
# (RESEARCH Pitfall #5) — not the Tauri WebView .exe — so locate by name pattern.
|
||||
BACKEND=$(find "/c/Program Files/OmniVoice Studio" -type f \( -name 'backend.exe' -o -name 'omnivoice-backend.exe' -o -name 'main.exe' \) 2>/dev/null | head -1)
|
||||
if [ -z "$BACKEND" ]; then
|
||||
echo "FAIL — bundled backend .exe not found under C:/Program Files/OmniVoice Studio. Contents:"
|
||||
find "/c/Program Files/OmniVoice Studio" -type f -name '*.exe' | head -20
|
||||
exit 1
|
||||
fi
|
||||
echo "Launching bundled backend: $BACKEND --health-check"
|
||||
"$BACKEND" --health-check &
|
||||
BACKEND_PID=$!
|
||||
# Wait for completion (--health-check is a short-lived, exits-after-200 invocation)
|
||||
wait $BACKEND_PID
|
||||
EXIT=$?
|
||||
# RESEARCH Pitfall #2: cleanup orphaned PyInstaller child processes on port 3900.
|
||||
# Safe on GH-hosted ephemeral runners; REQUIRED if/when we move to self-hosted Windows.
|
||||
taskkill //F //T //PID $BACKEND_PID 2>/dev/null || echo "backend process already exited cleanly"
|
||||
exit $EXIT
|
||||
INSTALL="/c/Program Files/OmniVoice Studio"
|
||||
fail() { echo "FAIL — $1. Contents:"; find "$INSTALL" -maxdepth 4 -type f 2>/dev/null | head -40; exit 1; }
|
||||
# Thin uv-venv installer ships no frozen backend .exe — verify the
|
||||
# install is complete: shell exe + bundled uv + backend source resources.
|
||||
test -f "$INSTALL/omnivoice-studio.exe" || fail "shell exe missing"
|
||||
test -f "$INSTALL/uv.exe" || fail "bundled uv missing"
|
||||
find "$INSTALL" -type f -name 'pyproject.toml' | grep -q . || fail "backend resource pyproject.toml missing"
|
||||
find "$INSTALL" -type f -path '*backend*main.py' | grep -q . || fail "backend source main.py missing"
|
||||
echo "OK — MSI installed shell + uv + backend resources"
|
||||
|
||||
- name: Installer smoke (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
@@ -469,20 +505,25 @@ jobs:
|
||||
set -euo pipefail
|
||||
# Use the AppImage — single-file, no installer needed.
|
||||
APPIMAGE=$(find frontend/src-tauri/target/${{ matrix.rust_target }}/release/bundle/appimage -name "*.AppImage" | head -1)
|
||||
# Resolve to an absolute path BEFORE the cd below — `--appimage-extract`
|
||||
# always writes ./squashfs-root into the CWD, so we cd into a temp dir,
|
||||
# at which point a relative AppImage path would no longer resolve.
|
||||
APPIMAGE=$(realpath "$APPIMAGE")
|
||||
echo "Smoke-testing AppImage: $APPIMAGE"
|
||||
chmod +x "$APPIMAGE"
|
||||
# GH runners have no FUSE — extract before running (mirrors APPIMAGE_EXTRACT_AND_RUN=1 used at build time).
|
||||
EXTRACT_DIR="$(mktemp -d)"
|
||||
cd "$EXTRACT_DIR"
|
||||
"$APPIMAGE" --appimage-extract >/dev/null
|
||||
# Tauri's AppRun lives at squashfs-root/AppRun; the actual binary is in squashfs-root/usr/bin/
|
||||
BIN=$(find squashfs-root -type f -name "OmniVoice Studio" -o -name "omnivoice-studio" 2>/dev/null | head -1)
|
||||
if [ -z "$BIN" ]; then
|
||||
BIN="$EXTRACT_DIR/squashfs-root/AppRun"
|
||||
fi
|
||||
echo "Launching under xvfb-run: $BIN --health-check"
|
||||
sudo apt-get install -y xvfb >/dev/null 2>&1 || true
|
||||
xvfb-run -a "$BIN" --health-check
|
||||
ROOT="$EXTRACT_DIR/squashfs-root"
|
||||
fail() { echo "FAIL — $1"; find "$ROOT" -maxdepth 5 -type f 2>/dev/null | head -40; exit 1; }
|
||||
# Thin uv-venv installer: verify the AppImage carries the shell binary,
|
||||
# the bundled uv sidecar, and the backend source resources.
|
||||
{ [ -f "$ROOT/AppRun" ] || find "$ROOT" -type f \( -name "OmniVoice Studio" -o -name "omnivoice-studio" \) | grep -q .; } || fail "shell binary / AppRun missing"
|
||||
find "$ROOT" -type f -name 'uv' | grep -q . || fail "bundled uv sidecar missing"
|
||||
find "$ROOT" -type f -name 'pyproject.toml' | grep -q . || fail "backend resource pyproject.toml missing"
|
||||
find "$ROOT" -type f -path '*/backend/main.py' | grep -q . || fail "backend source backend/main.py missing"
|
||||
echo "OK — AppImage has shell + uv + backend resources"
|
||||
|
||||
# ── Compute SHA-256 checksums (Phase 0 GATE-05) ───────────────────
|
||||
# Native OS tools: shasum -a 256 (POSIX) / Get-FileHash (Windows).
|
||||
@@ -499,7 +540,13 @@ jobs:
|
||||
|
||||
# Gather artifact paths per matrix leg's `bundles` (msi/app/dmg/deb/appimage/updater).
|
||||
# `find` is portable across all three runners (Git Bash on Windows).
|
||||
mapfile -t ARTIFACTS < <(find "$BUNDLE_DIR" -type f \
|
||||
# NB: macOS runners use /bin/bash 3.2, which has no `mapfile` (a bash 4+
|
||||
# builtin) — using it 127'd this step and dropped the macOS SHA256SUMS
|
||||
# for v0.3.1 and v0.3.2. A `while read` loop is portable to bash 3.2.
|
||||
ARTIFACTS=()
|
||||
while IFS= read -r artifact; do
|
||||
ARTIFACTS+=("$artifact")
|
||||
done < <(find "$BUNDLE_DIR" -type f \
|
||||
\( -name "*.dmg" -o -name "*.app.tar.gz" -o -name "*.app.tar.gz.sig" \
|
||||
-o -name "*.msi" -o -name "*.msi.sig" \
|
||||
-o -name "*.AppImage" -o -name "*.AppImage.sig" \
|
||||
@@ -543,3 +590,46 @@ jobs:
|
||||
body_path: ${{ steps.checksums.outputs.checksums_file }}
|
||||
files: ${{ steps.checksums.outputs.checksums_file }}
|
||||
fail_on_unmatched_files: true
|
||||
|
||||
# ── Auto-generated preview release notes ──────────────────────────────────
|
||||
# tauri-action publishes the rolling `preview` release with the plain
|
||||
# changelog-fallback body ("Auto-generated release for main…"). Replace it
|
||||
# with GitHub's auto-generated notes (What's Changed by PR + Contributors +
|
||||
# Full Changelog) once the matrix has finished. Runs once (no matrix race),
|
||||
# preview-only — stable `v*` releases keep their CHANGELOG section + the
|
||||
# appended checksums.
|
||||
preview-notes:
|
||||
needs: build
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.publish_preview
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Generate + apply GitHub release notes to the preview release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
NOTES=$(gh api --method POST "repos/$REPO/releases/generate-notes" -f tag_name=preview --jq .body)
|
||||
# Build a Contributors avatar strip from the PR authors GitHub listed
|
||||
# in the notes ("by @handle in …"). Inline <a>/<img> render on the
|
||||
# release page (GitHub strips inline styles, so avatars are square).
|
||||
CONTRIB=""
|
||||
HANDLES=$(printf '%s\n' "$NOTES" | grep -oE 'by @[A-Za-z0-9-]+' | sed 's/^by @//' | sort -u || true)
|
||||
if [ -n "$HANDLES" ]; then
|
||||
CONTRIB=$'## Contributors\n\n'
|
||||
while IFS= read -r h; do
|
||||
[ -z "$h" ] && continue
|
||||
CONTRIB="$CONTRIB<a href=\"https://github.com/$h\" title=\"@$h\"><img src=\"https://github.com/$h.png?size=64\" width=\"48\" alt=\"@$h\"/></a> "
|
||||
done <<< "$HANDLES"
|
||||
fi
|
||||
{
|
||||
echo "> 🧪 **Rolling preview build from \`main\`** — newest features, less tested. Opt in via **Settings → About → Update channel → Preview**; switch back to Stable any time."
|
||||
echo ""
|
||||
echo "$NOTES"
|
||||
echo ""
|
||||
echo "$CONTRIB"
|
||||
} > /tmp/preview-notes.md
|
||||
gh release edit preview --repo "$REPO" --notes-file /tmp/preview-notes.md
|
||||
echo "Applied auto-generated release notes + contributors to the preview release."
|
||||
|
||||
@@ -129,3 +129,8 @@ marketing.md
|
||||
.claude/skills/speckit-*/
|
||||
.antigravitycli/
|
||||
|
||||
playwright-report/
|
||||
.last-run.json
|
||||
|
||||
# probe — generated HTML reports
|
||||
tests/probe/reports/
|
||||
|
||||
@@ -6,6 +6,57 @@ The format is loosely based on [Keep a Changelog](https://keepachangelog.com/).
|
||||
Versions track the desktop app (`tauri.conf.json` + `frontend/src-tauri/Cargo.toml`).
|
||||
The bundled TTS model package (`pyproject.toml`) is versioned independently.
|
||||
|
||||
## [0.3.3] — 2026-06-03
|
||||
|
||||
### Fixed
|
||||
- **Settings → About showed the wrong architecture in the Docker/web build.**
|
||||
The "Architecture" row rendered the *client browser's* platform
|
||||
(`navigator.platform` → e.g. "Win32"); it now reports the **server's** CPU
|
||||
architecture from the backend (`platform.machine()`), correct for both the
|
||||
desktop app and Docker. The blank version/GPU/RAM/VRAM in the same report
|
||||
were the loopback-gate 403s already fixed in v0.3.2. (#262)
|
||||
|
||||
### CI
|
||||
- The release SHA-256 checksum step no longer uses `mapfile` (a bash 4+
|
||||
builtin) — it broke on the macOS runner's bash 3.2 and dropped the macOS
|
||||
`SHA256SUMS` for v0.3.1/v0.3.2. Now portable to bash 3.2.
|
||||
|
||||
## [0.3.2] — 2026-06-03
|
||||
|
||||
### Fixed
|
||||
- **"Loopback origin required" all over the Docker UI** (and a blank version).
|
||||
The `/system/*` and `/api/settings/*` routes are restricted to a loopback
|
||||
origin, but Docker's NAT makes every request look non-loopback, so the gate
|
||||
403'd the operator out of the admin UI — including `/system/info` (blanking
|
||||
the version) and HF-token entry. The Docker image now runs with
|
||||
`OMNIVOICE_SERVER_MODE=1`, which relaxes the gate for the headless
|
||||
deployment; exposure is governed by the `-p` port mapping plus the optional
|
||||
share PIN. Desktop builds are unaffected — their loopback boundary (and the
|
||||
denial of admin routes to LAN share guests) is unchanged. (#261)
|
||||
|
||||
## [0.3.1] — 2026-06-03
|
||||
|
||||
First tagged build of the 0.3 line off `main` — it ships the accumulated
|
||||
`[0.3.0]` work below plus the fixes here. (The `[0.3.0]` milestone heading is
|
||||
kept for the qualitative "actually useful" release.)
|
||||
|
||||
### Fixed
|
||||
- **Voice-clone / export download crashed in the Docker & browser build** with
|
||||
`TypeError: Cannot read properties of undefined (reading 'invoke')`. The
|
||||
export button called the Tauri save dialog unconditionally; outside the
|
||||
desktop shell it now falls back to a standard browser download of the file
|
||||
served at `/audio/<path>`. (#256)
|
||||
- **Docker container showed no version** (a dash) in Settings → About, and the
|
||||
desktop-only update-channel toggle appeared in the web build. The running
|
||||
version is now read from the backend (`/system/info` `app_version`, `/health`
|
||||
`version`); the updater UI is hidden outside Tauri. Also corrected the
|
||||
version-check command in the Docker docs (`omnivoice`, not
|
||||
`omnivoice-studio`). (#249)
|
||||
- **Transcription failures were masked** by a generic "Transcribe stream
|
||||
dropped" message. The transcribe SSE stream now surfaces the real, sanitized
|
||||
cause (with an actionable hint) instead of silently dropping when model load
|
||||
or VRAM offload fails. (#255)
|
||||
|
||||
## [0.3.0] — Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<a href="#features">Features</a> ·
|
||||
<a href="#why-omnivoice-studio">Why OmniVoice Studio?</a> ·
|
||||
<a href="#tts-engines">TTS Engines</a> ·
|
||||
<a href="#asr-engines">ASR Engines</a> ·
|
||||
<a href="#contributing">Contributing</a> ·
|
||||
<a href="https://discord.gg/bzQavDfVV9">Discord</a> ·
|
||||
<a href="README_CN.md"><strong>简体中文</strong></a>
|
||||
@@ -237,6 +238,22 @@ OmniVoice ships a multi-engine TTS backend. The default engine (OmniVoice) is al
|
||||
|
||||
> **CUDA** = GPU-accelerated · **MPS** = Apple Silicon Metal · **CPU** = runs everywhere, slower for large models · KittenTTS and MOSS-TTS-Nano run realtime on CPU · MLX-Audio is Apple Silicon only.
|
||||
|
||||
### ASR Engines
|
||||
|
||||
OmniVoice ships a multi-engine ASR (speech-to-text) backend that powers dictation, video dubbing, and subtitle generation — all fully local. **WhisperX** is the cross-platform default; the rest are opt-in and auto-detected. Switch in **Settings → ASR Engine** or via the `OMNIVOICE_ASR_BACKEND` env var.
|
||||
|
||||
| Engine | `OMNIVOICE_ASR_BACKEND` | Languages | Best for |
|
||||
|--------|-------------------------|:---------:|----------|
|
||||
| **WhisperX** (default) | `whisperx` | ~100 | Dubbing & subtitles — word-level timing via wav2vec2 forced alignment |
|
||||
| **Faster-Whisper** | `faster-whisper` | ~100 | Fast transcription on Linux / macOS / Windows (CTranslate2) |
|
||||
| **MLX Whisper** | `mlx-whisper` | ~100 | Native Apple Silicon speed (Apple MLX / Metal) |
|
||||
| **PyTorch Whisper** | `pytorch-whisper` | ~100 | CUDA / CPU fallback via 🤗 Transformers |
|
||||
| **Parakeet TDT** | `nemo-parakeet` | English + 25 EU | SOTA English accuracy, auto language detection (NVIDIA NeMo, GPU only) |
|
||||
| **Moonshine** | `moonshine` | English | Edge / low-latency, ONNX |
|
||||
| **FunASR** | `funasr` | 50+ | All-in-one multilingual — built-in VAD + inline speaker diarization (SenseVoice) |
|
||||
|
||||
> Whisper-family engines cover ~100 languages; **FunASR / SenseVoice** adds an all-in-one multilingual path with built-in voice-activity detection and inline speaker diarization. Every engine runs on-device — no API keys, no cloud.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
@@ -5,9 +5,12 @@ These are intentionally tiny — one concern per dependency — so they can be
|
||||
composed at the route or router level without surprises.
|
||||
|
||||
Currently exposed:
|
||||
- `require_loopback`: 403 unless the request came from a loopback origin.
|
||||
- `require_loopback`: 403 unless the request came from a loopback origin
|
||||
(bypassed in explicit server mode — see `_server_mode`).
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from fastapi import HTTPException, Request
|
||||
|
||||
|
||||
@@ -19,6 +22,28 @@ from fastapi import HTTPException, Request
|
||||
# the guard: nothing here matches a non-loopback origin.
|
||||
_LOOPBACK_HOSTS = frozenset({"127.0.0.1", "::1", "localhost"})
|
||||
|
||||
_TRUTHY = frozenset({"1", "true", "yes", "on"})
|
||||
|
||||
|
||||
def _server_mode() -> bool:
|
||||
"""Whether this process is a headless server deployment (Docker image).
|
||||
|
||||
In Docker the loopback gate is *unenforceable*: Docker's network NAT
|
||||
rewrites ``request.client.host`` to the bridge gateway (e.g. 172.17.0.1)
|
||||
even for a localhost-only ``-p 127.0.0.1:3900:3900`` mapping, so every
|
||||
request looks non-loopback and the gate 403s the operator out of the
|
||||
system/settings routes they need (issue #261 — incl. ``/system/info``,
|
||||
which blanks the version display).
|
||||
|
||||
The Docker image sets ``OMNIVOICE_SERVER_MODE=1`` to opt out of the gate.
|
||||
Network exposure then rests on the operator's port mapping plus the
|
||||
optional share PIN (``NetworkAccessMiddleware`` still 401s unauthenticated
|
||||
non-loopback clients whenever a PIN is set). The desktop build never sets
|
||||
this, so its loopback boundary — including denying LAN share guests access
|
||||
to admin routes — is unchanged. Read at call time so it stays testable.
|
||||
"""
|
||||
return os.environ.get("OMNIVOICE_SERVER_MODE", "").strip().lower() in _TRUTHY
|
||||
|
||||
|
||||
def require_loopback(request: Request) -> None:
|
||||
"""Reject any request whose `client.host` is not a loopback address.
|
||||
@@ -35,7 +60,14 @@ def require_loopback(request: Request) -> None:
|
||||
Returns None on success (FastAPI dependency convention). Raises 403
|
||||
on rejection — the response body is `{"detail": "loopback origin required"}`
|
||||
so existing tests for `/system/set-env` keep passing without modification.
|
||||
|
||||
In server mode (Docker, see `_server_mode`) the gate is a no-op: the
|
||||
loopback origin is unenforceable there and exposure is governed by the
|
||||
deployment's port mapping + the optional share PIN instead.
|
||||
"""
|
||||
host = request.client.host if request.client else None
|
||||
if host not in _LOOPBACK_HOSTS:
|
||||
raise HTTPException(status_code=403, detail="loopback origin required")
|
||||
if host in _LOOPBACK_HOSTS:
|
||||
return
|
||||
if _server_mode():
|
||||
return
|
||||
raise HTTPException(status_code=403, detail="loopback origin required")
|
||||
|
||||
@@ -41,6 +41,17 @@ _PREVIEW_DIR = Path(OUTPUTS_DIR) / "archetype_previews"
|
||||
# Seed fixed so repeated renders of the same archetype are reproducible
|
||||
# (mirrors scripts/render_demos_omnivoice.py).
|
||||
_PREVIEW_SEED = 42
|
||||
# Diffusion steps for previews. 16 under-converges: certain (script, seed)
|
||||
# points — notably the "social" sample script at seed 42 — collapse to a
|
||||
# degenerate tonal buzz (The Hype Host / Podcaster / Vlogger, issue follow-up).
|
||||
# 32 reliably converges to speech across the gallery's instruct/script space
|
||||
# at a one-time (cached) render cost.
|
||||
_PREVIEW_NUM_STEP = 32
|
||||
# Spectral-flatness floor below which a render is a degenerate tonal artifact
|
||||
# rather than speech. Real, mastered speech sits ~0.04–0.07; a tonal buzz
|
||||
# collapses to <0.005. 0.015 separates the two with wide margin and sits well
|
||||
# below even breathy/whisper voices (which are broadband → high flatness).
|
||||
_DEGENERATE_FLATNESS = 0.015
|
||||
|
||||
|
||||
def _preview_key(a: dict) -> str:
|
||||
@@ -80,6 +91,41 @@ def _is_blank_audio(audio_tensor) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _spectral_flatness(audio_tensor) -> Optional[float]:
|
||||
"""Geometric-mean / arithmetic-mean of the power spectrum.
|
||||
|
||||
~1.0 for broadband noise, →0 for a pure tone. The degenerate diffusion
|
||||
renders this guards against are near-pure tonal buzzes (flatness <0.005),
|
||||
distinct from both silence (caught by ``_is_blank_audio``) and real speech
|
||||
(~0.04+). Returns ``None`` if it can't be computed so callers don't act on
|
||||
a bad measurement.
|
||||
"""
|
||||
try:
|
||||
import torch
|
||||
|
||||
t = audio_tensor if isinstance(audio_tensor, torch.Tensor) else torch.as_tensor(audio_tensor)
|
||||
t = t.detach().to("cpu", dtype=torch.float32).flatten()
|
||||
if t.numel() < 1024 or not torch.isfinite(t).all():
|
||||
return None
|
||||
spec = torch.fft.rfft(t * torch.hann_window(t.numel())).abs().pow(2) + 1e-12
|
||||
return float(torch.exp(torch.mean(torch.log(spec))) / torch.mean(spec))
|
||||
except Exception: # never let the checker itself block a render
|
||||
return None
|
||||
|
||||
|
||||
def _is_unusable_audio(audio_tensor) -> bool:
|
||||
"""True if a render is silent/non-finite OR a degenerate tonal buzz.
|
||||
|
||||
The blank guard alone misses the tonal-collapse failure mode: a buzz is
|
||||
*loud* (peaks near -2 dBFS after normalize), so it sails past the silence
|
||||
floor and — without this — gets cached and served as the preview.
|
||||
"""
|
||||
if _is_blank_audio(audio_tensor):
|
||||
return True
|
||||
flatness = _spectral_flatness(audio_tensor)
|
||||
return flatness is not None and flatness < _DEGENERATE_FLATNESS
|
||||
|
||||
|
||||
async def _render_archetype_wav(a: dict, out_path: Path) -> None:
|
||||
"""Render an archetype's sample script to ``out_path`` using the live engine.
|
||||
|
||||
@@ -112,7 +158,7 @@ async def _render_archetype_wav(a: dict, out_path: Path) -> None:
|
||||
None, # ref_text
|
||||
a["instruct"], # instruct
|
||||
None, # duration
|
||||
16, # num_step
|
||||
_PREVIEW_NUM_STEP, # num_step
|
||||
2.0, # guidance_scale
|
||||
1.0, # speed
|
||||
None, # t_shift
|
||||
@@ -126,13 +172,14 @@ async def _render_archetype_wav(a: dict, out_path: Path) -> None:
|
||||
)
|
||||
|
||||
audio_tensor = await loop.run_in_executor(_gpu_pool, _infer, _PREVIEW_SEED)
|
||||
if _is_blank_audio(audio_tensor):
|
||||
# Static message only — the archetype id derives from the request path
|
||||
# param, and CodeQL flags logging request-derived data (clear-text /
|
||||
# log-injection). The seed is a module constant, safe to log.
|
||||
logger.warning("Archetype rendered blank at seed %d — retrying once", _PREVIEW_SEED)
|
||||
if _is_unusable_audio(audio_tensor):
|
||||
# Blank OR a degenerate tonal buzz — retry once on a different seed to
|
||||
# step off the bad diffusion trajectory. Static message only: the
|
||||
# archetype id is request-derived (CodeQL log-injection); the seed is a
|
||||
# module constant, safe to log.
|
||||
logger.warning("Archetype rendered unusable at seed %d — retrying once", _PREVIEW_SEED)
|
||||
audio_tensor = await loop.run_in_executor(_gpu_pool, _infer, _PREVIEW_SEED + 1)
|
||||
if _is_blank_audio(audio_tensor):
|
||||
if _is_unusable_audio(audio_tensor):
|
||||
raise RuntimeError("the voice engine returned no audible audio for this archetype")
|
||||
|
||||
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
@@ -200,7 +247,14 @@ async def preview_archetype(archetype_id: str):
|
||||
f"unavailable. See Settings → Logs → Backend. Error: {e}"
|
||||
),
|
||||
)
|
||||
return FileResponse(str(cache_path), media_type="audio/wav")
|
||||
# no-cache (not no-store): the URL is stable but its bytes change when an
|
||||
# archetype's preview is re-rendered, so force the client to revalidate
|
||||
# against the ETag instead of serving a stale cached clip indefinitely.
|
||||
return FileResponse(
|
||||
str(cache_path),
|
||||
media_type="audio/wav",
|
||||
headers={"Cache-Control": "no-cache"},
|
||||
)
|
||||
|
||||
|
||||
@router.post("/archetypes/{archetype_id}/use")
|
||||
|
||||
@@ -384,24 +384,39 @@ async def dub_transcribe_stream(job_id: str):
|
||||
if not job:
|
||||
preflight_error = "Job not found. It may have been cleaned up or was never created."
|
||||
else:
|
||||
_model = await get_model()
|
||||
asr_audio_target = job.get("vocals_path")
|
||||
if not asr_audio_target or not os.path.exists(asr_audio_target):
|
||||
asr_audio_target = job.get("audio_path")
|
||||
if not asr_audio_target or not os.path.exists(asr_audio_target):
|
||||
preflight_error = "No audio available for transcription."
|
||||
else:
|
||||
from services.asr_backend import get_active_asr_backend
|
||||
try:
|
||||
_asr_backend = get_active_asr_backend(asr_pipe=getattr(_model, "_asr_pipe", None))
|
||||
if _asr_backend.id == "pytorch-whisper" and getattr(_model, "_asr_pipe", None) is None:
|
||||
preflight_error = (
|
||||
"No ASR backend is ready. Install WhisperX/faster-whisper/MLX Whisper "
|
||||
"or set OMNIVOICE_PRELOAD_TTS_ASR=1 before launch to use the PyTorch fallback."
|
||||
# Guard the model load: if it raises, the SSE stream would otherwise die
|
||||
# before emitting any event, and the UI shows a misleading generic
|
||||
# "stream dropped" message instead of the real cause (issue #255).
|
||||
try:
|
||||
_model = await get_model()
|
||||
except Exception as e:
|
||||
logger.exception("transcribe preflight: model load failed (job=%s)", job_id)
|
||||
from core.failure import build_failure
|
||||
f = build_failure(e, stage="transcribe-preflight", include_diagnostic=False)
|
||||
preflight_error = f["reason"] + (f" — {f['hint']}" if f.get("hint") else "")
|
||||
_model = None
|
||||
if _model is not None:
|
||||
asr_audio_target = job.get("vocals_path")
|
||||
if not asr_audio_target or not os.path.exists(asr_audio_target):
|
||||
asr_audio_target = job.get("audio_path")
|
||||
if not asr_audio_target or not os.path.exists(asr_audio_target):
|
||||
preflight_error = "No audio available for transcription."
|
||||
else:
|
||||
from services.asr_backend import get_active_asr_backend
|
||||
try:
|
||||
_asr_backend = get_active_asr_backend(asr_pipe=getattr(_model, "_asr_pipe", None))
|
||||
if _asr_backend.id == "pytorch-whisper" and getattr(_model, "_asr_pipe", None) is None:
|
||||
preflight_error = (
|
||||
"No ASR backend is ready. Install WhisperX/faster-whisper/MLX Whisper "
|
||||
"or set OMNIVOICE_PRELOAD_TTS_ASR=1 before launch to use the PyTorch fallback."
|
||||
)
|
||||
except Exception as e:
|
||||
from core.failure import build_failure
|
||||
f = build_failure(e, stage="transcribe-preflight", include_diagnostic=False)
|
||||
preflight_error = "ASR backend initialization failed: " + f["reason"] + (
|
||||
f" — {f['hint']}" if f.get("hint") else ""
|
||||
)
|
||||
except Exception as e:
|
||||
preflight_error = f"ASR backend initialization failed: {e}"
|
||||
scene_cuts = job.get("scene_cuts") or []
|
||||
scene_cuts = job.get("scene_cuts") or []
|
||||
|
||||
async def gen():
|
||||
if preflight_error:
|
||||
@@ -429,7 +444,12 @@ async def dub_transcribe_stream(job_id: str):
|
||||
|
||||
# Free VRAM: move TTS model to CPU so WhisperX + VAD can fit.
|
||||
# Only offloads when free GPU memory is < 4 GB (e.g. laptop GPUs).
|
||||
await loop.run_in_executor(_cpu_pool, offload_tts_for_asr)
|
||||
# Non-fatal: an offload failure must not drop the stream (#255) —
|
||||
# transcription can still proceed (it just has less headroom).
|
||||
try:
|
||||
await loop.run_in_executor(_cpu_pool, offload_tts_for_asr)
|
||||
except Exception as e:
|
||||
logger.warning("offload_tts_for_asr failed (continuing): %s", e)
|
||||
|
||||
all_segments: list[dict] = []
|
||||
detected_lang = None
|
||||
@@ -540,15 +560,23 @@ async def dub_transcribe_stream(job_id: str):
|
||||
# whisperx's VAD load, or an unsupported audio format.
|
||||
if not all_segments:
|
||||
# Deduplicate while preserving order so one root cause doesn't
|
||||
# repeat N times in the UI toast.
|
||||
# repeat N times in the UI toast. Sanitize each message so home
|
||||
# paths / tokens from a backend traceback never leak (#255).
|
||||
from core.failure import sanitize, build_failure
|
||||
seen = set()
|
||||
uniq: list[str] = []
|
||||
for msg in chunk_errors:
|
||||
if msg and msg not in seen:
|
||||
seen.add(msg)
|
||||
uniq.append(msg)
|
||||
s = sanitize(msg)
|
||||
if s and s not in seen:
|
||||
seen.add(s)
|
||||
uniq.append(s)
|
||||
if uniq:
|
||||
detail = "Transcription produced no segments. " + " | ".join(uniq[:3])
|
||||
# Add the actionable hint for a recognized failure class
|
||||
# (e.g. pkg_resources missing → install setuptools).
|
||||
hint = build_failure(" ".join(uniq), stage="transcribe", include_diagnostic=False).get("hint")
|
||||
if hint:
|
||||
detail += f" — {hint}"
|
||||
else:
|
||||
detail = (
|
||||
"Transcription produced no segments. The audio may be silent, "
|
||||
|
||||
@@ -164,6 +164,16 @@ async def generate_speech(
|
||||
postprocess_output, layer_penalty_factor, position_temperature,
|
||||
class_temperature, used_seed, effect_preset,
|
||||
)
|
||||
# Invisible AudioSeal provenance watermark on the final audio. Embedding
|
||||
# was previously only wired into the dub pipeline (dub_generate.py), so
|
||||
# plain TTS came out unmarked despite the setting being on. embed_watermark
|
||||
# self-gates on the user's watermark setting + AudioSeal availability and
|
||||
# passes the audio through unchanged on any failure, so it never breaks
|
||||
# generation.
|
||||
from services.watermark import embed_watermark
|
||||
audio_tensor = await loop.run_in_executor(
|
||||
_gpu_pool, embed_watermark, audio_tensor, _model.sampling_rate
|
||||
)
|
||||
gen_time = round(time.time() - start_time, 2)
|
||||
|
||||
audio_id = str(uuid.uuid4())[:8]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
import sys
|
||||
import platform
|
||||
import uuid
|
||||
import psutil
|
||||
import asyncio
|
||||
@@ -15,6 +16,7 @@ import torch
|
||||
import shutil
|
||||
|
||||
from core.config import OUTPUTS_DIR, DATA_DIR, CRASH_LOG_PATH, LOG_PATH, IDLE_TIMEOUT_SECONDS
|
||||
from core.version import APP_VERSION
|
||||
from services.model_manager import get_model_status, get_best_device
|
||||
from services.ffmpeg_utils import find_ffmpeg, run_ffmpeg
|
||||
|
||||
@@ -168,6 +170,7 @@ def system_info():
|
||||
try:
|
||||
_ffmpeg = find_ffmpeg()
|
||||
return {
|
||||
"app_version": APP_VERSION,
|
||||
"data_dir": DATA_DIR,
|
||||
"outputs_dir": OUTPUTS_DIR,
|
||||
"crash_log_path": CRASH_LOG_PATH,
|
||||
@@ -179,6 +182,7 @@ def system_info():
|
||||
"device": get_best_device(),
|
||||
"python": sys.version.split()[0],
|
||||
"platform": sys.platform,
|
||||
"arch": platform.machine(),
|
||||
"ffmpeg_ok": bool(_ffmpeg),
|
||||
"ffmpeg_path": _ffmpeg or "",
|
||||
"proxy_url": os.environ.get("HTTP_PROXY") or os.environ.get("http_proxy") or "",
|
||||
@@ -193,6 +197,7 @@ def system_info():
|
||||
except Exception as e:
|
||||
logger.exception("system_info failed — returning safe defaults")
|
||||
return {
|
||||
"app_version": APP_VERSION,
|
||||
"data_dir": DATA_DIR,
|
||||
"outputs_dir": OUTPUTS_DIR,
|
||||
"crash_log_path": str(CRASH_LOG_PATH),
|
||||
@@ -204,6 +209,7 @@ def system_info():
|
||||
"device": "cpu",
|
||||
"python": sys.version.split()[0],
|
||||
"platform": sys.platform,
|
||||
"arch": platform.machine(),
|
||||
"proxy_url": "",
|
||||
"share_enabled": network_share.get_state().enabled,
|
||||
"share_port": network_share.get_state().share_port,
|
||||
|
||||
@@ -25,6 +25,7 @@ class SystemInfoResponse(BaseModel):
|
||||
"""GET /system/info"""
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
app_version: str = ""
|
||||
data_dir: str
|
||||
outputs_dir: str
|
||||
crash_log_path: str
|
||||
@@ -36,6 +37,7 @@ class SystemInfoResponse(BaseModel):
|
||||
device: str = "cpu"
|
||||
python: str = ""
|
||||
platform: str = ""
|
||||
arch: str = ""
|
||||
error: str | None = None
|
||||
ffmpeg_ok: bool = False
|
||||
ffmpeg_path: str = ""
|
||||
|
||||
@@ -58,11 +58,11 @@ models:
|
||||
size_gb: 0.08
|
||||
platforms: [darwin-arm64]
|
||||
|
||||
- repo_id: "Systran/faster-whisper-large-v3-turbo"
|
||||
- repo_id: "deepdml/faster-whisper-large-v3-turbo-ct2"
|
||||
label: "Whisper large-v3 Turbo (5× faster, 0.8B)"
|
||||
role: ASR
|
||||
size_gb: 1.6
|
||||
note: "Best speed/quality tradeoff. 5× faster than large-v3 with minimal WER loss."
|
||||
note: "Best speed/quality tradeoff. 5× faster than large-v3 with minimal WER loss. Community CTranslate2 conversion (no official Systran/OpenAI turbo repo) — re-verify availability on catalog audits."
|
||||
|
||||
- repo_id: "Systran/faster-distil-whisper-large-v3"
|
||||
label: "Distil-Whisper large-v3 (distilled, fast)"
|
||||
@@ -110,11 +110,11 @@ models:
|
||||
size_gb: 0.12
|
||||
note: "Variable-length processing, sub-200ms latency. Great for CPU/edge. Requires moonshine-onnx."
|
||||
|
||||
- repo_id: "UsefulSensors/moonshine-small"
|
||||
label: "Moonshine small (edge-optimized, 300M, ONNX)"
|
||||
- repo_id: "UsefulSensors/moonshine-tiny"
|
||||
label: "Moonshine tiny (edge-optimized, 27M, ONNX)"
|
||||
role: ASR
|
||||
size_gb: 0.6
|
||||
note: "Higher accuracy than base, still fast. Requires moonshine-onnx."
|
||||
size_gb: 0.05
|
||||
note: "Smallest/fastest Moonshine, sub-200ms latency. Lower accuracy than base. Requires moonshine-onnx."
|
||||
|
||||
# ── Diarisation ───────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@ taxonomy. Each archetype carries an ``instruct`` string (e.g.
|
||||
|
||||
Two tiers (the "hybrid" gallery model):
|
||||
|
||||
* **Featured** — ~24 hand-curated archetypes spanning the seven use-case
|
||||
categories. Pre-rendered preview WAVs are produced by
|
||||
``scripts/render_demos_omnivoice.py``; until a WAV exists the API renders one
|
||||
on demand.
|
||||
* **Featured** — ~51 hand-curated archetypes (24 English across the seven
|
||||
use-case categories + 27 multilingual designed voices in nine more languages).
|
||||
Pre-rendered preview WAVs are produced by ``scripts/render_demos_omnivoice.py``;
|
||||
until a WAV exists the API renders one on demand.
|
||||
* **Generated** — the full combinatorial space of gender × age × pitch ×
|
||||
accent (English) and gender × age × pitch × dialect (Chinese), pruned of
|
||||
physically-implausible combinations (no "child + very low pitch"). This is
|
||||
@@ -308,7 +308,69 @@ def _make_featured():
|
||||
return out
|
||||
|
||||
|
||||
_FEATURED = _make_featured()
|
||||
# ── Featured: multilingual designed voices ────────────────────────────────────
|
||||
# The voice-design *timbre* axes (gender/age/pitch) are language-independent, and
|
||||
# the spoken language of a designed voice is driven by the preview *text*, not by
|
||||
# the instruct — the same neutral instruct renders in any of OmniVoice's 646
|
||||
# languages (the exact ``model.generate(text=…, language=…, instruct=…)`` call
|
||||
# the Generate tab already makes). So we ship a curated set in the major languages
|
||||
# the app already localizes its UI into, giving the gallery more than English +
|
||||
# Chinese out of the box.
|
||||
#
|
||||
# These carry **no accent/dialect token**: accents are English-only and dialects
|
||||
# Chinese-only, so a "spanish accent" token doesn't exist in the taxonomy and
|
||||
# would crash synthesis (the issue-#89 failure mode). Using only the universal
|
||||
# gender/age/pitch axes keeps every instruct inside the validator's vocabulary —
|
||||
# ``test_archetypes.py`` enforces this independently.
|
||||
#
|
||||
# Each ``language`` label must match an entry in ``frontend/src/languages.json``
|
||||
# verbatim, because the string flows straight into ``model.generate(language=…)``
|
||||
# with no normalization. ("Arabic" is intentionally omitted — it is not in that
|
||||
# list.) ``_ML_SAMPLES`` is functional demo/eval text (like ``_ZH_SAMPLE``); the
|
||||
# Japanese/Korean lines are covered by this file's ``test_no_hardcoded_cjk.py``
|
||||
# allowlist entry.
|
||||
_ML_SAMPLES = {
|
||||
"Spanish": "Hola y bienvenido a esta breve demostración de voz. Espero que disfrutes escuchando cómo suena.",
|
||||
"French": "Bonjour et bienvenue dans cette courte démonstration vocale. J'espère que cette voix vous plaira.",
|
||||
"German": "Hallo und willkommen zu dieser kurzen Sprachdemo. Ich hoffe, diese Stimme gefällt dir.",
|
||||
"Italian": "Ciao e benvenuto in questa breve dimostrazione vocale. Spero che questa voce ti piaccia.",
|
||||
"Portuguese": "Olá e bem-vindo a esta breve demonstração de voz. Espero que goste de ouvir como ela soa.",
|
||||
"Russian": "Здравствуйте и добро пожаловать в эту короткую демонстрацию голоса. Надеюсь, вам понравится, как он звучит.",
|
||||
"Hindi": "नमस्ते और इस छोटे से वॉइस डेमो में आपका स्वागत है। मुझे आशा है कि आपको यह आवाज़ पसंद आएगी।",
|
||||
"Japanese": "こんにちは。この短い音声デモへようこそ。この声を気に入っていただけるとうれしいです。",
|
||||
"Korean": "안녕하세요. 이 짧은 음성 데모에 오신 것을 환영합니다. 이 목소리가 마음에 드시길 바랍니다.",
|
||||
}
|
||||
|
||||
# Three reusable, language-independent roles. (gender, age, pitch, use_case,
|
||||
# role, icon) — instruct is built from gender/age/pitch only.
|
||||
_ML_ROLES = [
|
||||
("female", "middle-aged", "low pitch", "narration", "Narrator", "BookOpen"),
|
||||
("male", "young adult", "moderate pitch", "informative", "Explainer", "GraduationCap"),
|
||||
("female", "young adult", "moderate pitch", "conversational", "Companion", "MessagesSquare"),
|
||||
]
|
||||
|
||||
|
||||
def _make_multilingual():
|
||||
"""Build the featured archetypes for the non-EN/ZH languages.
|
||||
|
||||
Cross-products ``_ML_SAMPLES`` (one localized preview script per language)
|
||||
with ``_ML_ROLES`` (the reusable, language-independent timbre roles), so each
|
||||
language gets the same curated set of neutral-instruct designed voices.
|
||||
"""
|
||||
out = []
|
||||
for language, script in _ML_SAMPLES.items():
|
||||
lang_slug = language.lower()
|
||||
for gender, age, pitch, uc, role, icon in _ML_ROLES:
|
||||
out.append(_build(
|
||||
gender, age, pitch,
|
||||
use_case=uc, name=f"{language} {role}", icon=icon,
|
||||
language=language, script=script,
|
||||
featured=True, fid=f"ml_{lang_slug}_{role.lower()}",
|
||||
))
|
||||
return out
|
||||
|
||||
|
||||
_FEATURED = _make_featured() + _make_multilingual()
|
||||
_FEATURED_KEYS = {(a["instruct"], a["language"]) for a in _FEATURED}
|
||||
|
||||
|
||||
|
||||
@@ -12,4 +12,4 @@ from importlib.metadata import PackageNotFoundError, version
|
||||
try:
|
||||
APP_VERSION = version("omnivoice")
|
||||
except PackageNotFoundError: # non-installed source checkout
|
||||
APP_VERSION = "0.3.0"
|
||||
APP_VERSION = "0.3.3"
|
||||
|
||||
+1
-1
@@ -606,7 +606,7 @@ def health():
|
||||
elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
|
||||
device = "mps"
|
||||
|
||||
return {"status": "ok", "device": device}
|
||||
return {"status": "ok", "device": device, "version": APP_VERSION}
|
||||
|
||||
|
||||
app.include_router(system.router)
|
||||
|
||||
@@ -115,13 +115,40 @@ class WhisperXBackend(ASRBackend):
|
||||
# ships a checkpoint with a new pickle class, the load fails loudly
|
||||
# and we extend `_allow_vad_pickle_globals()`.
|
||||
self._allow_vad_pickle_globals()
|
||||
self._asr = whisperx.load_model(
|
||||
self._model_name,
|
||||
device=self._device,
|
||||
compute_type=self._compute_type,
|
||||
# vad_method="silero" is the default; keep it so short gaps
|
||||
# get cleaned up before transcription.
|
||||
)
|
||||
try:
|
||||
self._asr = whisperx.load_model(
|
||||
self._model_name,
|
||||
device=self._device,
|
||||
compute_type=self._compute_type,
|
||||
# vad_method="silero" is the default; keep it so short gaps
|
||||
# get cleaned up before transcription.
|
||||
)
|
||||
except RuntimeError as e:
|
||||
# CUDA OOM: a resident TTS model + the GPU worker pool can starve
|
||||
# VRAM on small (e.g. 8 GB laptop) GPUs, so loading large-v3 on
|
||||
# CUDA dies here — which previously surfaced as a bare 500 from
|
||||
# /dub/transcribe with no guidance. Fall back to CPU (slower, but
|
||||
# dubbing still works and keeps the same model/accuracy) instead.
|
||||
# Only triggers on a CUDA OOM, so the MPS/CPU paths are untouched.
|
||||
if self._device == "cuda" and "out of memory" in str(e).lower():
|
||||
logger.warning(
|
||||
"whisperx CUDA OOM loading %s — retrying on CPU (slower). "
|
||||
"Free VRAM (Flush the TTS model) for GPU-speed ASR. Detail: %s",
|
||||
self._model_name, e,
|
||||
)
|
||||
try:
|
||||
import torch
|
||||
torch.cuda.empty_cache()
|
||||
except Exception: # noqa: BLE001 — cache clear is best-effort
|
||||
pass
|
||||
self._device, self._compute_type = "cpu", "int8"
|
||||
self._asr = whisperx.load_model(
|
||||
self._model_name,
|
||||
device=self._device,
|
||||
compute_type=self._compute_type,
|
||||
)
|
||||
else:
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
def _allow_vad_pickle_globals():
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
"""Unit tests for the archetype-preview quality guard (``api.routers.archetypes``).
|
||||
|
||||
Background: the Hype Host / Podcaster / Vlogger previews shipped a loud tonal
|
||||
*buzz* instead of speech. The renderer pinned ``num_step=16`` + ``seed=42`` and
|
||||
the "social" sample script collapsed to a near-pure tone at that point; the
|
||||
old silence-only guard missed it (the buzz is loud, not silent) so the garbage
|
||||
was cached and served.
|
||||
|
||||
These tests cover the fix *without the 5 GB model / a GPU*: they drive the pure
|
||||
``_spectral_flatness`` / ``_is_unusable_audio`` helpers with synthetic signals,
|
||||
and assert the render constants didn't regress. The real end-to-end render is
|
||||
verified manually (spectral flatness back in the speech range + Whisper ASR).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import types
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
# Stub core.config before the router imports OUTPUTS_DIR / VOICES_DIR from it.
|
||||
_TMP = tempfile.mkdtemp(prefix="omnivoice_preview_q_")
|
||||
_config = types.ModuleType("core.config")
|
||||
_config.DATA_DIR = _TMP
|
||||
_config.VOICES_DIR = str(Path(_TMP) / "voices")
|
||||
_config.OUTPUTS_DIR = str(Path(_TMP) / "outputs")
|
||||
sys.modules["core.config"] = _config
|
||||
|
||||
torch = pytest.importorskip("torch") # noqa: E402
|
||||
|
||||
from api.routers import archetypes as arch # noqa: E402
|
||||
|
||||
SR = 24_000
|
||||
N = SR * 3 # 3 s clips
|
||||
|
||||
|
||||
def _pure_tone(hz: float = 220.0) -> "torch.Tensor":
|
||||
t = torch.arange(N, dtype=torch.float32) / SR
|
||||
return 0.8 * torch.sin(2 * math.pi * hz * t)
|
||||
|
||||
|
||||
def _white_noise() -> "torch.Tensor":
|
||||
g = torch.Generator().manual_seed(0)
|
||||
return 0.5 * (torch.rand(N, generator=g) * 2 - 1)
|
||||
|
||||
|
||||
def _speech_like() -> "torch.Tensor":
|
||||
"""Broadband + harmonic + amplitude-modulated — a coarse stand-in for voiced
|
||||
speech: several harmonics (formant-ish), additive noise (consonants), and a
|
||||
syllabic envelope (word gaps). Flatness lands between a pure tone and noise.
|
||||
"""
|
||||
g = torch.Generator().manual_seed(1)
|
||||
t = torch.arange(N, dtype=torch.float32) / SR
|
||||
harm = sum(torch.sin(2 * math.pi * f * t) / (i + 1)
|
||||
for i, f in enumerate((130.0, 260.0, 390.0, 520.0)))
|
||||
noise = 0.3 * (torch.rand(N, generator=g) * 2 - 1)
|
||||
env = 0.5 + 0.5 * torch.sin(2 * math.pi * 4.0 * t).clamp(min=0) # ~4 Hz syllables
|
||||
sig = (harm + noise) * env
|
||||
return 0.7 * sig / sig.abs().max()
|
||||
|
||||
|
||||
# ── _spectral_flatness ──────────────────────────────────────────────────────
|
||||
def test_flatness_orders_tone_below_speech_below_noise():
|
||||
tone = arch._spectral_flatness(_pure_tone())
|
||||
speech = arch._spectral_flatness(_speech_like())
|
||||
noise = arch._spectral_flatness(_white_noise())
|
||||
assert tone is not None and speech is not None and noise is not None
|
||||
assert tone < arch._DEGENERATE_FLATNESS < speech < noise
|
||||
|
||||
|
||||
def test_flatness_returns_none_on_too_short_or_nonfinite():
|
||||
assert arch._spectral_flatness(torch.zeros(16)) is None
|
||||
bad = torch.full((4096,), float("nan"))
|
||||
assert arch._spectral_flatness(bad) is None
|
||||
|
||||
|
||||
# ── _is_unusable_audio ──────────────────────────────────────────────────────
|
||||
def test_pure_tone_is_unusable():
|
||||
# The degenerate-buzz failure mode: loud (passes the silence guard) but tonal.
|
||||
tone = _pure_tone()
|
||||
assert tone.abs().max() > 0.02 # not silent
|
||||
assert arch._is_unusable_audio(tone) is True
|
||||
|
||||
|
||||
def test_silence_is_unusable():
|
||||
assert arch._is_unusable_audio(torch.zeros(N)) is True
|
||||
|
||||
|
||||
def test_speech_like_is_usable():
|
||||
assert arch._is_unusable_audio(_speech_like()) is False
|
||||
|
||||
|
||||
# ── Constants didn't regress ────────────────────────────────────────────────
|
||||
def test_preview_render_constants():
|
||||
# 16 steps under-converged on the social script; the fix bumped it.
|
||||
assert arch._PREVIEW_NUM_STEP >= 24
|
||||
assert 0 < arch._DEGENERATE_FLATNESS < 0.03
|
||||
@@ -172,6 +172,38 @@ def test_filter_by_accent():
|
||||
assert all("british accent" in a["instruct"] for a in res)
|
||||
|
||||
|
||||
# ── (h2) Multilingual designed voices ship beyond English + Chinese ───────────
|
||||
_ML_LANGS = {
|
||||
"Spanish", "French", "German", "Italian", "Portuguese",
|
||||
"Russian", "Hindi", "Japanese", "Korean",
|
||||
}
|
||||
|
||||
|
||||
def test_multilingual_featured_languages_present():
|
||||
featured_langs = {a["language"] for a in archetypes.list_archetypes(featured=True)}
|
||||
missing = _ML_LANGS - featured_langs
|
||||
assert not missing, f"missing curated multilingual languages: {missing}"
|
||||
|
||||
|
||||
def test_multilingual_archetypes_are_neutral_timbre_with_valid_tokens():
|
||||
# A designed voice's spoken language is the preview text, not the instruct —
|
||||
# so these carry no English-accent / Chinese-dialect token, only the
|
||||
# universal gender/age/pitch axes that exist in every language.
|
||||
for lang in _ML_LANGS:
|
||||
res = archetypes.list_archetypes(lang=lang)
|
||||
assert res, f"no archetypes for language {lang!r}"
|
||||
for a in res:
|
||||
assert a["language"] == lang
|
||||
assert a["sample_script"].strip(), f"{a['id']} missing sample_script"
|
||||
toks = set(_tokens(a["instruct"]))
|
||||
assert toks, f"{a['id']} has empty instruct"
|
||||
assert all(t in _VALID_TOKENS for t in toks), (
|
||||
f"{a['id']} emits invalid token (instruct={a['instruct']!r})"
|
||||
)
|
||||
assert not (toks & _ACCENTS), f"{a['id']} should carry no English accent"
|
||||
assert not (toks & _DIALECTS), f"{a['id']} should carry no Chinese dialect"
|
||||
|
||||
|
||||
# ── (i) Lookup by id ──────────────────────────────────────────────────────────
|
||||
def test_get_archetype_roundtrip():
|
||||
sample = ALL[0]
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
"""WhisperX CUDA-OOM → CPU fallback (api/services parity for small GPUs).
|
||||
|
||||
On an 8 GB laptop GPU with the TTS model resident, whisperx's CTranslate2
|
||||
load of large-v3 dies with `RuntimeError: CUDA failed with error out of
|
||||
memory`, which previously surfaced as a bare 500 from /dub/transcribe. The
|
||||
backend now retries on CPU (slower, same model/accuracy). This test forces the
|
||||
OOM deterministically (no GPU needed) and asserts the device switch.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import types
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
_config = types.ModuleType("core.config")
|
||||
_config.DATA_DIR = tempfile.mkdtemp(prefix="omnivoice_asr_oom_")
|
||||
_config.VOICES_DIR = _config.DATA_DIR
|
||||
_config.OUTPUTS_DIR = _config.DATA_DIR
|
||||
sys.modules["core.config"] = _config
|
||||
|
||||
whisperx = pytest.importorskip("whisperx")
|
||||
|
||||
from services.asr_backend import WhisperXBackend # noqa: E402
|
||||
|
||||
|
||||
def test_cuda_oom_falls_back_to_cpu(monkeypatch):
|
||||
calls = []
|
||||
|
||||
def fake_load_model(name, device, compute_type, **kw):
|
||||
calls.append((device, compute_type))
|
||||
if device == "cuda":
|
||||
raise RuntimeError("CUDA failed with error out of memory")
|
||||
return object() # CPU load succeeds
|
||||
|
||||
monkeypatch.setattr(whisperx, "load_model", fake_load_model)
|
||||
|
||||
be = WhisperXBackend()
|
||||
# Force the CUDA starting point regardless of the CI host's hardware.
|
||||
be._device, be._compute_type = "cuda", "float16"
|
||||
be._allow_vad_pickle_globals = lambda: None # skip torch pickle allowlist
|
||||
|
||||
be._ensure_asr()
|
||||
|
||||
assert be._asr is not None # didn't raise — recovered
|
||||
assert be._device == "cpu" and be._compute_type == "int8"
|
||||
assert [d for d, _ in calls] == ["cuda", "cpu"] # tried CUDA, then CPU
|
||||
|
||||
|
||||
def test_non_oom_runtime_error_still_raises(monkeypatch):
|
||||
def fake_load_model(name, device, compute_type, **kw):
|
||||
raise RuntimeError("some other failure") # not an OOM → must propagate
|
||||
|
||||
monkeypatch.setattr(whisperx, "load_model", fake_load_model)
|
||||
|
||||
be = WhisperXBackend()
|
||||
be._device, be._compute_type = "cuda", "float16"
|
||||
be._allow_vad_pickle_globals = lambda: None
|
||||
with pytest.raises(RuntimeError, match="some other failure"):
|
||||
be._ensure_asr()
|
||||
@@ -54,6 +54,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@playwright/test": "^1.60.0",
|
||||
"@tauri-apps/api": "^2.11.0",
|
||||
"@tauri-apps/cli": "^2.11.0",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
@@ -203,6 +204,8 @@
|
||||
|
||||
"@oxc-project/types": ["@oxc-project/types@0.127.0", "", {}, "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ=="],
|
||||
|
||||
"@playwright/test": ["@playwright/test@1.60.0", "", { "dependencies": { "playwright": "1.60.0" }, "bin": { "playwright": "cli.js" } }, "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag=="],
|
||||
|
||||
"@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="],
|
||||
|
||||
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
|
||||
|
||||
@@ -29,6 +29,13 @@ ENV HF_HOME=/app/omnivoice_data/huggingface
|
||||
# Allow bare imports (from core.config, from services.*, etc.) when
|
||||
# uvicorn is started as `backend.main:app` from WORKDIR /app.
|
||||
ENV PYTHONPATH=/app/backend
|
||||
# Headless server deployment: relax the desktop-only loopback origin gate.
|
||||
# Docker's network NAT rewrites the client host to the bridge gateway, so the
|
||||
# gate would otherwise 403 the operator out of /system/* and /api/settings/*
|
||||
# ("Loopback origin required", issue #261). Exposure is governed by the
|
||||
# operator's `-p` port mapping plus the optional share PIN. Desktop builds
|
||||
# never set this, so their loopback boundary is unchanged.
|
||||
ENV OMNIVOICE_SERVER_MODE=1
|
||||
|
||||
# Install system dependencies (FFmpeg is critical for torchaudio/scene splitting)
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
|
||||
@@ -46,6 +46,12 @@ services:
|
||||
# OMNIVOICE_BIND_HOST=0.0.0.0 here only opens the container's own
|
||||
# interface. The backend default is 127.0.0.1 (see backend/main.py).
|
||||
- OMNIVOICE_BIND_HOST=0.0.0.0
|
||||
# Headless server: relax the desktop-only loopback origin gate so the
|
||||
# web UI's /system/* and /api/settings/* routes work through Docker's
|
||||
# NAT (issue #261). Already baked into the image; shown here so it's
|
||||
# discoverable. If you front the container with your own auth proxy on
|
||||
# loopback, set this to 0 to re-enable the strict gate.
|
||||
- OMNIVOICE_SERVER_MODE=1
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-sf", "http://localhost:3900/health"]
|
||||
interval: 30s
|
||||
@@ -76,6 +82,9 @@ services:
|
||||
# service above. The host-side `127.0.0.1:3900:3900` mapping keeps
|
||||
# LAN reachability off by default.
|
||||
- OMNIVOICE_BIND_HOST=0.0.0.0
|
||||
# See the CPU service above — relaxes the loopback origin gate for the
|
||||
# headless Docker deployment (issue #261). Set to 0 to re-enable it.
|
||||
- OMNIVOICE_SERVER_MODE=1
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-sf", "http://localhost:3900/health"]
|
||||
interval: 30s
|
||||
|
||||
@@ -4,6 +4,18 @@ For headless servers, dedicated GPUs, or "I want one command" deployments.
|
||||
The docker image bundles the backend; the UI is served over HTTP and you open
|
||||
it in a normal browser.
|
||||
|
||||
> **Image ↔ version mapping**
|
||||
>
|
||||
> | Tag | What you get |
|
||||
> |-----|--------------|
|
||||
> | `:latest` | Most recent versioned release (updated on every `v*` git tag) |
|
||||
> | `:0.3.0` | Exact release version |
|
||||
> | `:0.3` | Latest patch within the 0.3 minor |
|
||||
> | `:main` | Latest commit on `main` — may be ahead of the last release |
|
||||
> | `:sha-xxxxxxx` | Specific commit (produced by manual workflow dispatch) |
|
||||
>
|
||||
> **Note on the update-channel toggle:** The update-channel UI (Settings → About → Update channel) is part of the Tauri desktop app's built-in auto-updater. It does **not** apply to the Docker image — the Docker image is the headless web-server build. To update your Docker deployment, pull the new image tag and recreate the container (`docker compose pull && docker compose up -d`).
|
||||
|
||||
## Pull and run (CPU)
|
||||
|
||||
```bash
|
||||
@@ -100,6 +112,21 @@ Two paths are worth persisting across container restarts:
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **Container reports 0.2.7 but image is tagged 0.3.x:** This was a workflow bug
|
||||
(fixes #249, #251) — the `:latest` tag was not being updated on release tag
|
||||
pushes. Pull the image again after the fix is merged: `docker pull ghcr.io/debpalash/omnivoice-studio:latest`.
|
||||
The running version is now shown in **Settings → About → Version** (read live
|
||||
from the backend), so the web UI no longer displays a dash in Docker.
|
||||
- **Checking which version is running:** `docker exec omnivoice python -c "import importlib.metadata; print(importlib.metadata.version('omnivoice'))"`, or hit the `/health` endpoint — it returns `{"status": "ok", "device": ..., "version": "0.3.x"}`.
|
||||
- **"Loopback origin required" errors (and a blank version):** the desktop
|
||||
build restricts the `/system/*` and `/api/settings/*` routes to a loopback
|
||||
origin, but Docker's NAT makes every request look non-loopback, so the gate
|
||||
used to 403 the whole admin UI (issue #261). The image now ships with
|
||||
`OMNIVOICE_SERVER_MODE=1`, which relaxes that gate for the headless
|
||||
deployment — exposure is instead governed by your `-p` port mapping (keep the
|
||||
`127.0.0.1:` prefix to stay local) plus the optional share PIN. If you front
|
||||
the container with your own auth proxy on loopback, set `OMNIVOICE_SERVER_MODE=0`
|
||||
to re-enable the strict gate.
|
||||
- **Media-preview 404 in LAN mode:** see the [LAN access](#lan-access) section
|
||||
above — the `window.location.host` fix shipped in v0.3.
|
||||
- **GPU not detected:** verify `docker run --rm --gpus all nvidia/cuda:12.8.0-base-ubuntu22.04 nvidia-smi` succeeds first.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,115 @@
|
||||
# Preview channel: versioning + rollback — design spec
|
||||
|
||||
- **Date:** 2026-06-01
|
||||
- **Status:** Phase A implemented; Phase B = proposed design, pending review
|
||||
- **Ships on:** v0.3.0 line
|
||||
|
||||
## Problem
|
||||
|
||||
The Preview update channel (Settings → About → Update channel → Preview) builds
|
||||
from `main` and publishes to a rolling `preview` GitHub prerelease. But two gaps
|
||||
make it not actually work as an update channel:
|
||||
|
||||
1. **No versioning.** Every preview build stamped the static `tauri.conf.json`
|
||||
version (`0.3.0`). Tauri's updater only offers an update when the manifest
|
||||
version is **semver-greater** than the installed one, so `0.3.0 == 0.3.0` →
|
||||
"no update." Preview users install once and never receive the next preview.
|
||||
2. **No rollback.** The Tauri updater only moves forward. There is no way to
|
||||
return to an earlier preview build (e.g., when a fresh `main` build
|
||||
regresses) without a manual reinstall.
|
||||
|
||||
## Phase A — forward versioning (DONE)
|
||||
|
||||
`release.yml` stamps each preview build, on the `workflow_dispatch +
|
||||
publish_preview` path only, with a unique monotonic semver prerelease:
|
||||
|
||||
```
|
||||
<base>-preview.<github.run_number> e.g. 0.3.0-preview.42
|
||||
```
|
||||
|
||||
via an ephemeral, never-committed rewrite of `tauri.conf.json`'s `version`
|
||||
(Tauri reads the bundle + updater version from there). Properties:
|
||||
|
||||
- **Monotonic** (`run_number` only increases) → `…preview.43 > …preview.42`, so
|
||||
the updater offers each newer preview.
|
||||
- **Prerelease of the current target** → when stable `0.3.0` ships,
|
||||
`0.3.0 > 0.3.0-preview.N`, so preview users **converge to stable** (matches
|
||||
the channel's preview→stable fallback).
|
||||
- **No `+build` metadata** — kept out to avoid `+`-in-filename / MSI edge cases.
|
||||
Commit traceability lives in the release notes (the `preview-notes` job
|
||||
already renders the commit range + Contributors).
|
||||
|
||||
### Known caveat (Windows MSI)
|
||||
|
||||
The Windows MSI `ProductVersion` is a 4-field numeric (`a.b.c.d`) and **strips
|
||||
the semver prerelease** → every preview MSI reports `0.3.0`. The Tauri updater
|
||||
compares the **full** semver from `latest.json` (so it still *offers* the new
|
||||
preview and runs the new MSI), but `msiexec` installing an MSI whose
|
||||
`ProductVersion` is unchanged is a "reinstall," not an "upgrade." **Action:**
|
||||
verify Windows preview→preview actually replaces files in testing. macOS/Linux
|
||||
replace the bundle wholesale and are unaffected. If Windows misbehaves, the
|
||||
fallback is a numeric scheme (`0.3.<run_number>`) at the cost of clean
|
||||
convergence — decide after a real Windows test.
|
||||
|
||||
## Phase B — version catalog + rollback (PROPOSED)
|
||||
|
||||
### Publish model: per-version prereleases
|
||||
|
||||
Each preview build publishes a **distinct** prerelease tagged
|
||||
`preview-<version>` (e.g. `preview-0.3.0-preview.42`), self-contained: signed
|
||||
artifacts + its own `latest.json`. Separately, the rolling `preview` tag's
|
||||
`latest.json` **mirrors the newest** so the default forward-update keeps reading
|
||||
a stable URL (`releases/download/preview/latest.json`).
|
||||
|
||||
- **Retention:** keep the last ~10 `preview-*` releases; a cleanup step prunes
|
||||
older releases + tags. These releases *are* the rollback catalog — so unlike
|
||||
Phase A's tidy-up instinct, we deliberately **keep** old artifacts.
|
||||
|
||||
### App side: a "Preview builds" picker
|
||||
|
||||
In Settings → About → Update channel (shown when on Preview):
|
||||
|
||||
- List available builds from the GitHub Releases API (prereleases matching
|
||||
`preview-*`): version, date, commit range, and an **alembic-head** marker
|
||||
(see Data safety).
|
||||
- Each row → **Install**. Choosing an *older* build is the rollback.
|
||||
- **Install path** reuses the Rust updater commands from #199, extended with:
|
||||
- an explicit endpoint (`…/preview-<chosen>/latest.json`), and
|
||||
- **`allow_downgrades`** (Tauri `UpdaterBuilder::version_comparator`, e.g.
|
||||
`|current, candidate| candidate != current`) so it installs even when the
|
||||
target is older than the running version.
|
||||
- Every build is minisign-signed → rollback installs are verified too.
|
||||
|
||||
### ⚠️ Data safety: DB schema + rollback
|
||||
|
||||
Alembic migrations are forward-only and tested for **upgrade**. Rolling the app
|
||||
back across a migration means an *older* app meets a `omnivoice_data` DB at a
|
||||
**newer** schema head than it expects — which can break (the inverse of the
|
||||
"backward-compatible data" constraint). Mitigations, in order of effort:
|
||||
|
||||
1. **Tag each preview with its alembic head** (a build-time `alembic heads`
|
||||
captured into the release notes / a sidecar field). The picker marks builds
|
||||
as "safe to roll back to" (same head) vs "data-incompatible (newer schema)."
|
||||
2. **Warn on cross-schema rollback** in the picker; require explicit confirm.
|
||||
3. (Later) implement + test alembic **downgrade** paths for the affected
|
||||
revisions so rollback is truly safe.
|
||||
|
||||
Phase B should at least do (1)+(2); (3) is per-migration follow-up work.
|
||||
|
||||
## Implementation outline (Phase B)
|
||||
|
||||
- `release.yml`: per-version `preview-<version>` publish + mirror newest →
|
||||
rolling `preview/latest.json`; retention/prune step; capture alembic head.
|
||||
- `backend`: small endpoint to expose the running alembic head (for the picker's
|
||||
safety check), or read it client-side from the release metadata.
|
||||
- `frontend/src-tauri` (`updater_channel.rs`): `install_specific(version)` with
|
||||
endpoint override + `allow_downgrades`; `list_preview_builds()` via GH API.
|
||||
- `frontend` (Settings): the "Preview builds" picker + rollback confirm dialog;
|
||||
i18n (en + zh-CN, then backfill the rest).
|
||||
|
||||
## Open decisions
|
||||
|
||||
1. Base scheme: **`0.3.0-preview.N`** (chosen) vs `0.3.1-preview.N`.
|
||||
2. Retention count (proposed **10**).
|
||||
3. Whether to gate rollback across alembic heads behind a hard block or a
|
||||
confirm-with-warning (proposed: confirm-with-warning + a clear marker).
|
||||
@@ -0,0 +1,164 @@
|
||||
# Updates in the status bar — design spec
|
||||
|
||||
**Date:** 2026-06-02
|
||||
**Status:** Approved (brainstorm) → ready for implementation plan
|
||||
**Branch:** `feat/updates-status-bar`
|
||||
|
||||
## Problem / goal
|
||||
|
||||
The auto-update surface is a floating pill (`UpdateBadge`) fixed at top-right. The
|
||||
user wants updates to live in the **bottom status bar** (`LogsFooter`): a persistent
|
||||
version indicator plus an **Updates tab/panel** that exposes the changelog, the
|
||||
stable/preview channel switcher, and update/release history — so updates have a
|
||||
permanent, discoverable home instead of a transient floating pill.
|
||||
|
||||
## Decisions (from brainstorm)
|
||||
|
||||
1. **Placement:** a persistent **version chip** on the right of the always-visible
|
||||
28px `LogsFooter` bar, plus an **"Updates" tab** that expands the footer into an
|
||||
**Updates panel**. Clicking the chip opens the panel.
|
||||
2. **Idle behavior:** the chip is **always on** — shows `v<current> ✓` when up to
|
||||
date and morphs into available / downloading / ready / error states.
|
||||
3. **Data source:** **GitHub Releases (live)**, fetched through a Rust command.
|
||||
Changelog and "history" **unify into one Releases list** (release history with
|
||||
the running version marked "current"). This is *not* a personal install log.
|
||||
4. **Channel switcher:** the panel gets an editable stable/preview switcher that
|
||||
**coexists with the existing Settings → About switcher**; both read/write the
|
||||
same Rust `get_update_channel` / `set_update_channel`, kept in sync via a single
|
||||
store-held `updateChannel` value.
|
||||
|
||||
## Existing code this builds on
|
||||
|
||||
- `frontend/src/components/LogsFooter.jsx` / `.css` — the bottom bar. Fixed,
|
||||
`z-index: 40`, 28px collapsed, expands 180–720px. Tabs come from a `SOURCES`
|
||||
array rendered as `.logs-footer__pill` buttons; active tab persists to
|
||||
localStorage `omnivoice.logs.active`. Body renders per active tab; the
|
||||
Notifications tab is the precedent for a non-log custom tab body. Sets CSS var
|
||||
`--logs-footer-height`.
|
||||
- `frontend/src/components/UpdateBadge.jsx` / `.css` — current floating pill,
|
||||
mounted at `App.jsx` (`<UpdateBadge/>`). Renders available / downloading / ready
|
||||
/ error (idle+checking → null). **Current `main` adds a dismiss (X) button on the
|
||||
error state + a `dismissUpdate` store action + `update.dismiss` key** — must be
|
||||
preserved.
|
||||
- `frontend/src/store/updaterSlice.ts` — `updateStatus | updateVersion |
|
||||
updateNotes | updateProgress | updateError`; setters
|
||||
`setUpdateChecking/Available/Idle/Progress/Ready/Error` + `dismissUpdate`. **Not
|
||||
persisted.** No history field.
|
||||
- `frontend/src/utils/updater.js` — `isTauri()`, `currentChannel()`
|
||||
(`invoke('get_update_channel')`), `checkForUpdate(store)`
|
||||
(`invoke('check_update',{channel})`), `installUpdate(store)`
|
||||
(`invoke('install_update',{channel})`, listens `update://progress`, `relaunch()`).
|
||||
- `frontend/src/utils/updateChannel.js` — `UPDATE_CHANNELS = ['stable','preview']`,
|
||||
`normalizeChannel()`.
|
||||
- `frontend/src/pages/Settings.jsx` (About tab) — existing channel `Segmented`
|
||||
(`changeChannel` → `set_update_channel`), endpoint display, "Check for updates".
|
||||
- Rust (`src-tauri/src`) — commands `get_update_channel`, `set_update_channel`,
|
||||
`check_update`, `install_update`. **New:** `list_releases`.
|
||||
- `CHANGELOG.md` exists at repo root but is not surfaced (not used by this design;
|
||||
release notes come from GitHub).
|
||||
|
||||
## Architecture
|
||||
|
||||
### A. Bar chip — `UpdateStatusChip`
|
||||
New component rendered on the **right** side of `LogsFooter`'s top bar (near the
|
||||
Discord/donate cluster). Subscribes to `updaterSlice`. State → presentation:
|
||||
|
||||
| `updateStatus` | chip |
|
||||
|---|---|
|
||||
| `idle` (up to date) | `v<current> ✓` (subtle/dim) |
|
||||
| `available` | `⬆ <version> · Update` (click row installs in panel) |
|
||||
| `downloading` | `↺ Updating <pct>%` |
|
||||
| `ready` | `↺ Restart` |
|
||||
| `error` | `⚠ Failed · Retry` (+ dismiss preserved) |
|
||||
| `checking` | brief `… Checking` (or stay on prior chip) |
|
||||
|
||||
- Click → open the footer to the Updates tab (`openTo('updates')`).
|
||||
- **Current version source:** `@tauri-apps/api/app` `getVersion()` on mount, stored
|
||||
in `updaterSlice.appVersion` (fallback: hidden chip in non-Tauri/dev where version
|
||||
is unknown). The chip is the *indicator only*; primary actions live in the panel
|
||||
(Install is reachable from chip via opening panel; Retry/Restart may act inline to
|
||||
preserve today's one-click behavior — see Open Questions resolved below).
|
||||
|
||||
### B. Updates panel — `UpdatesPanel`
|
||||
Rendered as the footer body when `active === 'updates'`. Sections top→bottom:
|
||||
|
||||
1. **Live row** — mirrors chip state with the actionable control:
|
||||
- available → `Update available · <v>` + **Install** (gated while `dubStep ===
|
||||
'generating'`, same toast as today).
|
||||
- downloading → progress bar + %; ready → **Restart**; error → message +
|
||||
**Retry** + **Dismiss**; idle → `Up to date · v<current>` + **Check now**.
|
||||
2. **Channel** — `Segmented` stable/preview bound to store `updateChannel`; onChange
|
||||
→ `set_update_channel` + refetch releases. Stays in sync with Settings.
|
||||
3. **Releases list** — scrollable; each row: version, date, `prerelease` tag,
|
||||
expandable notes. The **running version** is marked `current`. Loading and
|
||||
empty/offline states handled (see Data).
|
||||
|
||||
### C. Data — `list_releases` Rust command + `releasesSlice`
|
||||
- **Rust** `list_releases(channel) -> Vec<ReleaseInfo>`: GET
|
||||
`https://api.github.com/repos/{owner}/{repo}/releases` via existing `reqwest`
|
||||
(no auth; `User-Agent` set). Map to `{ version, name, date, prerelease, notes }`.
|
||||
**Stable** channel filters out `prerelease`; **preview** includes them. Sorted
|
||||
newest first. Short in-memory cache (e.g. 5 min) to avoid refetch spam.
|
||||
- **Frontend** transient `releasesSlice` (NOT persisted): `releases`,
|
||||
`releasesStatus: 'idle'|'loading'|'loaded'|'error'`, `loadReleases(channel)`
|
||||
(calls the command; sets error on failure). Fetched lazily when the panel first
|
||||
opens and on channel change.
|
||||
- **Offline / failure:** panel shows "Couldn't load releases (offline?)" + a retry
|
||||
button. The live update flow (`check_update`/`install_update`) and the rest of the
|
||||
app are unaffected. Non-Tauri/dev → releases unavailable, panel shows the same
|
||||
empty state; live update no-ops as today.
|
||||
|
||||
### D. Removals / moves
|
||||
- Remove floating `<UpdateBadge/>` from `App.jsx`.
|
||||
- `UpdateBadge`'s state logic moves into `UpdateStatusChip` (incl. dismiss + dub-busy
|
||||
gating). `UpdateBadge.jsx`/`.css` retired (or renamed to the chip). The existing
|
||||
`update.*` i18n keys are reused.
|
||||
|
||||
## State / store
|
||||
|
||||
- `updaterSlice`: add `appVersion: string | null` + `setAppVersion`. Everything else
|
||||
unchanged (still not persisted).
|
||||
- New `updateChannelSlice` (or fold into updaterSlice): `updateChannel:
|
||||
'stable'|'preview'`, `setUpdateChannel(ch)` (writes via Rust). Settings + panel both
|
||||
bind here → single source of truth, auto-synced.
|
||||
- New `releasesSlice` (transient): releases + status + `loadReleases`.
|
||||
|
||||
## i18n (hard rule)
|
||||
|
||||
New `updates.*` keys added to **all 21 locale files** (parity enforced; CJK guard
|
||||
stays green — no hardcoded user-facing strings). Reuse `update.*` and
|
||||
`about.channel_*` where possible. New keys (indicative): `updates.tab`,
|
||||
`updates.up_to_date`, `updates.check_now`, `updates.current`, `updates.releases`,
|
||||
`updates.prerelease`, `updates.load_error`, `updates.retry_load`,
|
||||
`updates.installed_version`.
|
||||
|
||||
## Cross-platform parity (strict rule)
|
||||
|
||||
The chip + panel are pure frontend; `list_releases` is platform-agnostic Rust. The
|
||||
feature is **default-on** and behaves identically on macOS / Windows / Linux. No
|
||||
OS-gated default behavior. Updater itself remains Tauri-only (no-ops in dev/web) —
|
||||
the chip degrades to hidden/version-only there, identically across platforms.
|
||||
|
||||
## Testing
|
||||
|
||||
- vitest: chip state→presentation mapping for all six states; panel render with a
|
||||
mock releases array (current-version marking, prerelease filtering by channel);
|
||||
channel switch calls `set_update_channel` + triggers reload; releases load-error →
|
||||
offline empty state; dub-busy gating on Install.
|
||||
- Keep the existing `updater.test.js` (#216 guard) + `updaterSlice.test.ts` green.
|
||||
- i18n parity test + CJK guard must pass.
|
||||
|
||||
## Out of scope (YAGNI)
|
||||
|
||||
- Personal install-history log ("you installed X on date Y").
|
||||
- Per-release manual download / rollback / downgrade.
|
||||
- Rich markdown rendering of notes beyond the current plain/`pre-wrap` treatment.
|
||||
- Auto-refresh/polling of the releases list (fetch on open + channel change only).
|
||||
|
||||
## Open questions — resolved
|
||||
|
||||
- **Chip vs panel for one-click actions:** chip shows state; Restart/Retry remain
|
||||
one-click from the chip (preserve today's behavior); Install opens the panel (it's
|
||||
the consequential action and benefits from showing notes first).
|
||||
- **Changelog vs history:** unified into the Releases list (GitHub source decision).
|
||||
- **Version chip when version unknown (dev/web):** hidden.
|
||||
@@ -0,0 +1,49 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
/** Every routable view (the `mode` values in App.jsx). */
|
||||
export const MODES = [
|
||||
'launchpad', 'clone', 'design', 'gallery', 'dub', 'stories',
|
||||
'projects', 'queue', 'tools', 'transcriptions', 'settings', 'donate',
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Fatal client errors that mean a view failed to LOAD — code-split chunk /
|
||||
* dynamic-import failures (the "Use design → Importing a module script failed"
|
||||
* regression) and uncaught exceptions. Deliberately NOT matching network/API
|
||||
* noise (5xx, fetch failures) — backend health is covered elsewhere, and a
|
||||
* flaky API shouldn't fail a UI-mount test.
|
||||
*/
|
||||
const FATAL = [
|
||||
/Importing a module script failed/i,
|
||||
/Failed to fetch dynamically imported module/i,
|
||||
/error loading dynamically imported module/i,
|
||||
/ChunkLoadError/i,
|
||||
];
|
||||
|
||||
export type ErrorSink = { fatal: string[]; all: string[] };
|
||||
|
||||
/** Attach console/pageerror listeners; returns a sink you assert on later. */
|
||||
export function collectErrors(page: Page): ErrorSink {
|
||||
const sink: ErrorSink = { fatal: [], all: [] };
|
||||
const record = (text: string) => {
|
||||
sink.all.push(text);
|
||||
if (FATAL.some((re) => re.test(text))) sink.fatal.push(text);
|
||||
};
|
||||
page.on('pageerror', (err) => record(`pageerror: ${err.message}`));
|
||||
page.on('console', (msg) => {
|
||||
if (msg.type() === 'error') record(`console.error: ${msg.text()}`);
|
||||
});
|
||||
return sink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Land directly on a view by seeding the zustand-persist store (key
|
||||
* `omnivoice.app`) before the app boots. A shallow merge over slice defaults,
|
||||
* so only `mode` is forced.
|
||||
*/
|
||||
export async function gotoMode(page: Page, mode: string): Promise<void> {
|
||||
await page.addInitScript((m) => {
|
||||
localStorage.setItem('omnivoice.app', JSON.stringify({ state: { mode: m }, version: 4 }));
|
||||
}, mode);
|
||||
await page.goto('/');
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { collectErrors, gotoMode } from './_helpers';
|
||||
|
||||
test.describe('OmniVoice Gallery', () => {
|
||||
test('heading is "OmniVoice Gallery"', async ({ page }) => {
|
||||
await gotoMode(page, 'gallery');
|
||||
await expect(page.getByRole('heading', { name: /OmniVoice Gallery/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('facet dropdowns use the dark theme, not the OS-default light surface', async ({ page }) => {
|
||||
await gotoMode(page, 'gallery');
|
||||
const select = page.locator('select.facet-select').first();
|
||||
await expect(select).toBeVisible();
|
||||
// Regression guard for the undefined-var fallback: the fixed style resolves
|
||||
// --chrome-hover-bg → rgba(255,255,255,0.04), NOT an opaque UA light surface
|
||||
// and NOT transparent (rgba(0,0,0,0), the broken undefined-var state).
|
||||
const bg = await select.evaluate((el) => getComputedStyle(el).backgroundColor);
|
||||
expect(bg).toBe('rgba(255, 255, 255, 0.04)');
|
||||
});
|
||||
|
||||
test('opening an archetype in the Designer mounts the design view (no chunk-load failure)', async ({ page }) => {
|
||||
const errors = collectErrors(page);
|
||||
await gotoMode(page, 'gallery');
|
||||
|
||||
// Cards load from the backend; wait for the first one.
|
||||
const designerBtn = page.locator('.archetype-card .designer-btn').first();
|
||||
await expect(designerBtn).toBeVisible({ timeout: 20_000 });
|
||||
await designerBtn.click();
|
||||
|
||||
// The design view (CloneDesignTab — the lazy chunk that failed when Vite
|
||||
// was down) must mount. Its prompt/personality UI is the tell.
|
||||
await expect(
|
||||
page.getByText(/personality|prompt|steps/i).first()
|
||||
).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
await expect(page.getByText(/this tab hit a snag/i)).toHaveCount(0);
|
||||
expect(errors.fatal, errors.fatal.join('\n')).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { MODES, collectErrors, gotoMode } from './_helpers';
|
||||
|
||||
// Every view must mount without a code-split/import failure or an uncaught
|
||||
// exception, and without tripping the ErrorBoundary fallback. This is the
|
||||
// regression guard for "Use design → Importing a module script failed" (a dead
|
||||
// Vite/module server) and any lazy() page that fails to load.
|
||||
for (const mode of MODES) {
|
||||
test(`view "${mode}" mounts without fatal client errors`, async ({ page }) => {
|
||||
const errors = collectErrors(page);
|
||||
await gotoMode(page, mode);
|
||||
|
||||
// Give the lazy chunk time to fetch + the Suspense boundary to resolve.
|
||||
// (No networkidle wait — views with a live WS/SSE log stream, e.g. Settings,
|
||||
// never reach it.)
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// The ErrorBoundary fallback copy ("This tab hit a snag.") must not show.
|
||||
const snag = page.getByText(/this tab hit a snag/i);
|
||||
await expect(snag).toHaveCount(0);
|
||||
|
||||
expect(errors.fatal, `fatal errors in "${mode}":\n${errors.fatal.join('\n')}`).toEqual([]);
|
||||
});
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "omnivoice-studio",
|
||||
"private": true,
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -14,7 +14,8 @@
|
||||
"test:watch": "vitest",
|
||||
"test:legacy": "node --experimental-strip-types --no-warnings --test ../tests/frontend/*.test.mjs",
|
||||
"preview": "vite preview",
|
||||
"tauri": "tauri"
|
||||
"tauri": "tauri",
|
||||
"e2e": "playwright test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource-variable/inter": "^5.2.8",
|
||||
@@ -54,6 +55,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@playwright/test": "^1.60.0",
|
||||
"@tauri-apps/api": "^2.11.0",
|
||||
"@tauri-apps/cli": "^2.11.0",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
// E2E runs against the Vite dev server (UI on :3901, backend on :3900). It
|
||||
// drives the SYSTEM chromium (no `playwright install` browser download) — set
|
||||
// PLAYWRIGHT_CHROMIUM to override the path. reuseExistingServer keeps a dev
|
||||
// session you already have running; CI starts its own `bun run dev`.
|
||||
const PORT = Number(process.env.E2E_PORT || 3901);
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './e2e',
|
||||
timeout: 45_000,
|
||||
expect: { timeout: 10_000 },
|
||||
fullyParallel: false,
|
||||
retries: process.env.CI ? 1 : 0,
|
||||
reporter: [['list']],
|
||||
use: {
|
||||
baseURL: `http://localhost:${PORT}`,
|
||||
headless: true,
|
||||
trace: 'retain-on-failure',
|
||||
launchOptions: {
|
||||
executablePath: process.env.PLAYWRIGHT_CHROMIUM || '/usr/bin/chromium',
|
||||
},
|
||||
},
|
||||
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
|
||||
webServer: {
|
||||
command: 'bun run dev',
|
||||
url: `http://localhost:${PORT}`,
|
||||
reuseExistingServer: true,
|
||||
timeout: 60_000,
|
||||
},
|
||||
});
|
||||
Generated
+248
-13
@@ -256,6 +256,28 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-rs"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00"
|
||||
dependencies = [
|
||||
"aws-lc-sys",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-sys"
|
||||
version = "0.41.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cmake",
|
||||
"dunce",
|
||||
"fs_extra",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.7"
|
||||
@@ -536,6 +558,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
@@ -590,6 +614,15 @@ dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.7"
|
||||
@@ -619,6 +652,16 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.10.1"
|
||||
@@ -642,7 +685,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.1",
|
||||
"core-graphics-types",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
@@ -655,7 +698,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.1",
|
||||
"core-graphics-types",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
@@ -668,7 +711,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.1",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -1025,6 +1068,15 @@ version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "endi"
|
||||
version = "1.1.1"
|
||||
@@ -1037,7 +1089,7 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cf6f550bbbdd5fe66f39d429cb2604bcdacbf00dca0f5bbe2e9306a0009b7c6"
|
||||
dependencies = [
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.1",
|
||||
"core-graphics 0.24.0",
|
||||
"foreign-types-shared",
|
||||
"libc",
|
||||
@@ -1245,6 +1297,12 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
@@ -1461,8 +1519,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1472,9 +1532,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"r-efi 5.3.0",
|
||||
"wasip2",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1656,6 +1718,25 @@ dependencies = [
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http",
|
||||
"indexmap 2.14.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
@@ -1769,6 +1850,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
@@ -1812,9 +1894,11 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"windows-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2149,6 +2233,16 @@ dependencies = [
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.97"
|
||||
@@ -2291,6 +2385,12 @@ dependencies = [
|
||||
"value-bag",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lru-slab"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
||||
|
||||
[[package]]
|
||||
name = "markup5ever"
|
||||
version = "0.38.0"
|
||||
@@ -2778,12 +2878,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "omnivoice-studio"
|
||||
version = "0.3.0"
|
||||
version = "0.3.3"
|
||||
dependencies = [
|
||||
"dirs-next",
|
||||
"enigo",
|
||||
"libc",
|
||||
"log",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sysinfo",
|
||||
@@ -2961,7 +3062,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
|
||||
dependencies = [
|
||||
"phf_shared 0.11.3",
|
||||
"rand",
|
||||
"rand 0.8.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3237,6 +3338,62 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn"
|
||||
version = "0.11.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"cfg_aliases",
|
||||
"pin-project-lite",
|
||||
"quinn-proto",
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn-proto"
|
||||
version = "0.11.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"bytes",
|
||||
"getrandom 0.3.4",
|
||||
"lru-slab",
|
||||
"rand 0.9.4",
|
||||
"ring",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"slab",
|
||||
"thiserror 2.0.18",
|
||||
"tinyvec",
|
||||
"tracing",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn-udp"
|
||||
version = "0.5.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
|
||||
dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
@@ -3271,8 +3428,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
|
||||
dependencies = [
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3282,7 +3449,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3294,6 +3471,15 @@ dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-window-handle"
|
||||
version = "0.6.2"
|
||||
@@ -3406,8 +3592,10 @@ checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
@@ -3416,8 +3604,10 @@ dependencies = [
|
||||
"hyper-util",
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"rustls-platform-verifier",
|
||||
@@ -3514,7 +3704,7 @@ dependencies = [
|
||||
"borsh",
|
||||
"bytes",
|
||||
"num-traits",
|
||||
"rand",
|
||||
"rand 0.8.6",
|
||||
"rkyv",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -3555,6 +3745,7 @@ version = "0.23.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"log",
|
||||
"once_cell",
|
||||
"ring",
|
||||
@@ -3582,6 +3773,7 @@ version = "1.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
|
||||
dependencies = [
|
||||
"web-time",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -3591,7 +3783,7 @@ version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0"
|
||||
dependencies = [
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.1",
|
||||
"core-foundation-sys",
|
||||
"jni 0.22.4",
|
||||
"log",
|
||||
@@ -3618,6 +3810,7 @@ version = "0.103.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
@@ -3717,7 +3910,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.1",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
@@ -4152,6 +4345,27 @@ dependencies = [
|
||||
"windows 0.57.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"core-foundation 0.9.4",
|
||||
"system-configuration-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration-sys"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-deps"
|
||||
version = "6.2.2"
|
||||
@@ -4173,7 +4387,7 @@ checksum = "1cf65722394c2ac443e80120064987f8914ee1d4e4e36e63cdf10f2990f01159"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"block2 0.6.2",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.1",
|
||||
"core-graphics 0.25.0",
|
||||
"crossbeam-channel",
|
||||
"dbus",
|
||||
@@ -5378,6 +5592,16 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-time"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web_atoms"
|
||||
version = "0.2.4"
|
||||
@@ -5735,6 +5959,17 @@ dependencies = [
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
|
||||
dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
"windows-result 0.4.1",
|
||||
"windows-strings 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.1.2"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "omnivoice-studio"
|
||||
version = "0.3.0"
|
||||
version = "0.3.3"
|
||||
description = "OmniVoice Studio – AI voice cloning & dubbing desktop app"
|
||||
authors = ["Debpalash"]
|
||||
license = "AGPL-3.0"
|
||||
@@ -38,6 +38,8 @@ enigo = { version = "0.3", features = ["serde"] }
|
||||
# bundled pyproject.toml — which installs torch, whisperx, etc.
|
||||
# ureq is used for HTTP health checks and ffmpeg downloads.
|
||||
ureq = "2"
|
||||
# reqwest is used for GitHub Releases API calls (list_releases command).
|
||||
reqwest = { version = "0.13", features = ["json"] }
|
||||
|
||||
# ── Rust IPC commands (cross-platform) ──
|
||||
# get_sysinfo: CPU + RAM metrics without HTTP round-trip
|
||||
|
||||
@@ -369,7 +369,27 @@ pub fn ensure_venv_ready<R: tauri::Runtime>(app: &tauri::AppHandle<R>, progress:
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status();
|
||||
if matches!(uvicorn_check, Ok(ref s) if s.success()) {
|
||||
// #248: also verify pkg_resources is importable. Venvs created before the
|
||||
// setuptools<80 pin (commit 675cc20, fixes #224) have setuptools 80+, which
|
||||
// dropped the bundled pkg_resources. whisperx / ctranslate2 import it at
|
||||
// runtime, so dubbing/transcription crashes silently on those installs even
|
||||
// though uvicorn starts fine. We detect this here so we can force a repair
|
||||
// sync rather than handing back a broken venv.
|
||||
let pkg_resources_ok = if matches!(uvicorn_check, Ok(ref s) if s.success()) {
|
||||
let mut pr_check = Command::new(&venv_py);
|
||||
scrub_python_env(&mut pr_check);
|
||||
matches!(
|
||||
pr_check
|
||||
.args(["-c", "import pkg_resources"])
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status(),
|
||||
Ok(ref s) if s.success()
|
||||
)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if matches!(uvicorn_check, Ok(ref s) if s.success()) && pkg_resources_ok {
|
||||
// Always sync source dirs from bundle so code fixes land on
|
||||
// existing installs without requiring a full clean+reinstall.
|
||||
let resource_dir = app.path().resource_dir().ok();
|
||||
@@ -401,10 +421,21 @@ pub fn ensure_venv_ready<R: tauri::Runtime>(app: &tauri::AppHandle<R>, progress:
|
||||
}
|
||||
return Some((venv_py, backend_dir));
|
||||
}
|
||||
log::warn!(
|
||||
"Venv exists at {} but uvicorn is not importable — re-running uv sync",
|
||||
venv_dir.display()
|
||||
);
|
||||
if matches!(uvicorn_check, Ok(ref s) if s.success()) {
|
||||
// uvicorn is fine but pkg_resources is missing (#248): setuptools>=80 was
|
||||
// installed before the <80 pin landed (issue #224). Force a repair sync
|
||||
// to downgrade setuptools to a version that ships pkg_resources.
|
||||
log::warn!(
|
||||
"Venv at {} is missing pkg_resources (setuptools>=80 pre-dates the <80 pin) \
|
||||
— re-running uv sync to repair (#248)",
|
||||
venv_dir.display()
|
||||
);
|
||||
} else {
|
||||
log::warn!(
|
||||
"Venv exists at {} but uvicorn is not importable — re-running uv sync",
|
||||
venv_dir.display()
|
||||
);
|
||||
}
|
||||
if let Some(p) = progress {
|
||||
set_stage(p, BootstrapStage::InstallingDeps);
|
||||
}
|
||||
@@ -423,6 +454,64 @@ pub fn ensure_venv_ready<R: tauri::Runtime>(app: &tauri::AppHandle<R>, progress:
|
||||
repair_cmd.current_dir(&project_dir);
|
||||
let repair_status = run_streaming(app, "installing_deps", &mut repair_cmd);
|
||||
if matches!(repair_status, Ok(ref s) if s.success()) {
|
||||
// #248: after the repair sync, ensure pkg_resources landed. The repair
|
||||
// path is also triggered when pkg_resources is missing (see above), so
|
||||
// we must verify here rather than trusting that uv sync alone fixed it
|
||||
// (e.g. if the bundled uv.lock still pins setuptools>=80 somehow).
|
||||
let mut pr_repair_check = Command::new(&venv_py);
|
||||
scrub_python_env(&mut pr_repair_check);
|
||||
let pr_ok = matches!(
|
||||
pr_repair_check
|
||||
.args(["-c", "import pkg_resources"])
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status(),
|
||||
Ok(ref s) if s.success()
|
||||
);
|
||||
if !pr_ok {
|
||||
log::warn!("pkg_resources still missing after repair sync — installing setuptools<80 directly (#248)");
|
||||
emit_log(app, "installing_deps",
|
||||
"Repairing pkg_resources: installing setuptools<80 (#248)");
|
||||
let mut st_cmd = Command::new(&uv_path);
|
||||
scrub_python_env(&mut st_cmd);
|
||||
apply_uv_http_env(&mut st_cmd);
|
||||
st_cmd
|
||||
.args(["pip", "install", "setuptools>=75,<80"])
|
||||
.current_dir(&project_dir);
|
||||
match run_streaming(app, "installing_deps", &mut st_cmd) {
|
||||
Ok(ref s) if s.success() => {
|
||||
log::info!("setuptools<80 installed after repair sync; pkg_resources now available (#248)");
|
||||
}
|
||||
other => {
|
||||
log::error!("Failed to install setuptools<80 after repair sync: {:?} — dubbing may fail (#248)", other);
|
||||
}
|
||||
}
|
||||
// Re-verify pkg_resources is importable after the targeted install.
|
||||
let mut pr_post_check = Command::new(&venv_py);
|
||||
scrub_python_env(&mut pr_post_check);
|
||||
let pr_final_ok = matches!(
|
||||
pr_post_check
|
||||
.args(["-c", "import pkg_resources"])
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status(),
|
||||
Ok(ref s) if s.success()
|
||||
);
|
||||
if !pr_final_ok {
|
||||
// Repair could not restore pkg_resources — fail loudly instead of
|
||||
// handing back a venv that will crash on the first ASR/dub call. The
|
||||
// "pkg_resources" text routes to the PKG_RESOURCES_MISSING failure
|
||||
// mapping (clear, doc-linked remediation in the UI). (#248)
|
||||
fail(
|
||||
progress,
|
||||
"pkg_resources is missing from the backend venv and the automatic \
|
||||
setuptools repair did not restore it. Open a terminal and run \
|
||||
`uv pip install 'setuptools>=75,<80'` in the backend venv, then \
|
||||
restart. (#248)",
|
||||
);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
return Some((venv_py, backend_dir));
|
||||
}
|
||||
fail(progress, &format!("Repair uv sync failed: {:?}", repair_status));
|
||||
@@ -574,6 +663,43 @@ docs/install/troubleshooting.md).",
|
||||
return None;
|
||||
}
|
||||
|
||||
// #248 belt-and-suspenders: after every uv sync, verify that pkg_resources is
|
||||
// importable. If it isn't (setuptools>=80 somehow landed — e.g. no lock file in
|
||||
// bundle, or the lock was resolved without our pin), run a targeted
|
||||
// `uv pip install "setuptools<80"` to repair the venv without touching anything
|
||||
// else. This is safe on all platforms (pure-Python wheel, no native code).
|
||||
{
|
||||
let mut pr_verify = Command::new(&venv_py);
|
||||
scrub_python_env(&mut pr_verify);
|
||||
let pr_ok = matches!(
|
||||
pr_verify
|
||||
.args(["-c", "import pkg_resources"])
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status(),
|
||||
Ok(ref s) if s.success()
|
||||
);
|
||||
if !pr_ok {
|
||||
log::warn!("pkg_resources not importable after uv sync — installing setuptools<80 (#248)");
|
||||
emit_log(app, "installing_deps",
|
||||
"pkg_resources missing (setuptools>=80) — installing setuptools<80 to fix (#248)");
|
||||
let mut st_cmd = Command::new(&uv_path);
|
||||
scrub_python_env(&mut st_cmd);
|
||||
apply_uv_http_env(&mut st_cmd);
|
||||
st_cmd
|
||||
.args(["pip", "install", "setuptools>=75,<80"])
|
||||
.current_dir(&project_dir);
|
||||
match run_streaming(app, "installing_deps", &mut st_cmd) {
|
||||
Ok(ref s) if s.success() => {
|
||||
log::info!("setuptools<80 installed; pkg_resources now available (#248)");
|
||||
}
|
||||
other => {
|
||||
log::error!("Failed to install setuptools<80: {:?} — dubbing may fail (#248)", other);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Opt-in AMD ROCm (#124): the default install ships the CUDA torch build,
|
||||
// so AMD-only machines fall back to CPU. If the user set
|
||||
// OMNIVOICE_TORCH_VARIANT=rocm, reinstall torch/torchaudio from the ROCm
|
||||
@@ -668,4 +794,41 @@ mod tests {
|
||||
std::env::remove_var("OMNIVOICE_TORCH_VARIANT");
|
||||
std::env::remove_var("OMNIVOICE_TORCH_INDEX");
|
||||
}
|
||||
|
||||
/// #248: verify that the setuptools repair install uses the correct specifier.
|
||||
/// The specifier `"setuptools>=75,<80"` must be passed as a single argument so
|
||||
/// pip/uv interprets the range constraint as one requirement, not two.
|
||||
#[test]
|
||||
fn setuptools_repair_uses_correct_specifier() {
|
||||
// Mirror the exact args slice used in both repair branches so a regression
|
||||
// (e.g. accidentally splitting into ["setuptools>=75", ",<80"]) is caught
|
||||
// here rather than silently installing the latest setuptools.
|
||||
let repair_args: &[&str] = &["pip", "install", "setuptools>=75,<80"];
|
||||
|
||||
// The version specifier must be the third positional argument — one string,
|
||||
// not split. This is the key property the review bot flagged: a split arg
|
||||
// would make uv install the latest setuptools and leave pkg_resources absent.
|
||||
assert_eq!(repair_args[0], "pip");
|
||||
assert_eq!(repair_args[1], "install");
|
||||
assert_eq!(repair_args[2], "setuptools>=75,<80",
|
||||
"specifier must be a single arg; splitting it would bypass the <80 bound");
|
||||
|
||||
// The single-string specifier must contain both bounds.
|
||||
let specifier = repair_args[2];
|
||||
assert!(specifier.contains("setuptools"), "arg must name the package");
|
||||
assert!(specifier.contains(">=75"), "lower bound must be >=75");
|
||||
assert!(specifier.contains("<80"), "upper bound must be <80 to keep pkg_resources");
|
||||
// No comma-split: the entire range is in one argument with no spaces.
|
||||
assert!(!specifier.contains(' '), "specifier must not contain spaces (would be split by shell)");
|
||||
|
||||
// Verify 79.x satisfies the range
|
||||
let v79: (u32, u32) = (79, 0);
|
||||
assert!(v79.0 >= 75 && v79.0 < 80, "79.x must satisfy >=75,<80");
|
||||
// Verify 80.x does NOT satisfy
|
||||
let v80: (u32, u32) = (80, 0);
|
||||
assert!(!(v80.0 >= 75 && v80.0 < 80), "80.x must NOT satisfy <80");
|
||||
// Verify 82.x (what was installed before #224 fix) does NOT satisfy
|
||||
let v82: (u32, u32) = (82, 0);
|
||||
assert!(!(v82.0 >= 75 && v82.0 < 80), "82.x (pre-fix version) must NOT satisfy <80");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ pub fn run() {
|
||||
config::set_update_channel,
|
||||
updater_channel::check_update,
|
||||
updater_channel::install_update,
|
||||
updater_channel::list_releases,
|
||||
commands::get_sysinfo,
|
||||
commands::read_log_tail,
|
||||
commands::hf_cache_scan,
|
||||
|
||||
@@ -100,3 +100,67 @@ pub async fn install_update(app: AppHandle, channel: String) -> Result<(), Strin
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ── GitHub releases (changelog/history panel) ─────────────────────────────
|
||||
|
||||
const RELEASES_API: &str =
|
||||
"https://api.github.com/repos/debpalash/OmniVoice-Studio/releases?per_page=30";
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ReleaseInfo {
|
||||
pub version: String,
|
||||
pub name: String,
|
||||
pub date: String,
|
||||
pub prerelease: bool,
|
||||
pub notes: String,
|
||||
}
|
||||
|
||||
/// Fetch the project's GitHub releases for the changelog/history panel.
|
||||
/// `channel` is accepted for symmetry with the other update commands; channel
|
||||
/// filtering is applied on the frontend (prepareReleases) so this returns all.
|
||||
#[tauri::command]
|
||||
pub async fn list_releases(_channel: String) -> Result<Vec<ReleaseInfo>, String> {
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(10))
|
||||
.build()
|
||||
.unwrap_or_default();
|
||||
let resp = client
|
||||
.get(RELEASES_API)
|
||||
.header("User-Agent", "OmniVoice-Studio")
|
||||
.header("Accept", "application/vnd.github+json")
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("releases request failed: {e}"))?;
|
||||
if !resp.status().is_success() {
|
||||
return Err(format!("releases request status {}", resp.status()));
|
||||
}
|
||||
let arr: serde_json::Value = resp
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| format!("releases parse failed: {e}"))?;
|
||||
let mut out = Vec::new();
|
||||
if let Some(items) = arr.as_array() {
|
||||
for it in items {
|
||||
let tag = it.get("tag_name").and_then(|v| v.as_str()).unwrap_or("");
|
||||
out.push(ReleaseInfo {
|
||||
version: tag.trim_start_matches('v').to_string(),
|
||||
name: it
|
||||
.get("name")
|
||||
.and_then(|v| v.as_str())
|
||||
.filter(|s| !s.is_empty())
|
||||
.unwrap_or(tag)
|
||||
.to_string(),
|
||||
date: it
|
||||
.get("published_at")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("")
|
||||
.chars()
|
||||
.take(10)
|
||||
.collect(),
|
||||
prerelease: it.get("prerelease").and_then(|v| v.as_bool()).unwrap_or(false),
|
||||
notes: it.get("body").and_then(|v| v.as_str()).unwrap_or("").to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||
"productName": "OmniVoice Studio",
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.3",
|
||||
"identifier": "com.debpalash.omnivoice-studio",
|
||||
"build": {
|
||||
"frontendDist": "../dist",
|
||||
|
||||
+62
-51
@@ -21,13 +21,11 @@ const VoicePreview = lazy(() => import('./components/VoicePreview'));
|
||||
const LogsFooter = lazy(() => import('./components/LogsFooter'));
|
||||
const ProjectsPage = lazy(() => import('./pages/Projects'));
|
||||
const VoiceGallery = lazy(() => import('./pages/VoiceGallery'));
|
||||
const DonatePage = lazy(() => import('./pages/DonatePage'));
|
||||
const EnterprisePage = lazy(() => import('./pages/EnterprisePage'));
|
||||
const SupportPage = lazy(() => import('./pages/SupportPage'));
|
||||
const TranscriptionsPage = lazy(() => import('./pages/Transcriptions'));
|
||||
const StoriesEditor = lazy(() => import('./components/StoriesEditor'));
|
||||
|
||||
import Header from './components/Header';
|
||||
import UpdateBadge from './components/UpdateBadge';
|
||||
import NavRail from './components/NavRail';
|
||||
import ErrorBoundary from './components/ErrorBoundary';
|
||||
import FloatingPill from './components/FloatingPill';
|
||||
@@ -48,7 +46,7 @@ import useProfiles from './hooks/useProfiles';
|
||||
import useTTS from './hooks/useTTS';
|
||||
import useDubWorkflow from './hooks/useDubWorkflow';
|
||||
|
||||
const LazyFallback = () => <div className="app-lazy-fallback">Loading…</div>;
|
||||
const LazyFallback = () => <div className="app-lazy-fallback">{i18n.t('app.loading')}</div>;
|
||||
|
||||
import { Toaster, toast } from 'react-hot-toast';
|
||||
import {
|
||||
@@ -62,7 +60,9 @@ import { saveProject as apiSaveProject, loadProject as apiLoadProject, deletePro
|
||||
import { exportAction, exportReveal, exportRecord } from './api/exports';
|
||||
|
||||
import { isTauri, doubleClickMaximize, fileToMediaUrl, playBlobAudio, playPing } from './utils/media';
|
||||
import { checkForUpdate } from './utils/updater';
|
||||
import { browserDownload } from './utils/download';
|
||||
import { checkForUpdate, fetchAppVersion } from './utils/updater';
|
||||
import { syncChannel } from './utils/channelControl';
|
||||
import i18n from './i18n';
|
||||
|
||||
function App() {
|
||||
@@ -395,10 +395,18 @@ function App() {
|
||||
if (typeof window === 'undefined') return;
|
||||
if (!('__TAURI_INTERNALS__' in window)) return;
|
||||
if (import.meta.env.DEV) return;
|
||||
// Non-blocking: surface availability into the store. The UpdateBadge lets
|
||||
// the user install + restart when they choose (with a progress bar), so an
|
||||
// update never interrupts in-flight work.
|
||||
// Non-blocking: surface availability into the store. The UpdateStatusChip
|
||||
// in LogsFooter lets the user install + restart when they choose (with a
|
||||
// progress bar), so an update never interrupts in-flight work.
|
||||
fetchAppVersion().then(v => useAppStore.getState().setAppVersion(v));
|
||||
syncChannel(useAppStore.getState());
|
||||
checkForUpdate(useAppStore.getState());
|
||||
// Re-check periodically so a long-running session still gets notified, not
|
||||
// only at boot. checkForUpdate no-ops while a download/restart is already
|
||||
// in flight, so this can't interrupt an install.
|
||||
const SIX_HOURS = 6 * 60 * 60 * 1000;
|
||||
const id = setInterval(() => checkForUpdate(useAppStore.getState()), SIX_HOURS);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
|
||||
// ── DESKTOP NATIVE INTEGRATION ──
|
||||
@@ -499,6 +507,25 @@ function App() {
|
||||
|
||||
const handleNativeExport = async (e, sourceIdentifier, fallbackName, mode) => {
|
||||
if (e) { e.preventDefault(); e.stopPropagation(); }
|
||||
// Browser / Docker web build: there is no Tauri shell, so the native save
|
||||
// dialog is unavailable — invoking it throws "Cannot read properties of
|
||||
// undefined (reading 'invoke')" (issue #256). Fall back to a plain HTTP
|
||||
// blob download of the file already served at /audio/<path>.
|
||||
if (!isTauri) {
|
||||
const niceName = (fallbackName || sourceIdentifier || 'audio').split('/').pop();
|
||||
try {
|
||||
const finalName = await browserDownload(`${API}/audio/${sourceIdentifier}`, niceName);
|
||||
toast.success(i18n.t('app.toast_downloaded', { name: finalName }));
|
||||
try {
|
||||
await exportRecord({ filename: finalName, destination_path: `~/Downloads/${finalName}`, mode });
|
||||
loadExportHistory();
|
||||
} catch (err) { console.warn('exportRecord (browser export path) failed:', err); }
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error(i18n.t('app.toast_export_failed', { message: err?.message || err }));
|
||||
}
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const { save } = await import('@tauri-apps/plugin-dialog');
|
||||
const ext = fallbackName.includes('.') ? fallbackName.split('.').pop() : 'wav';
|
||||
@@ -506,28 +533,20 @@ function App() {
|
||||
if (!destPath) return; // User cancelled
|
||||
|
||||
await exportAction({ source_filename: sourceIdentifier, destination_path: destPath, mode });
|
||||
toast.success(`Exported: ${fallbackName}`);
|
||||
toast.success(i18n.t('app.toast_exported', { name: fallbackName }));
|
||||
loadExportHistory();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error(`Export failed: ${err?.message || err}`);
|
||||
toast.error(i18n.t('app.toast_export_failed', { message: err?.message || err }));
|
||||
}
|
||||
};
|
||||
const revealInFolder = async (filePath) => {
|
||||
try {
|
||||
await exportReveal({ path: filePath });
|
||||
} catch (err) {
|
||||
toast.error(`Could not open folder: ${err.message}`);
|
||||
toast.error(i18n.t('app.toast_open_folder_failed', { message: err.message }));
|
||||
}
|
||||
};
|
||||
const parseFilenameFromContentDisposition = (header) => {
|
||||
if (!header) return null;
|
||||
const utf8 = header.match(/filename\*=(?:UTF-8|utf-8)''([^;]+)/i);
|
||||
if (utf8) { try { return decodeURIComponent(utf8[1].trim().replace(/^"|"$/g, '')); } catch { /* ignore */ } }
|
||||
const plain = header.match(/filename="?([^";]+)"?/i);
|
||||
return plain ? plain[1].trim() : null;
|
||||
};
|
||||
|
||||
const triggerDownload = async (url, fallbackName) => {
|
||||
const extGuess = (fallbackName.includes('.') ? fallbackName.split('.').pop() : 'bin').toLowerCase();
|
||||
const modeGuess = ['mp4','mov','mkv','webm'].includes(extGuess)
|
||||
@@ -543,7 +562,7 @@ function App() {
|
||||
filters: [{ name: modeGuess === 'video' ? 'Video' : 'Audio', extensions: [extGuess] }],
|
||||
});
|
||||
if (!destPath) return; // user cancelled
|
||||
toast.loading(`Saving ${fallbackName}...`, { id: fallbackName });
|
||||
toast.loading(i18n.t('app.toast_saving', { name: fallbackName }), { id: fallbackName });
|
||||
const sep = url.includes('?') ? '&' : '?';
|
||||
const res = await fetch(`${url}${sep}save_path=${encodeURIComponent(destPath)}`);
|
||||
if (!res.ok) {
|
||||
@@ -551,42 +570,30 @@ function App() {
|
||||
throw new Error(err.detail || 'Save failed');
|
||||
}
|
||||
const data = await res.json();
|
||||
toast.success(`Saved: ${data.path}`, { id: fallbackName });
|
||||
toast.success(i18n.t('app.toast_saved', { path: data.path }), { id: fallbackName });
|
||||
try {
|
||||
await exportRecord({ filename: data.display_name || fallbackName, destination_path: data.path, mode: modeGuess });
|
||||
loadExportHistory();
|
||||
} catch (err) { console.warn('exportRecord (Tauri save path) failed:', err); }
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error(`Save error: ${err.message}`, { id: fallbackName });
|
||||
toast.error(i18n.t('app.toast_save_error', { message: err.message }), { id: fallbackName });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Browser path: standard blob download.
|
||||
try {
|
||||
toast.loading(`Processing ${fallbackName}...`, { id: fallbackName });
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error("Download failed");
|
||||
const serverName = parseFilenameFromContentDisposition(response.headers.get('content-disposition'));
|
||||
const finalName = serverName || fallbackName || 'download';
|
||||
const blob = await response.blob();
|
||||
const localUrl = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = localUrl;
|
||||
a.download = finalName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(localUrl);
|
||||
toast.success(`Downloaded ${finalName}`, { id: fallbackName });
|
||||
toast.loading(i18n.t('app.toast_processing', { name: fallbackName }), { id: fallbackName });
|
||||
const finalName = await browserDownload(url, fallbackName);
|
||||
toast.success(i18n.t('app.toast_downloaded', { name: finalName }), { id: fallbackName });
|
||||
try {
|
||||
await exportRecord({ filename: finalName, destination_path: `~/Downloads/${finalName}`, mode: modeGuess });
|
||||
loadExportHistory();
|
||||
} catch (err) { console.warn('exportRecord (browser download path) failed:', err); }
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error(`Download error: ${err.message}`, { id: fallbackName });
|
||||
toast.error(i18n.t('app.toast_download_error', { message: err.message }), { id: fallbackName });
|
||||
}
|
||||
};
|
||||
// Pre-flight for audio/video exports. If any segments are at preview
|
||||
@@ -634,7 +641,7 @@ function App() {
|
||||
// ═══ STUDIO PROJECT CRUD ═══
|
||||
const saveProject = async () => {
|
||||
if (dubStep === 'idle') {
|
||||
toast.error("Please click 'Upload & Transcribe' first so the video is processed on the server before saving.");
|
||||
toast.error(i18n.t('app.toast_upload_first'));
|
||||
return;
|
||||
}
|
||||
const name = activeProjectName || dubFilename || `Project ${new Date().toLocaleString()}`;
|
||||
@@ -652,10 +659,10 @@ function App() {
|
||||
try {
|
||||
const data = await apiSaveProject(statePayload, activeProjectId);
|
||||
setActiveProject(data.id, name);
|
||||
toast.success(activeProjectId ? 'Project saved' : 'Project created');
|
||||
toast.success(activeProjectId ? i18n.t('app.toast_project_saved') : i18n.t('app.toast_project_created'));
|
||||
loadProjects();
|
||||
} catch (err) {
|
||||
toast.error('Save failed: ' + err.message);
|
||||
toast.error(i18n.t('app.toast_save_failed', { message: err.message }));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -683,7 +690,7 @@ function App() {
|
||||
// the last generate.
|
||||
setLastGenFingerprints(s.segHashes || {});
|
||||
setSpeakerClones(s.speakerClones || {});
|
||||
toast.success(`Opened: ${data.name}`);
|
||||
toast.success(i18n.t('app.toast_opened', { name: data.name }));
|
||||
} catch (err) {
|
||||
toast.error(err.message);
|
||||
}
|
||||
@@ -698,7 +705,7 @@ function App() {
|
||||
setActiveProject(null);
|
||||
}
|
||||
loadProjects();
|
||||
toast.success('Project deleted');
|
||||
toast.success(i18n.t('app.toast_project_deleted'));
|
||||
} catch (err) { toast.error(err.message); }
|
||||
};
|
||||
|
||||
@@ -738,7 +745,7 @@ function App() {
|
||||
|
||||
// Switch to studio tab
|
||||
setSidebarTab('projects');
|
||||
toast.success('Restored previous generation state');
|
||||
toast.success(i18n.t('app.toast_restored_state'));
|
||||
};
|
||||
|
||||
const deleteHistory = async (id, type) => {
|
||||
@@ -751,7 +758,7 @@ function App() {
|
||||
} else {
|
||||
loadHistory();
|
||||
}
|
||||
toast.success('History item deleted');
|
||||
toast.success(i18n.t('app.toast_history_deleted'));
|
||||
} catch (err) {
|
||||
toast.error(err.message);
|
||||
}
|
||||
@@ -830,7 +837,7 @@ function App() {
|
||||
file={pendingTrimFile}
|
||||
maxSeconds={CLONE_MAX_SECONDS}
|
||||
onCancel={() => setPendingTrimFile(null)}
|
||||
onConfirm={(trimmed) => { setPendingTrimFile(null); setRefAudio(trimmed); setSelectedProfile(null); toast.success('Trimmed audio loaded'); }}
|
||||
onConfirm={(trimmed) => { setPendingTrimFile(null); setRefAudio(trimmed); setSelectedProfile(null); toast.success(i18n.t('app.trimmed_loaded')); }}
|
||||
/>
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
@@ -844,7 +851,6 @@ function App() {
|
||||
<FloatingPill />
|
||||
|
||||
|
||||
<UpdateBadge />
|
||||
<Header
|
||||
mode={mode} setMode={setMode}
|
||||
sysStats={sysStats} modelStatus={modelStatus}
|
||||
@@ -853,8 +859,12 @@ function App() {
|
||||
onFlushMemory={async (unloadModel) => {
|
||||
try {
|
||||
const r = await apiFlushMemory(unloadModel);
|
||||
toast.success(`Flushed — RAM ${r.ram_after}G · VRAM ${r.vram_after}G${r.unloaded_model ? ' · model unloaded' : ''}`);
|
||||
} catch (e) { toast.error('Flush failed: ' + e.message); }
|
||||
toast.success(i18n.t('app.toast_flushed', {
|
||||
ram: r.ram_after,
|
||||
vram: r.vram_after,
|
||||
unloaded: r.unloaded_model ? i18n.t('app.toast_model_unloaded') : '',
|
||||
}));
|
||||
} catch (e) { toast.error(i18n.t('app.toast_flush_failed', { message: e.message })); }
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -930,13 +940,13 @@ function App() {
|
||||
) : mode === 'donate' ? (
|
||||
<ErrorBoundary name="donate">
|
||||
<Suspense fallback={<LazyFallback />}>
|
||||
<DonatePage onBack={() => setMode('launchpad')} onEnterprise={() => setMode('enterprise')} />
|
||||
<SupportPage initialView="support" onBack={() => setMode('launchpad')} />
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
) : mode === 'enterprise' ? (
|
||||
<ErrorBoundary name="enterprise">
|
||||
<Suspense fallback={<LazyFallback />}>
|
||||
<EnterprisePage onBack={() => setMode('launchpad')} />
|
||||
<SupportPage initialView="license" onBack={() => setMode('launchpad')} />
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
) : mode === 'launchpad' ? (
|
||||
@@ -946,6 +956,7 @@ function App() {
|
||||
profiles={profiles}
|
||||
studioProjects={studioProjects}
|
||||
dubHistory={dubHistory}
|
||||
exportHistory={exportHistory}
|
||||
setMode={setMode}
|
||||
setIsCompareModalOpen={setIsCompareModalOpen}
|
||||
handleSelectProfile={handleSelectProfile}
|
||||
|
||||
@@ -60,6 +60,7 @@ export interface SystemInfo {
|
||||
app_version?: string;
|
||||
python?: string;
|
||||
platform?: string;
|
||||
arch?: string;
|
||||
device?: string;
|
||||
data_dir?: string;
|
||||
outputs_dir?: string;
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
decodeToMonoLowRate, DEFAULT_PEAK_BUCKETS,
|
||||
} from '../utils/audioTrim.js';
|
||||
import { Dialog, Button } from '../ui';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import './AudioTrimmer.css';
|
||||
|
||||
const EDGE_GRAB_PX = 10;
|
||||
@@ -29,6 +30,7 @@ function fmtHMS(t) {
|
||||
}
|
||||
|
||||
export default function AudioTrimmer({ file, maxSeconds = 15, onConfirm, onCancel }) {
|
||||
const { t } = useTranslation();
|
||||
const waveRef = useRef(null);
|
||||
const rulerRef = useRef(null);
|
||||
const audioRef = useRef(null);
|
||||
@@ -99,7 +101,7 @@ export default function AudioTrimmer({ file, maxSeconds = 15, onConfirm, onCance
|
||||
peaksRef.current = refined;
|
||||
setPeakProgress(1);
|
||||
} catch (e) {
|
||||
if (!cancelled) setError('Decode failed: ' + (e.message || e));
|
||||
if (!cancelled) setError(t('trimmer.decode_failed', { message: e.message || e }));
|
||||
setDecoding(false);
|
||||
}
|
||||
})();
|
||||
@@ -465,7 +467,7 @@ export default function AudioTrimmer({ file, maxSeconds = 15, onConfirm, onCance
|
||||
const doPlay = () => {
|
||||
try { a.currentTime = s; } catch (err) { console.warn('currentTime set failed', err); }
|
||||
a.play().then(() => setPlaying(true)).catch((err) => {
|
||||
setError('Playback failed: ' + (err.message || err));
|
||||
setError(t('trimmer.playback_failed', { message: err.message || err }));
|
||||
});
|
||||
};
|
||||
// HAVE_METADATA = 1 is enough to set currentTime on most browsers.
|
||||
@@ -473,7 +475,7 @@ export default function AudioTrimmer({ file, maxSeconds = 15, onConfirm, onCance
|
||||
doPlay();
|
||||
} else {
|
||||
a.addEventListener('loadedmetadata', doPlay, { once: true });
|
||||
a.addEventListener('error', () => setError('Audio load failed'), { once: true });
|
||||
a.addEventListener('error', () => setError(t('trimmer.audio_load_failed')), { once: true });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -569,18 +571,18 @@ export default function AudioTrimmer({ file, maxSeconds = 15, onConfirm, onCance
|
||||
open
|
||||
onClose={onCancel}
|
||||
size="xl"
|
||||
title={<><Scissors size={15} color="var(--color-brand)" /> Trim reference audio</>}
|
||||
title={<><Scissors size={15} color="var(--color-brand)" /> {t('trimmer.title')}</>}
|
||||
>
|
||||
<div ref={containerRef} tabIndex={-1} className="audio-trimmer">
|
||||
<div className="audio-trimmer__meta">
|
||||
<span>{decoding
|
||||
? 'Decoding audio…'
|
||||
? t('trimmer.decoding')
|
||||
: (audioMeta
|
||||
? `Length ${fmtHMS(audioMeta.duration)} · ${audioMeta.sampleRate} Hz${peakProgress > 0 && peakProgress < 1 ? ` · rendering waveform ${Math.round(peakProgress * 100)}%` : ''}`
|
||||
? `${t('trimmer.meta_length', { duration: fmtHMS(audioMeta.duration), sampleRate: audioMeta.sampleRate })}${peakProgress > 0 && peakProgress < 1 ? ` · ${t('trimmer.meta_rendering', { percent: Math.round(peakProgress * 100) })}` : ''}`
|
||||
: '…')
|
||||
}</span>
|
||||
<span className="audio-trimmer__hint">
|
||||
scroll = zoom · shift+scroll = pan · alt+drag = pan · ⏐ ⟵ ⟶ ⏐ keys adjust handles
|
||||
{t('trimmer.keyboard_hint')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -588,12 +590,12 @@ export default function AudioTrimmer({ file, maxSeconds = 15, onConfirm, onCance
|
||||
|
||||
{/* Zoom controls */}
|
||||
<div className="audio-trimmer__toolbar">
|
||||
<Button variant="subtle" iconSize="md" onClick={zoomIn} disabled={!ready} title="Zoom in (+)"><ZoomIn size={12}/></Button>
|
||||
<Button variant="subtle" iconSize="md" onClick={zoomOut} disabled={!ready} title="Zoom out (-)"><ZoomOut size={12}/></Button>
|
||||
<Button variant="subtle" iconSize="md" onClick={fitAll} disabled={!ready} title="Fit all (Home)"><Maximize2 size={12}/></Button>
|
||||
<Button variant="chip" size="sm" onClick={fitSelection} disabled={!ready} title="Fit selection (End)">FIT SEL</Button>
|
||||
<Button variant="subtle" iconSize="md" onClick={zoomIn} disabled={!ready} title={t('trimmer.zoom_in')}><ZoomIn size={12}/></Button>
|
||||
<Button variant="subtle" iconSize="md" onClick={zoomOut} disabled={!ready} title={t('trimmer.zoom_out')}><ZoomOut size={12}/></Button>
|
||||
<Button variant="subtle" iconSize="md" onClick={fitAll} disabled={!ready} title={t('trimmer.fit_all')}><Maximize2 size={12}/></Button>
|
||||
<Button variant="chip" size="sm" onClick={fitSelection} disabled={!ready} title={t('trimmer.fit_selection')}>{t('trimmer.fit_sel_btn')}</Button>
|
||||
<div className="audio-trimmer__view-info">
|
||||
View {fmtHMS(viewStart)} → {fmtHMS(viewEnd)} ({fmtSec(viewEnd - viewStart, viewEnd - viewStart < 10 ? 2 : 0)})
|
||||
{t('trimmer.view_range', { start: fmtHMS(viewStart), end: fmtHMS(viewEnd), duration: fmtSec(viewEnd - viewStart, viewEnd - viewStart < 10 ? 2 : 0) })}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -606,7 +608,7 @@ export default function AudioTrimmer({ file, maxSeconds = 15, onConfirm, onCance
|
||||
{/* Numeric fields */}
|
||||
<div className="audio-trimmer__fields">
|
||||
<label className="trim-field">
|
||||
<span className="trim-field__label">Start</span>
|
||||
<span className="trim-field__label">{t('trimmer.start_label')}</span>
|
||||
<input
|
||||
type="text" inputMode="decimal" value={startInput}
|
||||
onChange={(e) => setStartInput(e.target.value)}
|
||||
@@ -614,10 +616,10 @@ export default function AudioTrimmer({ file, maxSeconds = 15, onConfirm, onCance
|
||||
onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); commitStartInput(); } }}
|
||||
className="trim-field__input"
|
||||
/>
|
||||
<span className="trim-field__unit">s</span>
|
||||
<span className="trim-field__unit">{t('trimmer.unit_seconds')}</span>
|
||||
</label>
|
||||
<label className="trim-field">
|
||||
<span className="trim-field__label">End</span>
|
||||
<span className="trim-field__label">{t('trimmer.end_label')}</span>
|
||||
<input
|
||||
type="text" inputMode="decimal" value={endInput}
|
||||
onChange={(e) => setEndInput(e.target.value)}
|
||||
@@ -625,21 +627,21 @@ export default function AudioTrimmer({ file, maxSeconds = 15, onConfirm, onCance
|
||||
onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); commitEndInput(); } }}
|
||||
className="trim-field__input"
|
||||
/>
|
||||
<span className="trim-field__unit">s</span>
|
||||
<span className="trim-field__unit">{t('trimmer.unit_seconds')}</span>
|
||||
</label>
|
||||
<div className="trim-field trim-field--readonly">
|
||||
<span className="trim-field__label">Length</span>
|
||||
<span className="trim-field__label">{t('trimmer.length_label')}</span>
|
||||
<span className={`trim-field__value ${tooLong ? 'is-err' : ''}`}>
|
||||
{(duration_ms / 1000).toFixed(2)}s
|
||||
{(duration_ms / 1000).toFixed(2)}{t('trimmer.unit_seconds')}
|
||||
</span>
|
||||
<span className="trim-field__unit">{tooLong ? `>${maxSeconds}s` : tooShort ? 'too short' : 'ok'}</span>
|
||||
<span className="trim-field__unit">{tooLong ? t('trimmer.too_long', { max: maxSeconds }) : tooShort ? t('trimmer.too_short') : t('trimmer.length_ok')}</span>
|
||||
</div>
|
||||
<Button
|
||||
variant="icon"
|
||||
iconSize="md"
|
||||
active={loop}
|
||||
onClick={() => setLoop((v) => !v)}
|
||||
title="Loop preview"
|
||||
title={t('trimmer.loop_preview')}
|
||||
>
|
||||
<Repeat size={12} />
|
||||
</Button>
|
||||
@@ -654,19 +656,19 @@ export default function AudioTrimmer({ file, maxSeconds = 15, onConfirm, onCance
|
||||
leading={playing ? <Pause size={12} /> : <Play size={12} />}
|
||||
className="audio-trimmer__play-btn"
|
||||
>
|
||||
{playing ? 'Pause' : 'Preview selection'}
|
||||
{playing ? t('trimmer.pause') : t('trimmer.preview_selection')}
|
||||
</Button>
|
||||
<span className="audio-trimmer__kbd-hint">Space to play · Enter to confirm · Esc to cancel</span>
|
||||
<span className="audio-trimmer__kbd-hint">{t('trimmer.play_hint')}</span>
|
||||
|
||||
<div className="audio-trimmer__actions-right">
|
||||
<Button variant="ghost" onClick={onCancel}>Cancel</Button>
|
||||
<Button variant="ghost" onClick={onCancel}>{t('trimmer.cancel')}</Button>
|
||||
<Button
|
||||
variant={(tooLong || tooShort) ? 'danger' : 'primary'}
|
||||
disabled={!ready || tooLong || tooShort}
|
||||
onClick={handleConfirm}
|
||||
leading={<Check size={12} />}
|
||||
>
|
||||
Use trimmed
|
||||
{t('trimmer.use_trimmed')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useRef, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Upload, Film, Globe, X, Plus, Loader } from 'lucide-react';
|
||||
import { Button } from '../ui';
|
||||
import MultiLangPicker from './MultiLangPicker';
|
||||
@@ -17,6 +18,7 @@ export default function BatchAddDialog({
|
||||
profiles = [],
|
||||
onEnqueue, // async (files, settings) => void
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [files, setFiles] = useState([]);
|
||||
const [langs, setLangs] = useState([{ lang: 'Spanish', code: 'es' }]);
|
||||
const [voiceId, setVoiceId] = useState('');
|
||||
@@ -53,7 +55,7 @@ export default function BatchAddDialog({
|
||||
<div className="batch-add" onClick={e => e.stopPropagation()}>
|
||||
<div className="batch-add__head">
|
||||
<span className="batch-add__title">
|
||||
<Plus size={13} /> Add Videos to Queue
|
||||
<Plus size={13} /> {t('batch.add_to_queue_title')}
|
||||
</span>
|
||||
<button type="button" className="batch-add__close" onClick={onClose}>
|
||||
<X size={13} />
|
||||
@@ -70,8 +72,8 @@ export default function BatchAddDialog({
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
>
|
||||
<Upload size={24} />
|
||||
<span>Drop video files here or click to browse</span>
|
||||
<span className="batch-add__drop-hint">MP4 · MOV · MKV · WEBM</span>
|
||||
<span>{t('batch.drop_hint_text')}</span>
|
||||
<span className="batch-add__drop-hint">{t('batch.drop_formats')}</span>
|
||||
</div>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
@@ -89,12 +91,12 @@ export default function BatchAddDialog({
|
||||
{/* File list */}
|
||||
{files.length > 0 && (
|
||||
<div className="batch-add__files">
|
||||
<span className="batch-add__kicker">FILES ({files.length})</span>
|
||||
<span className="batch-add__kicker">{t('batch.files_kicker', { count: files.length })}</span>
|
||||
{files.map((f, i) => (
|
||||
<div key={`${f.name}-${i}`} className="batch-add__file-row">
|
||||
<Film size={10} />
|
||||
<span className="batch-add__file-name">{f.name}</span>
|
||||
<span className="batch-add__file-size">{(f.size / 1024 / 1024).toFixed(1)} MB</span>
|
||||
<span className="batch-add__file-size">{t('batch.file_size_mb', { size: (f.size / 1024 / 1024).toFixed(1) })}</span>
|
||||
<button type="button" className="batch-add__file-x" onClick={() => removeFile(i)}>
|
||||
<X size={9} />
|
||||
</button>
|
||||
@@ -106,27 +108,27 @@ export default function BatchAddDialog({
|
||||
{/* Settings */}
|
||||
<div className="batch-add__settings">
|
||||
<div className="batch-add__field">
|
||||
<span className="batch-add__kicker"><Globe size={9} /> TARGET LANGUAGES</span>
|
||||
<span className="batch-add__kicker"><Globe size={9} /> {t('batch.target_languages')}</span>
|
||||
<MultiLangPicker selected={langs} onChange={setLangs} />
|
||||
</div>
|
||||
|
||||
<div className="batch-add__field">
|
||||
<span className="batch-add__kicker">VOICE</span>
|
||||
<span className="batch-add__kicker">{t('batch.voice_kicker')}</span>
|
||||
<select
|
||||
className="input-base batch-add__select"
|
||||
value={voiceId}
|
||||
onChange={e => setVoiceId(e.target.value)}
|
||||
>
|
||||
<option value="">Default</option>
|
||||
<option value="">{t('batch.default_option')}</option>
|
||||
{profiles.filter(p => !p.instruct).length > 0 && (
|
||||
<optgroup label="Clone Profiles">
|
||||
<optgroup label={t('batch.clone_profiles')}>
|
||||
{profiles.filter(p => !p.instruct).map(p => (
|
||||
<option key={p.id} value={p.id}>{p.name}</option>
|
||||
))}
|
||||
</optgroup>
|
||||
)}
|
||||
{PRESETS.length > 0 && (
|
||||
<optgroup label="Presets">
|
||||
<optgroup label={t('batch.presets')}>
|
||||
{PRESETS.map(p => (
|
||||
<option key={p.id} value={`preset:${p.id}`}>{p.name}</option>
|
||||
))}
|
||||
@@ -137,7 +139,7 @@ export default function BatchAddDialog({
|
||||
|
||||
<label className="batch-add__toggle">
|
||||
<input type="checkbox" checked={preserveBg} onChange={e => setPreserveBg(e.target.checked)} />
|
||||
<span>Preserve background audio (music/FX)</span>
|
||||
<span>{t('batch.preserve_bg')}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -145,10 +147,10 @@ export default function BatchAddDialog({
|
||||
<div className="batch-add__foot">
|
||||
<span className="batch-add__estimate">
|
||||
{files.length > 0 && langs.length > 0
|
||||
? `${files.length} video${files.length > 1 ? 's' : ''} × ${langs.length} lang${langs.length > 1 ? 's' : ''} = ${files.length * langs.length} job${files.length * langs.length > 1 ? 's' : ''}`
|
||||
: 'Select files and languages'}
|
||||
? t('batch.estimate', { videos: files.length, langs: langs.length, jobs: files.length * langs.length })
|
||||
: t('batch.select_files_langs')}
|
||||
</span>
|
||||
<Button variant="ghost" size="sm" onClick={onClose}>Cancel</Button>
|
||||
<Button variant="ghost" size="sm" onClick={onClose}>{t('common.cancel')}</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
@@ -157,10 +159,11 @@ export default function BatchAddDialog({
|
||||
loading={submitting}
|
||||
leading={!submitting && <Plus size={10} />}
|
||||
>
|
||||
Add to Queue
|
||||
{t('batch.add_to_queue')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ export function BootstrapSplash({ stage, message }) {
|
||||
|
||||
const handleCleanRetry = async () => {
|
||||
if (retrying) return;
|
||||
if (!confirm('This will delete the cached Python environment and re-download all dependencies (~5-10 min). Continue?')) return;
|
||||
if (!confirm(t('bootstrap.clean_retry_confirm'))) return;
|
||||
setRetrying(true);
|
||||
try {
|
||||
const { invoke } = await import('@tauri-apps/api/core');
|
||||
@@ -258,10 +258,10 @@ export function BootstrapSplash({ stage, message }) {
|
||||
onChange={(e) => handleRegionChange(e.target.value)}
|
||||
>
|
||||
<option value="auto">🌐 {t('bootstrap.auto_detect', 'Auto-detect')}</option>
|
||||
<option value="global">🌐 Global (direct)</option>
|
||||
<option value="china">🇨🇳 China (mirror)</option>
|
||||
<option value="russia">🇷🇺 Russia (mirror)</option>
|
||||
<option value="restricted">🌍 Restricted (mirror)</option>
|
||||
<option value="global">🌐 {t('bootstrap.region_global')}</option>
|
||||
<option value="china">🇨🇳 {t('bootstrap.region_china')}</option>
|
||||
<option value="russia">🇷🇺 {t('bootstrap.region_russia')}</option>
|
||||
<option value="restricted">🌍 {t('bootstrap.region_restricted')}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="bootstrap-splash__lang" style={{ marginLeft: '0.5rem' }}>
|
||||
@@ -288,7 +288,7 @@ export function BootstrapSplash({ stage, message }) {
|
||||
<p className="bootstrap-splash__status">{label}</p>
|
||||
{isFailed ? (
|
||||
<>
|
||||
<pre className="bootstrap-splash__error">{message || 'Unknown error'}</pre>
|
||||
<pre className="bootstrap-splash__error">{message || t('bootstrap.unknown_error')}</pre>
|
||||
<div className="bootstrap-splash__hints">
|
||||
<strong>💡 {t('bootstrap.what_to_try', 'What to try:')}</strong>
|
||||
<ul>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { copyText } from "../utils/copyText";
|
||||
import { X, Loader } from 'lucide-react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useAppStore } from '../store';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import './CaptureWidget.css';
|
||||
|
||||
import { API as API_BASE } from '../api/client';
|
||||
@@ -35,6 +36,7 @@ function formatElapsed(ms) {
|
||||
* Records → transcribes → auto-pastes → auto-dismisses.
|
||||
*/
|
||||
export default function CaptureWidget({ onDismiss }) {
|
||||
const { t } = useTranslation();
|
||||
const [state, setState] = useState('idle'); // idle | recording | transcribing | done | error
|
||||
const [transcript, setTranscript] = useState('');
|
||||
const [duration, setDuration] = useState(0);
|
||||
@@ -254,11 +256,11 @@ export default function CaptureWidget({ onDismiss }) {
|
||||
const isMac = navigator.platform?.includes('Mac');
|
||||
const isWindows = navigator.platform?.includes('Win');
|
||||
const hint = isMac
|
||||
? 'macOS: open System Settings → Privacy & Security → Microphone and enable OmniVoice.'
|
||||
? t('capture.mic_hint_mac')
|
||||
: isWindows
|
||||
? 'Windows: open Settings → Privacy & security → Microphone and allow OmniVoice.'
|
||||
: 'Linux: check that your user is in the audio group and the WebView has mic access.';
|
||||
toast.error(`Microphone access denied. ${hint}`, { duration: 6000 });
|
||||
? t('capture.mic_hint_windows')
|
||||
: t('capture.mic_hint_linux');
|
||||
toast.error(t('capture.mic_denied_toast', { hint }), { duration: 6000 });
|
||||
setTrayRecording(false);
|
||||
setState('error');
|
||||
}
|
||||
@@ -320,7 +322,7 @@ export default function CaptureWidget({ onDismiss }) {
|
||||
await applyResult(data);
|
||||
} catch (err) {
|
||||
if (wsHadFinalRef.current) return;
|
||||
toast.error(`Transcription failed: ${err.message}`);
|
||||
toast.error(t('capture.transcription_failed', { message: err.message }));
|
||||
setState('error');
|
||||
setTranscript('');
|
||||
}
|
||||
@@ -348,19 +350,19 @@ export default function CaptureWidget({ onDismiss }) {
|
||||
let emoji = '';
|
||||
if (state === 'recording') {
|
||||
emoji = '🎙️';
|
||||
label = partialText || 'Listening…';
|
||||
label = partialText || t('capture.listening_label');
|
||||
} else if (state === 'transcribing') {
|
||||
emoji = '📝';
|
||||
label = partialText || 'Transcribing…';
|
||||
label = partialText || t('capture.transcribing_label');
|
||||
} else if (state === 'done' && transcript) {
|
||||
emoji = '✅';
|
||||
label = 'Pasted';
|
||||
label = t('capture.pasted');
|
||||
} else if (state === 'done' && !transcript) {
|
||||
emoji = '⚠️';
|
||||
label = 'No speech detected';
|
||||
label = t('capture.no_speech');
|
||||
} else if (state === 'error') {
|
||||
emoji = '❌';
|
||||
label = 'Mic access denied';
|
||||
label = t('capture.mic_denied');
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -389,7 +391,7 @@ export default function CaptureWidget({ onDismiss }) {
|
||||
|
||||
{/* Dismiss — only on done/error */}
|
||||
{(state === 'done' || state === 'error') && (
|
||||
<button className="capture-pill__dismiss" onClick={dismiss} aria-label="Dismiss">
|
||||
<button className="capture-pill__dismiss" onClick={dismiss} aria-label={t('common.dismiss')}>
|
||||
<X size={12} />
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { User, Mic, ChevronDown, Check, Shuffle, Volume2 } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import './CastingView.css';
|
||||
|
||||
/**
|
||||
@@ -26,6 +27,7 @@ export default function CastingView({
|
||||
onPreview,
|
||||
}) {
|
||||
const [openDropdown, setOpenDropdown] = useState(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const assign = useCallback((speakerId, profileId) => {
|
||||
const next = { ...assignments, [speakerId]: profileId };
|
||||
@@ -54,19 +56,19 @@ export default function CastingView({
|
||||
<div className="casting-view">
|
||||
<div className="casting-view__header">
|
||||
<h3 className="casting-view__title">
|
||||
<User size={14} /> Speaker Casting
|
||||
<User size={14} /> {t('casting.title')}
|
||||
</h3>
|
||||
<div className="casting-view__actions">
|
||||
<button
|
||||
className="casting-view__auto-btn"
|
||||
onClick={autoAssignAll}
|
||||
title="Auto-assign voices from extracted speaker clones"
|
||||
title={t('casting.auto_assign_title')}
|
||||
>
|
||||
<Shuffle size={12} /> Auto-cast
|
||||
<Shuffle size={12} /> {t('casting.auto_cast')}
|
||||
</button>
|
||||
{allAssigned && (
|
||||
<span className="casting-view__badge">
|
||||
<Check size={10} /> All cast
|
||||
<Check size={10} /> {t('casting.all_cast')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -82,7 +84,7 @@ export default function CastingView({
|
||||
if (match) autoName = match;
|
||||
}
|
||||
const assignedProfile = isAuto
|
||||
? { name: `🎤 From video (${autoName})`, type: 'clone' }
|
||||
? { name: t('casting.from_video', { name: autoName }), type: 'clone' }
|
||||
: profiles.find(p => p.id === currentAssignment);
|
||||
|
||||
return (
|
||||
@@ -98,7 +100,7 @@ export default function CastingView({
|
||||
<div className="casting-row__info">
|
||||
<span className="casting-row__name">{speaker.label || speaker.id}</span>
|
||||
<span className="casting-row__meta">
|
||||
{speaker.segments_count || 0} segments
|
||||
{t('casting.segments_count', { count: speaker.segments_count || 0 })}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -119,7 +121,7 @@ export default function CastingView({
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="casting-row__unassigned">Assign voice…</span>
|
||||
<span className="casting-row__unassigned">{t('casting.assign_voice')}</span>
|
||||
</>
|
||||
)}
|
||||
<ChevronDown size={12} />
|
||||
@@ -167,9 +169,9 @@ export default function CastingView({
|
||||
))}
|
||||
|
||||
{profiles.length === 0 && !autoClones[speaker.id] && (
|
||||
<div className="casting-dropdown__empty">
|
||||
No voice profiles saved yet.
|
||||
</div>
|
||||
<div className="casting-dropdown__empty">
|
||||
{t('casting.no_profiles')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -180,7 +182,7 @@ export default function CastingView({
|
||||
<button
|
||||
className="casting-row__preview"
|
||||
onClick={() => onPreview(currentAssignment)}
|
||||
title="Preview voice"
|
||||
title={t('casting.preview_voice')}
|
||||
>
|
||||
<Volume2 size={12} />
|
||||
</button>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { CheckCircle, ArrowRight, X, Sparkles, Languages, Mic } from 'lucide-react';
|
||||
import { Button } from '../ui';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import './Misc.css';
|
||||
|
||||
/**
|
||||
@@ -16,62 +17,50 @@ import './Misc.css';
|
||||
* pipeline directly from the banner's CTA (translate, generate, etc).
|
||||
*/
|
||||
|
||||
const STAGE_CONFIG = {
|
||||
asr: {
|
||||
icon: Mic,
|
||||
accent: '#b8bb26',
|
||||
title: 'Transcripts ready',
|
||||
cta: 'Translate',
|
||||
ctaIcon: Languages,
|
||||
hint: 'Fix any ASR errors now — tight diction saves TTS attempts later.',
|
||||
},
|
||||
translate: {
|
||||
icon: Languages,
|
||||
accent: '#83a598',
|
||||
title: 'Translations ready',
|
||||
cta: 'Generate dub',
|
||||
ctaIcon: Sparkles,
|
||||
hint: 'Skim the target text. Over-length lines get speed-boosted; you can also edit directly.',
|
||||
},
|
||||
done: {
|
||||
icon: CheckCircle,
|
||||
accent: '#8ec07c',
|
||||
title: 'Dub complete',
|
||||
cta: null,
|
||||
hint: 'Review timing and sync ratios. Tweak any line and hit "Regen changed" for a fast partial redo.',
|
||||
},
|
||||
const STAGE_ICONS = {
|
||||
asr: { icon: Mic, accent: '#b8bb26', ctaIcon: Languages },
|
||||
translate: { icon: Languages, accent: '#83a598', ctaIcon: Sparkles },
|
||||
done: { icon: CheckCircle, accent: '#8ec07c' },
|
||||
};
|
||||
|
||||
const STAGE_KEYS = {
|
||||
asr: { title: 'checkpoint.asr_title', cta: 'checkpoint.asr_cta', hint: 'checkpoint.asr_hint' },
|
||||
translate: { title: 'checkpoint.translate_title', cta: 'checkpoint.translate_cta', hint: 'checkpoint.translate_hint' },
|
||||
done: { title: 'checkpoint.done_title', cta: null, hint: 'checkpoint.done_hint' },
|
||||
};
|
||||
|
||||
export default function CheckpointBanner({ stage, count, onContinue, onDismiss, continueLoading }) {
|
||||
const cfg = STAGE_CONFIG[stage];
|
||||
if (!cfg) return null;
|
||||
const { t } = useTranslation();
|
||||
const icons = STAGE_ICONS[stage];
|
||||
const keys = STAGE_KEYS[stage];
|
||||
if (!icons || !keys) return null;
|
||||
|
||||
const Icon = cfg.icon;
|
||||
const CtaIcon = cfg.ctaIcon;
|
||||
const Icon = icons.icon;
|
||||
const CtaIcon = icons.ctaIcon;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="checkpoint-banner ckpt-banner"
|
||||
style={{ borderLeft: `2px solid ${cfg.accent}` }}
|
||||
style={{ borderLeft: `2px solid ${icons.accent}` }}
|
||||
role="status"
|
||||
>
|
||||
<Icon size={14} color={cfg.accent} className="ckpt-icon" />
|
||||
<Icon size={14} color={icons.accent} className="ckpt-icon" />
|
||||
<div className="ckpt-body">
|
||||
<div className="ckpt-head">
|
||||
<span className="ckpt-title">
|
||||
{cfg.title}
|
||||
{t(keys.title)}
|
||||
</span>
|
||||
{typeof count === 'number' && (
|
||||
<span className="ckpt-count">
|
||||
{count} segment{count === 1 ? '' : 's'}
|
||||
{t('checkpoint.segment', { count })}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="ckpt-hint">
|
||||
{cfg.hint}
|
||||
{t(keys.hint)}
|
||||
</span>
|
||||
</div>
|
||||
{cfg.cta && onContinue && (
|
||||
{keys.cta && onContinue && (
|
||||
<Button
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
@@ -80,7 +69,7 @@ export default function CheckpointBanner({ stage, count, onContinue, onDismiss,
|
||||
leading={CtaIcon ? <CtaIcon size={10} /> : null}
|
||||
trailing={<ArrowRight size={10} />}
|
||||
>
|
||||
{cfg.cta}
|
||||
{t(keys.cta)}
|
||||
</Button>
|
||||
)}
|
||||
{onDismiss && (
|
||||
@@ -88,7 +77,7 @@ export default function CheckpointBanner({ stage, count, onContinue, onDismiss,
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={onDismiss}
|
||||
title="Dismiss — won't reappear for this stage until reload"
|
||||
title={t('checkpoint.dismiss_title')}
|
||||
iconSize="sm"
|
||||
>
|
||||
<X size={10} />
|
||||
|
||||
@@ -4,6 +4,7 @@ import { toast } from 'react-hot-toast';
|
||||
import { PRESETS } from '../utils/constants';
|
||||
import { generateSpeech } from '../api/generate';
|
||||
import { Button, Panel, Field, Textarea, Select } from '../ui';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import './CompareModal.css';
|
||||
|
||||
export default function CompareModal({
|
||||
@@ -20,6 +21,7 @@ export default function CompareModal({
|
||||
fileToMediaUrl, loadHistory,
|
||||
}) {
|
||||
const drawerRef = useRef(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
@@ -43,7 +45,7 @@ export default function CompareModal({
|
||||
setCompareResultB(null);
|
||||
|
||||
const generateVoice = async (voiceId) => {
|
||||
setCompareProgress('Preparing voice...');
|
||||
setCompareProgress(t('compare.preparing_voice'));
|
||||
const formData = new FormData();
|
||||
formData.append('text', compareText);
|
||||
let fin_prof = voiceId;
|
||||
@@ -72,17 +74,17 @@ export default function CompareModal({
|
||||
};
|
||||
|
||||
try {
|
||||
setCompareProgress('Generating Voice A...');
|
||||
setCompareProgress(t('compare.generating_voice_a'));
|
||||
const audioA = await generateVoice(compareVoiceA);
|
||||
setCompareResultA(audioA);
|
||||
setCompareProgress('Generating Voice B...');
|
||||
setCompareProgress(t('compare.generating_voice_b'));
|
||||
const audioB = await generateVoice(compareVoiceB);
|
||||
setCompareResultB(audioB);
|
||||
setCompareProgress('');
|
||||
toast.success('Comparison complete!');
|
||||
toast.success(t('compare.comparison_complete'));
|
||||
loadHistory();
|
||||
} catch (err) {
|
||||
toast.error('Play failed: ' + err.message);
|
||||
toast.error(t('compare.play_failed', { message: err.message }));
|
||||
setCompareProgress('');
|
||||
} finally {
|
||||
setIsComparing(false);
|
||||
@@ -94,18 +96,18 @@ export default function CompareModal({
|
||||
if (!open) return null;
|
||||
|
||||
return (
|
||||
<div className="compare-drawer" role="dialog" aria-modal="false" aria-label="A/B Voice Comparison">
|
||||
<div className="compare-drawer" role="dialog" aria-modal="false" aria-label={t('compare.title')}>
|
||||
<div className="compare-drawer__sheet" ref={drawerRef}>
|
||||
<header className="compare-drawer__head">
|
||||
<span className="compare-drawer__handle" aria-hidden="true" />
|
||||
<span className="compare-drawer__title">
|
||||
<Scale size={14} /> A/B Voice Comparison
|
||||
<Scale size={14} /> {t('compare.title')}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="compare-drawer__close"
|
||||
onClick={onClose}
|
||||
aria-label="Close comparison"
|
||||
aria-label={t('compare.close')}
|
||||
>
|
||||
<X size={12} />
|
||||
</button>
|
||||
@@ -113,10 +115,10 @@ export default function CompareModal({
|
||||
|
||||
<div className="compare-drawer__body">
|
||||
<p className="ui-compare__desc">
|
||||
Compare two voices side by side to make casting decisions. App stays interactive behind.
|
||||
{t('compare.desc')}
|
||||
</p>
|
||||
|
||||
<Field label="Test phrase">
|
||||
<Field label={t('compare.test_phrase')}>
|
||||
<Textarea
|
||||
value={compareText}
|
||||
onChange={e => setCompareText(e.target.value)}
|
||||
@@ -128,7 +130,7 @@ export default function CompareModal({
|
||||
<div className="ui-compare__grid">
|
||||
<CompareSide
|
||||
accent="var(--color-brand)"
|
||||
label="Voice A"
|
||||
label={t('compare.voice_a')}
|
||||
profiles={profiles}
|
||||
value={compareVoiceA}
|
||||
onChange={setCompareVoiceA}
|
||||
@@ -136,7 +138,7 @@ export default function CompareModal({
|
||||
/>
|
||||
<CompareSide
|
||||
accent="var(--color-success)"
|
||||
label="Voice B"
|
||||
label={t('compare.voice_b')}
|
||||
profiles={profiles}
|
||||
value={compareVoiceB}
|
||||
onChange={setCompareVoiceB}
|
||||
@@ -146,7 +148,7 @@ export default function CompareModal({
|
||||
</div>
|
||||
|
||||
<footer className="compare-drawer__foot">
|
||||
<Button variant="ghost" onClick={onClose}>Close</Button>
|
||||
<Button variant="ghost" onClick={onClose}>{t('compare.close_btn')}</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
loading={isComparing}
|
||||
@@ -154,7 +156,7 @@ export default function CompareModal({
|
||||
onClick={runCompare}
|
||||
leading={!isComparing && <Play size={12} />}
|
||||
>
|
||||
{isComparing ? (compareProgress || 'Comparing…') : 'Compare'}
|
||||
{isComparing ? (compareProgress || t('compare.comparing')) : t('compare.compare_btn')}
|
||||
</Button>
|
||||
</footer>
|
||||
</div>
|
||||
@@ -163,6 +165,7 @@ export default function CompareModal({
|
||||
}
|
||||
|
||||
function CompareSide({ accent, label, profiles, value, onChange, audio }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Panel variant="flat" padding="sm">
|
||||
<h3 className="ui-compare__head" style={{ color: accent }}>
|
||||
@@ -170,15 +173,15 @@ function CompareSide({ accent, label, profiles, value, onChange, audio }) {
|
||||
</h3>
|
||||
<Field>
|
||||
<Select value={value} onChange={e => onChange(e.target.value)}>
|
||||
<option value="">— Select voice —</option>
|
||||
<option value="">{t('compare.select_voice')}</option>
|
||||
{profiles.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}
|
||||
{PRESETS.map(p => <option key={p.id} value={`preset:${p.id}`}>{p.name} (Preset)</option>)}
|
||||
{PRESETS.map(p => <option key={p.id} value={`preset:${p.id}`}>{p.name} {t('compare.preset_suffix')}</option>)}
|
||||
</Select>
|
||||
</Field>
|
||||
{audio ? (
|
||||
<audio src={audio} controls className="ui-compare__audio" />
|
||||
) : (
|
||||
<div className="ui-compare__audio-empty">No audio yet</div>
|
||||
<div className="ui-compare__audio-empty">{t('compare.no_audio')}</div>
|
||||
)}
|
||||
</Panel>
|
||||
);
|
||||
|
||||
@@ -30,21 +30,21 @@ import './DictationDemo.css';
|
||||
const SCRIPTS = [
|
||||
{
|
||||
id: 'en_conversational',
|
||||
label: 'Conversational',
|
||||
labelKey: 'demo.script_conversational',
|
||||
language: 'English',
|
||||
text: 'Schedule a meeting with Pat for Tuesday at three PM and remind me to bring the quarterly report.',
|
||||
wav: '/demo_audio/dictation/en_conversational.wav',
|
||||
},
|
||||
{
|
||||
id: 'en_technical',
|
||||
label: 'Technical vocabulary',
|
||||
labelKey: 'demo.script_technical',
|
||||
language: 'English',
|
||||
text: 'Patch the WebGPU shader in renderer.tsx, then bump pnpm to nine point fifteen and rerun the Vitest suite.',
|
||||
wav: '/demo_audio/dictation/en_technical.wav',
|
||||
},
|
||||
{
|
||||
id: 'fr_reservation',
|
||||
label: 'Non-English (French)',
|
||||
labelKey: 'demo.script_french',
|
||||
language: 'French',
|
||||
text: 'Bonjour, je voudrais réserver une table pour deux personnes à vingt heures.',
|
||||
wav: '/demo_audio/dictation/fr_reservation.wav',
|
||||
@@ -218,7 +218,7 @@ export default function DictationDemo({ embedded = false }) {
|
||||
<div key={s.id} className="dictation-demo__card">
|
||||
<div className="dictation-demo__card-head">
|
||||
<span className="dictation-demo__lang">{s.language}</span>
|
||||
<span className="dictation-demo__card-label">{s.label}</span>
|
||||
<span className="dictation-demo__card-label">{t(s.labelKey)}</span>
|
||||
</div>
|
||||
<blockquote className="dictation-demo__script">{s.text}</blockquote>
|
||||
<div className="dictation-demo__card-actions">
|
||||
@@ -227,7 +227,7 @@ export default function DictationDemo({ embedded = false }) {
|
||||
variant="subtle"
|
||||
onClick={() => togglePlay(s)}
|
||||
leading={isPlaying ? <Pause size={11} /> : <Play size={11} />}
|
||||
aria-label={isPlaying ? `Pause ${s.label}` : `Hear ${s.label}`}
|
||||
aria-label={isPlaying ? t('demo.aria_pause', { label: t(s.labelKey) }) : t('demo.aria_hear', { label: t(s.labelKey) })}
|
||||
>
|
||||
{isPlaying ? t('demo.dictation_stop') : t('demo.dictation_hear')}
|
||||
</Button>
|
||||
@@ -237,7 +237,7 @@ export default function DictationDemo({ embedded = false }) {
|
||||
onClick={() => replay(s)}
|
||||
loading={tx.state === 'loading'}
|
||||
leading={tx.state !== 'loading' && <Mic size={11} />}
|
||||
aria-label={`Replay ${s.label} through transcriber`}
|
||||
aria-label={t('demo.aria_replay', { label: t(s.labelKey) })}
|
||||
>
|
||||
{tx.state === 'loading' ? t('demo.dictation_transcribing') : t('demo.dictation_replay')}
|
||||
</Button>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Sparkles, X } from 'lucide-react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Dialog, Button, Textarea, Field, Badge } from '../ui';
|
||||
import { apiPost } from '../api/client';
|
||||
import './Misc.css';
|
||||
@@ -17,6 +18,7 @@ import './Misc.css';
|
||||
* always the canonical input.
|
||||
*/
|
||||
export default function DirectionDialog({ open, seg, onSave, onClose }) {
|
||||
const { t } = useTranslation();
|
||||
const [text, setText] = useState('');
|
||||
const [preview, setPreview] = useState(null);
|
||||
const [parsing, setParsing] = useState(false);
|
||||
@@ -35,7 +37,7 @@ export default function DirectionDialog({ open, seg, onSave, onClose }) {
|
||||
try {
|
||||
setPreview(await apiPost('/tools/direction', { text }));
|
||||
} catch (e) {
|
||||
toast.error(`Preview failed: ${e.message}`);
|
||||
toast.error(t('direction.previewFailed', { message: e.message }));
|
||||
} finally {
|
||||
setParsing(false);
|
||||
}
|
||||
@@ -56,7 +58,7 @@ export default function DirectionDialog({ open, seg, onSave, onClose }) {
|
||||
open
|
||||
onClose={onClose}
|
||||
size="md"
|
||||
title={<><Sparkles size={14} /> Direction for segment #{seg?.id?.slice?.(0, 6) || ''}</>}
|
||||
title={<><Sparkles size={14} /> {t('direction.title', { id: seg?.id?.slice?.(0, 6) || '' })}</>}
|
||||
footer={
|
||||
<>
|
||||
{seg?.direction && (
|
||||
@@ -66,28 +68,26 @@ export default function DirectionDialog({ open, seg, onSave, onClose }) {
|
||||
leading={<X size={11} />}
|
||||
className="dir-clear-btn"
|
||||
>
|
||||
Clear
|
||||
{t('direction.clear')}
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="ghost" onClick={onClose}>Cancel</Button>
|
||||
<Button variant="primary" onClick={save} loading={saving}>Save direction</Button>
|
||||
<Button variant="ghost" onClick={onClose}>{t('direction.cancel')}</Button>
|
||||
<Button variant="primary" onClick={save} loading={saving}>{t('direction.saveDirection')}</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<p className="direction-dialog__desc">
|
||||
Tell the pipeline how this line should feel. Plain English works — the system
|
||||
maps your words onto a stable taxonomy (energy / emotion / pace / intimacy / formality),
|
||||
then threads the taxonomy through Cinematic translation, TTS, and slot-fit.
|
||||
{t('direction.desc')}
|
||||
</p>
|
||||
|
||||
<Field label="Direction" hint={
|
||||
seg?.text ? <>Line: <em>"{seg.text.slice(0, 80)}{seg.text.length > 80 ? '…' : ''}"</em></> : null
|
||||
<Field label={t('direction.label')} hint={
|
||||
seg?.text ? <>{t('direction.lineHint', { text: seg.text.slice(0, 80) + (seg.text.length > 80 ? '…' : '') })}</> : null
|
||||
}>
|
||||
<Textarea
|
||||
rows={3}
|
||||
value={text}
|
||||
onChange={e => setText(e.target.value)}
|
||||
placeholder="e.g. urgent and surprised / warm, hopeful / whispered, intimate"
|
||||
placeholder={t('direction.placeholder')}
|
||||
autoFocus
|
||||
/>
|
||||
</Field>
|
||||
@@ -99,7 +99,7 @@ export default function DirectionDialog({ open, seg, onSave, onClose }) {
|
||||
loading={parsing}
|
||||
disabled={!text.trim()}
|
||||
>
|
||||
Preview parse
|
||||
{t('direction.previewParse')}
|
||||
</Button>
|
||||
{preview && (
|
||||
<Badge tone={preview.method === 'llm' ? 'violet' : 'neutral'} size="xs">
|
||||
@@ -111,19 +111,19 @@ export default function DirectionDialog({ open, seg, onSave, onClose }) {
|
||||
{preview && (
|
||||
<div className="direction-dialog__preview">
|
||||
<div>
|
||||
<strong>TTS instruct:</strong> <code>{preview.instruct_prompt || '— (nothing parsed)'}</code>
|
||||
<strong>{t('direction.ttsInstruct')}</strong> <code>{preview.instruct_prompt || t('direction.nothingParsed')}</code>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Translate hint:</strong> <em>{preview.translate_hint || '—'}</em>
|
||||
<strong>{t('direction.translateHint')}</strong> <em>{preview.translate_hint || '—'}</em>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Rate bias:</strong> <code>{preview.rate_bias?.toFixed?.(2)}</code>
|
||||
{preview.rate_bias > 1.05 && <> · <span className="dir-rate-up">speeds up</span></>}
|
||||
{preview.rate_bias < 0.95 && <> · <span className="dir-rate-down">slows down</span></>}
|
||||
<strong>{t('direction.rateBias')}</strong> <code>{preview.rate_bias?.toFixed?.(2)}</code>
|
||||
{preview.rate_bias > 1.05 && <> · <span className="dir-rate-up">{t('direction.speedsUp')}</span></>}
|
||||
{preview.rate_bias < 0.95 && <> · <span className="dir-rate-down">{t('direction.slowsDown')}</span></>}
|
||||
</div>
|
||||
{Object.keys(preview.tokens || {}).length > 0 && (
|
||||
<details>
|
||||
<summary>taxonomy tokens</summary>
|
||||
<summary>{t('direction.taxonomyTokens')}</summary>
|
||||
<pre>{JSON.stringify(preview.tokens, null, 2)}</pre>
|
||||
</details>
|
||||
)}
|
||||
|
||||
@@ -75,32 +75,32 @@ function DubSegmentRow({
|
||||
let fitBadge = null;
|
||||
if (fitStatus) {
|
||||
if (fitStatus.status === 'fits') {
|
||||
fitBadge = { color: '#b8bb26', Icon: CheckCircle, label: 'Fits', title: 'Natural-rate audio fit inside the slot.' };
|
||||
fitBadge = { color: '#b8bb26', Icon: CheckCircle, label: t('segment.fit_fits'), title: t('segment.fit_fits_title') };
|
||||
} else if (fitStatus.status === 'overflows') {
|
||||
const over = fitStatus.overflow_s || 0;
|
||||
fitBadge = {
|
||||
color: over > 0.5 ? '#fb4934' : '#fabd2f',
|
||||
Icon: AlertCircle,
|
||||
label: `Overflows +${over.toFixed(2)}s`,
|
||||
title: `Translated text was longer than the original slot by ${over.toFixed(2)}s. The audio was hard-trimmed; shorten the text or switch Timing to "Stretch Video".`,
|
||||
label: t('segment.fit_overflows', { seconds: over.toFixed(2) }),
|
||||
title: t('segment.fit_overflows_title', { seconds: over.toFixed(2) }),
|
||||
};
|
||||
} else if (fitStatus.status === 'video_stretched') {
|
||||
const r = fitStatus.stretch_ratio || 1.0;
|
||||
fitBadge = {
|
||||
color: r > 1.18 ? '#fb4934' : r > 1.05 ? '#fabd2f' : '#83a598',
|
||||
Icon: Circle,
|
||||
label: `Video ${r.toFixed(2)}×`,
|
||||
title: `Stretch Video mode: this segment's video was slowed to ${r.toFixed(2)}× to fit the natural dub audio.`,
|
||||
label: t('segment.fit_stretched', { ratio: r.toFixed(2) }),
|
||||
title: t('segment.fit_stretched_title', { ratio: r.toFixed(2) }),
|
||||
};
|
||||
}
|
||||
} else if (seg.sync_ratio !== undefined) {
|
||||
const r = seg.sync_ratio;
|
||||
if (r > 1.25) {
|
||||
fitBadge = { color: '#fb4934', Icon: AlertCircle, label: `${Math.round(r * 100)}%`, title: `TTS audio is ${Math.round(r * 100)}% of the slot — heavily compressed.` };
|
||||
fitBadge = { color: '#fb4934', Icon: AlertCircle, label: `${Math.round(r * 100)}%`, title: t('segment.fit_compressed_title', { pct: Math.round(r * 100) }) };
|
||||
} else if (r >= 0.95 && r <= 1.05) {
|
||||
fitBadge = { color: '#b8bb26', Icon: CheckCircle, label: 'Fits', title: 'Audio fit inside the slot.' };
|
||||
fitBadge = { color: '#b8bb26', Icon: CheckCircle, label: t('segment.fit_fits'), title: t('segment.fit_audio_title') };
|
||||
} else {
|
||||
fitBadge = { color: '#fabd2f', Icon: Circle, label: `${Math.round(r * 100)}%`, title: `TTS audio is ${Math.round(r * 100)}% of the slot.` };
|
||||
fitBadge = { color: '#fabd2f', Icon: Circle, label: `${Math.round(r * 100)}%`, title: t('segment.fit_ratio_title', { pct: Math.round(r * 100) }) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ function DubSegmentRow({
|
||||
defaultValue={formatTime(seg.start)}
|
||||
key={`start-${seg.id}-${seg.start}`}
|
||||
disabled={disabled}
|
||||
title="Click to edit start time (m:ss.s). Enter to commit, Esc to cancel."
|
||||
title={t('segment.time_edit_title')}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') e.target.blur();
|
||||
|
||||
@@ -84,7 +84,7 @@ export default function DubbingDemo({ onDismiss }) {
|
||||
if (!manifest) {
|
||||
return (
|
||||
<div className="dubbing-demo dubbing-demo--loading">
|
||||
Loading dubbing demo…
|
||||
{t('demo.dubbing_loading')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -115,7 +115,7 @@ export default function DubbingDemo({ onDismiss }) {
|
||||
type="button"
|
||||
className="dubbing-demo__dismiss"
|
||||
onClick={onDismiss}
|
||||
aria-label="Dismiss dubbing demo"
|
||||
aria-label={t('demo.dubbing_dismiss')}
|
||||
>
|
||||
<X size={13} />
|
||||
</button>
|
||||
@@ -125,7 +125,7 @@ export default function DubbingDemo({ onDismiss }) {
|
||||
|
||||
<div className="dubbing-demo__players">
|
||||
<div className="dubbing-demo__pane">
|
||||
<div className="dubbing-demo__pane-label">{source.label} <span>· original</span></div>
|
||||
<div className="dubbing-demo__pane-label">{source.label} <span>· {t('demo.original_tag')}</span></div>
|
||||
<video
|
||||
ref={sourceRef}
|
||||
src={`${base}/${source.video}`}
|
||||
@@ -137,7 +137,7 @@ export default function DubbingDemo({ onDismiss }) {
|
||||
</div>
|
||||
<div className="dubbing-demo__pane">
|
||||
<div className="dubbing-demo__pane-label">
|
||||
{dubbed.label} <span>· dubbed</span>
|
||||
{dubbed.label} <span>· {t('demo.dubbed_tag')}</span>
|
||||
</div>
|
||||
<video
|
||||
ref={dubbedRef}
|
||||
|
||||
@@ -38,8 +38,25 @@
|
||||
|
||||
.engine-matrix__table {
|
||||
width: 100%;
|
||||
/* Responsive: the fixed-width columns (status/gpu/isolation/actions) +
|
||||
the flexible name column need ~840px. On a narrow Settings pane they used
|
||||
to collapse and OVERLAP (name text under the badges). Instead keep the
|
||||
table's shape and let it scroll horizontally — the data-table treatment
|
||||
used elsewhere — so every column stays legible at any width. */
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* Header + body share one min-width so columns stay aligned while scrolling. */
|
||||
.engine-matrix__table .ui-table-header,
|
||||
.engine-matrix__body {
|
||||
min-width: 840px;
|
||||
}
|
||||
|
||||
/* Fixed-width cells must not shrink below their column width (that shrink was
|
||||
what caused the overlap); the name column keeps a sane floor and still grows. */
|
||||
.engine-matrix__cell { flex-shrink: 0; }
|
||||
.engine-matrix__cell--name { min-width: 200px; }
|
||||
|
||||
.engine-matrix__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Cpu, Mic, MessageSquare, Activity, AlertTriangle, CheckCircle2, RefreshCw, Layers } from 'lucide-react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { listEngines, getEngineHealth } from '../api/engines';
|
||||
import { Badge, Button, Segmented, Table } from '../ui';
|
||||
import SupertonicLicenseDialog from './SupertonicLicenseDialog';
|
||||
@@ -75,14 +76,6 @@ const GPU_LABEL = {
|
||||
|
||||
const TEST_COOLDOWN_MS = 5000;
|
||||
|
||||
const COLUMNS = [
|
||||
{ key: 'name', label: 'Engine', flex: 3 },
|
||||
{ key: 'status', label: 'Install state', width: 130, align: 'center' },
|
||||
{ key: 'gpu', label: 'GPU compat', width: 170, align: 'left' },
|
||||
{ key: 'isolation', label: 'Isolation', width: 110, align: 'center' },
|
||||
{ key: 'action', label: 'Actions', width: 220, align: 'right' },
|
||||
];
|
||||
|
||||
/** Subset of the unified engine entry the matrix actually reads. */
|
||||
function normalizeEntry(entry) {
|
||||
return {
|
||||
@@ -108,6 +101,7 @@ export default function EngineCompatibilityMatrix({
|
||||
apiListEngines = listEngines,
|
||||
apiGetEngineHealth = getEngineHealth,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [data, setData] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
@@ -132,11 +126,11 @@ export default function EngineCompatibilityMatrix({
|
||||
} catch (e) {
|
||||
const msg = e?.message || String(e);
|
||||
setError(msg);
|
||||
toast.error(`Failed to load engines: ${msg}`);
|
||||
toast.error(t('engines.loadFailed', { message: msg }));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [apiListEngines]);
|
||||
}, [apiListEngines, t]);
|
||||
|
||||
useEffect(() => { reload(); }, [reload]);
|
||||
|
||||
@@ -188,19 +182,27 @@ export default function EngineCompatibilityMatrix({
|
||||
}
|
||||
}, [apiGetEngineHealth, healthByEngine]);
|
||||
|
||||
const COLUMNS = [
|
||||
{ key: 'name', label: t('engines.matrixTitle').split(' ')[0] || 'Engine', flex: 3 },
|
||||
{ key: 'status', label: t('engines.status'), width: 130, align: 'center' },
|
||||
{ key: 'gpu', label: 'GPU compat', width: 170, align: 'left' },
|
||||
{ key: 'isolation', label: 'Isolation', width: 110, align: 'center' },
|
||||
{ key: 'action', label: 'Actions', width: 220, align: 'right' },
|
||||
];
|
||||
|
||||
if (loading && !data) {
|
||||
return (
|
||||
<section className="engine-matrix engine-matrix--loading" aria-busy="true">
|
||||
<span className="engine-matrix__muted">Loading engines…</span>
|
||||
<span className="engine-matrix__muted">{t('engines.loading')}</span>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
if (error && !data) {
|
||||
return (
|
||||
<section className="engine-matrix engine-matrix--error" role="alert">
|
||||
<AlertTriangle size={14} /> Could not load engines: {error}
|
||||
<AlertTriangle size={14} /> {t('engines.couldNotLoad', { message: error })}
|
||||
<Button size="sm" variant="subtle" onClick={reload} leading={<RefreshCw size={11} />}>
|
||||
Retry
|
||||
{t('engines.retry')}
|
||||
</Button>
|
||||
</section>
|
||||
);
|
||||
@@ -213,7 +215,7 @@ export default function EngineCompatibilityMatrix({
|
||||
<section className="engine-matrix">
|
||||
<header className="engine-matrix__head">
|
||||
<h3 className="engine-matrix__title">
|
||||
<Layers size={14} /> Engine Compatibility Matrix
|
||||
<Layers size={14} /> {t('engines.matrixTitle')}
|
||||
</h3>
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -222,7 +224,7 @@ export default function EngineCompatibilityMatrix({
|
||||
loading={loading}
|
||||
leading={<RefreshCw size={11} />}
|
||||
>
|
||||
Refresh
|
||||
{t('engines.refresh')}
|
||||
</Button>
|
||||
</header>
|
||||
|
||||
@@ -233,7 +235,7 @@ export default function EngineCompatibilityMatrix({
|
||||
onChange={setActiveFamily}
|
||||
items={families.map((f) => ({
|
||||
value: f,
|
||||
title: `Active ${FAMILY_META[f].label}: ${data[f].active}`,
|
||||
title: t('engines.activeEngine', { family: FAMILY_META[f].label, engine: data[f].active }),
|
||||
label: (
|
||||
<span className="engine-matrix__tab-label">
|
||||
<span className="engine-matrix__tab-family">{FAMILY_META[f].label}</span>
|
||||
@@ -244,7 +246,7 @@ export default function EngineCompatibilityMatrix({
|
||||
/>
|
||||
)}
|
||||
|
||||
<Table className="engine-matrix__table" role="table" aria-label={`${activeFamily} engine compatibility`}>
|
||||
<Table className="engine-matrix__table" role="table" aria-label={t('engines.engineCompatLabel', { family: activeFamily })}>
|
||||
<Table.Header columns={COLUMNS} />
|
||||
<div className="engine-matrix__body" role="rowgroup">
|
||||
{backends.map((b) => {
|
||||
@@ -261,7 +263,7 @@ export default function EngineCompatibilityMatrix({
|
||||
<div role="cell" className="engine-matrix__cell engine-matrix__cell--name" style={{ flex: 3 }}>
|
||||
<span className="engine-matrix__name">
|
||||
{b.display_name}
|
||||
{isActive && <Badge tone="brand" size="xs">active</Badge>}
|
||||
{isActive && <Badge tone="brand" size="xs">{t('engines.active')}</Badge>}
|
||||
</span>
|
||||
<code className="engine-matrix__id">{b.id}</code>
|
||||
{/* For available rows, show install_hint inline (one line — usually
|
||||
@@ -275,7 +277,7 @@ export default function EngineCompatibilityMatrix({
|
||||
)}
|
||||
{!b.available && (b.reason || b.install_hint || b.last_error) && (
|
||||
<details className="engine-matrix__why">
|
||||
<summary className="engine-matrix__why-summary">Why unavailable?</summary>
|
||||
<summary className="engine-matrix__why-summary">{t('engines.whyUnavailable')}</summary>
|
||||
<div className="engine-matrix__why-body">
|
||||
{b.reason && (
|
||||
<span className="engine-matrix__reason">{b.reason}</span>
|
||||
@@ -285,7 +287,7 @@ export default function EngineCompatibilityMatrix({
|
||||
)}
|
||||
{b.last_error && b.last_error !== b.reason && (
|
||||
<span className="engine-matrix__last-error" data-testid="last-error">
|
||||
Last error: {b.last_error}
|
||||
{t('engines.lastError', { error: b.last_error })}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -298,11 +300,11 @@ export default function EngineCompatibilityMatrix({
|
||||
role="cell"
|
||||
className="engine-matrix__cell engine-matrix__cell--center"
|
||||
style={{ width: 130 }}
|
||||
title={b.available ? 'Installed and ready' : (b.reason || 'Not installed')}
|
||||
title={b.available ? t('engines.installedAndReady') : (b.reason || t('engines.notInstalled'))}
|
||||
>
|
||||
{b.available
|
||||
? <Badge tone="success" size="xs"><CheckCircle2 size={10} /> Available</Badge>
|
||||
: <Badge tone="warn" size="xs"><AlertTriangle size={10} /> Unavailable</Badge>}
|
||||
? <Badge tone="success" size="xs"><CheckCircle2 size={10} /> {t('engines.available')}</Badge>
|
||||
: <Badge tone="warn" size="xs"><AlertTriangle size={10} /> {t('engines.unavailable')}</Badge>}
|
||||
</div>
|
||||
|
||||
{/* GPU compat chips */}
|
||||
@@ -322,8 +324,8 @@ export default function EngineCompatibilityMatrix({
|
||||
className="engine-matrix__cell engine-matrix__cell--center"
|
||||
style={{ width: 110 }}
|
||||
title={b.isolation_mode === 'subprocess'
|
||||
? 'Runs in its own subprocess + venv'
|
||||
: 'Runs in the OmniVoice Python process'}
|
||||
? t('engines.subprocessTitle')
|
||||
: t('engines.inProcessTitle')}
|
||||
>
|
||||
<Badge tone={ISOLATION_TONE[b.isolation_mode] || 'neutral'} size="xs">
|
||||
{b.isolation_mode}
|
||||
@@ -350,7 +352,7 @@ export default function EngineCompatibilityMatrix({
|
||||
leading={!health?.inflight && <Activity size={11} />}
|
||||
aria-label={`Test ${b.display_name}`}
|
||||
>
|
||||
{health?.inflight ? 'Testing…' : 'Test engine'}
|
||||
{health?.inflight ? t('engines.testing') : t('engines.testEngine')}
|
||||
</Button>
|
||||
)}
|
||||
{!b.available && (
|
||||
@@ -363,7 +365,7 @@ export default function EngineCompatibilityMatrix({
|
||||
leading={!health?.inflight && <RefreshCw size={11} />}
|
||||
aria-label={`Re-check ${b.display_name}`}
|
||||
>
|
||||
{health?.inflight ? 'Re-checking…' : 'Re-check'}
|
||||
{health?.inflight ? t('engines.rechecking') : t('engines.recheck')}
|
||||
</Button>
|
||||
)}
|
||||
{health && !health.inflight && (
|
||||
@@ -373,8 +375,8 @@ export default function EngineCompatibilityMatrix({
|
||||
title={health.message}
|
||||
>
|
||||
{health.ok
|
||||
? `${health.latency_ms} ms`
|
||||
: `failed`}
|
||||
? t('engines.latencyMs', { ms: health.latency_ms })
|
||||
: t('engines.failed')}
|
||||
</span>
|
||||
)}
|
||||
{onSelect && b.available && !isActive && (
|
||||
@@ -384,7 +386,7 @@ export default function EngineCompatibilityMatrix({
|
||||
onClick={() => onSelect(activeFamily, b.id)}
|
||||
aria-label={`Use ${b.display_name}`}
|
||||
>
|
||||
Use
|
||||
{t('engines.use')}
|
||||
</Button>
|
||||
)}
|
||||
{/* TTS-05: license-acceptance entry point. Surfaced when
|
||||
@@ -401,7 +403,7 @@ export default function EngineCompatibilityMatrix({
|
||||
onClick={() => setLicenseDialogFor(b.id)}
|
||||
aria-label={`Review and accept ${b.display_name} license`}
|
||||
>
|
||||
Accept license
|
||||
{t('engines.acceptLicense')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -410,7 +412,7 @@ export default function EngineCompatibilityMatrix({
|
||||
})}
|
||||
{backends.length === 0 && (
|
||||
<div className="engine-matrix__empty" role="row">
|
||||
<span role="cell">No backends registered.</span>
|
||||
<span role="cell">{t('engines.noBackends')}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { AlertCircle, BookOpen, RefreshCw } from 'lucide-react';
|
||||
import i18next from 'i18next';
|
||||
import { classifyError, openDocsFor } from '../utils/errorDocsMap';
|
||||
import './WaveformErrorBoundary.css';
|
||||
|
||||
@@ -44,10 +45,10 @@ export default class ErrorBoundary extends React.Component {
|
||||
<div className="errbnd-card">
|
||||
<AlertCircle size={32} color="var(--chrome-severity-err)" className="errbnd-icon" />
|
||||
<h2 className="errbnd-title">
|
||||
This tab hit a snag.
|
||||
{i18next.t('errors.title')}
|
||||
</h2>
|
||||
<p className="errbnd-desc">
|
||||
Don't worry — the rest of the app still works. You can switch tabs, or try again below.
|
||||
{i18next.t('errors.desc')}
|
||||
</p>
|
||||
<pre className="errbnd-trace">{msg}</pre>
|
||||
<div className="errbnd-actions">
|
||||
@@ -55,15 +56,15 @@ export default class ErrorBoundary extends React.Component {
|
||||
onClick={this.reset}
|
||||
className="btn-primary errbnd-retry"
|
||||
>
|
||||
<RefreshCw size={12} /> Try again
|
||||
<RefreshCw size={12} /> {i18next.t('errors.tryAgain')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={this.openDocs}
|
||||
className="btn-secondary errbnd-docs"
|
||||
title="Open the docs page for this error in your browser"
|
||||
title={i18next.t('errors.openDocs')}
|
||||
>
|
||||
<BookOpen size={12} /> Open docs for this error
|
||||
<BookOpen size={12} /> {i18next.t('errors.openDocs')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { X, CheckCircle, AlertCircle } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAppStore } from '../store';
|
||||
import './FloatingPill.css';
|
||||
|
||||
@@ -35,6 +36,7 @@ const STAGE_LABELS = {
|
||||
};
|
||||
|
||||
export default function FloatingPill() {
|
||||
const { t } = useTranslation();
|
||||
const visible = useAppStore(s => s.visible);
|
||||
const stage = useAppStore(s => s.stage);
|
||||
const label = useAppStore(s => s.label);
|
||||
@@ -127,8 +129,8 @@ export default function FloatingPill() {
|
||||
<button
|
||||
className="floating-pill__dismiss"
|
||||
onClick={handleDismiss}
|
||||
title={cancellable ? 'Cancel' : 'Dismiss'}
|
||||
aria-label={cancellable ? 'Cancel operation' : 'Dismiss status'}
|
||||
title={cancellable ? t('common.cancel') : t('common.dismiss')}
|
||||
aria-label={cancellable ? t('common.cancelOp') : t('common.dismissStatus')}
|
||||
>
|
||||
<X size={12} />
|
||||
</button>
|
||||
|
||||
@@ -7,15 +7,15 @@ import NotificationPanel from './NotificationPanel';
|
||||
import { useAppStore } from '../store';
|
||||
|
||||
const VIEW_META = {
|
||||
launchpad: { label: 'Launchpad', Icon: Globe, accent: '#f3a5b6', kicker: 'Studio' },
|
||||
clone: { label: 'Voice Clone', Icon: Fingerprint, accent: '#d3869b', kicker: 'Studio' },
|
||||
design: { label: 'Voice Design', Icon: Wand2, accent: '#8ec07c', kicker: 'Studio' },
|
||||
dub: { label: 'Dubbing', Icon: Film, accent: '#fe8019', kicker: 'Studio' },
|
||||
projects: { label: 'OmniDrive', Icon: FolderOpen, accent: '#83a598', kicker: 'Library' },
|
||||
gallery: { label: 'Gallery', Icon: Library, accent: '#b8bb26', kicker: 'Library' },
|
||||
transcriptions: { label: 'Transcriptions', Icon: FileText, accent: '#d3869b', kicker: 'Library' },
|
||||
settings: { label: 'Settings', Icon: Settings2, accent: '#fabd2f', kicker: 'Preferences' },
|
||||
enterprise: { label: 'Commercial License', Icon: Building2, accent: '#fe8019', kicker: 'Licensing' },
|
||||
launchpad: { labelKey: 'header.label_launchpad', Icon: Globe, accent: '#f3a5b6', kickerKey: 'header.kicker_studio' },
|
||||
clone: { labelKey: 'header.label_clone', Icon: Fingerprint, accent: '#d3869b', kickerKey: 'header.kicker_studio' },
|
||||
design: { labelKey: 'header.label_design', Icon: Wand2, accent: '#8ec07c', kickerKey: 'header.kicker_studio' },
|
||||
dub: { labelKey: 'header.label_dub', Icon: Film, accent: '#fe8019', kickerKey: 'header.kicker_studio' },
|
||||
projects: { labelKey: 'header.label_projects', Icon: FolderOpen, accent: '#83a598', kickerKey: 'header.kicker_library' },
|
||||
gallery: { labelKey: 'header.label_gallery', Icon: Library, accent: '#b8bb26', kickerKey: 'header.kicker_library' },
|
||||
transcriptions: { labelKey: 'header.label_transcriptions', Icon: FileText, accent: '#d3869b', kickerKey: 'header.kicker_library' },
|
||||
settings: { labelKey: 'header.label_settings', Icon: Settings2, accent: '#fabd2f', kickerKey: 'header.kicker_preferences' },
|
||||
enterprise: { labelKey: 'header.label_enterprise', Icon: Building2, accent: '#fe8019', kickerKey: 'header.kicker_licensing' },
|
||||
};
|
||||
|
||||
function WaveBars({ color = '#f3a5b6', active }) {
|
||||
@@ -148,11 +148,11 @@ export default function Header({
|
||||
<div className="hq-col-left__spacer" />
|
||||
<div className="hq-view-title">
|
||||
<span className="hq-view-dot" style={dotStyle} />
|
||||
<span className="hq-view-kicker">{view.kicker}</span>
|
||||
<span className="hq-view-kicker">{t(view.kickerKey)}</span>
|
||||
<ChevronRight size={10} color="#504945" className="hq-breadcrumb-sep" />
|
||||
<span className="hq-view-label" style={labelStyle}>
|
||||
<ViewIcon size={12} className="hq-view-icon" />
|
||||
{view.label}
|
||||
{t(view.labelKey)}
|
||||
</span>
|
||||
{activeProjectName ? (
|
||||
<>
|
||||
@@ -212,7 +212,7 @@ export default function Header({
|
||||
dot
|
||||
className={`hq-stats__status-badge ${modelStatus === 'loading' ? 'ui-badge--pulse' : ''}`}
|
||||
>
|
||||
{modelStatus === 'ready' ? 'Ready' : modelStatus === 'loading' ? 'Loading…' : 'Idle'}
|
||||
{modelStatus === 'ready' ? t('header.status_ready') : modelStatus === 'loading' ? t('header.status_loading') : t('header.status_idle')}
|
||||
</Badge>
|
||||
</span>
|
||||
{onFlushMemory && (
|
||||
@@ -221,14 +221,14 @@ export default function Header({
|
||||
ref={flushBtnRef}
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
title="Memory management"
|
||||
title={t('header.memory_management')}
|
||||
loading={flushing}
|
||||
leading={!flushing && <Zap size={8} />}
|
||||
trailing={<ChevronDown size={8} />}
|
||||
onClick={() => setFlushOpen(o => !o)}
|
||||
className="hq-flush-btn"
|
||||
>
|
||||
Flush
|
||||
{t('header.flush')}
|
||||
</Button>
|
||||
{flushOpen && createPortal(
|
||||
<div
|
||||
@@ -236,9 +236,9 @@ export default function Header({
|
||||
style={{ top: dropdownPos.top, left: dropdownPos.left }}
|
||||
ref={dropdownRef}
|
||||
>
|
||||
<div className="hq-flush-dropdown__header">Loaded Models</div>
|
||||
<div className="hq-flush-dropdown__header">{t('header.loaded_models')}</div>
|
||||
{loadedModels.length === 0 ? (
|
||||
<div className="hq-flush-dropdown__empty">No models loaded</div>
|
||||
<div className="hq-flush-dropdown__empty">{t('header.no_models')}</div>
|
||||
) : (
|
||||
loadedModels.map(m => (
|
||||
<div key={m.id} className="hq-flush-dropdown__item">
|
||||
@@ -255,7 +255,7 @@ export default function Header({
|
||||
disabled={unloading === m.id}
|
||||
aria-label={`Unload ${m.name}`}
|
||||
>
|
||||
{unloading === m.id ? '…' : 'Unload'}
|
||||
{unloading === m.id ? '…' : t('header.unload')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -270,7 +270,7 @@ export default function Header({
|
||||
try { await onFlushMemory(false); } finally { setFlushing(false); }
|
||||
}}
|
||||
>
|
||||
<Zap size={10} /> Flush caches
|
||||
<Zap size={10} /> {t('header.flush_caches')}
|
||||
</button>
|
||||
<button
|
||||
className="hq-flush-dropdown__action hq-flush-dropdown__action--danger"
|
||||
@@ -280,7 +280,7 @@ export default function Header({
|
||||
try { await onFlushMemory(true); } finally { setFlushing(false); }
|
||||
}}
|
||||
>
|
||||
<Trash2 size={10} /> Unload all + flush
|
||||
<Trash2 size={10} /> {t('header.unload_all_flush')}
|
||||
</button>
|
||||
</div>,
|
||||
document.body
|
||||
|
||||
@@ -1,54 +1,57 @@
|
||||
import React from 'react';
|
||||
import { Command, X } from 'lucide-react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import './KeyboardCheatsheet.css';
|
||||
|
||||
const SECTIONS = [
|
||||
{
|
||||
title: 'Navigation',
|
||||
items: [
|
||||
['?', 'Show this cheatsheet'],
|
||||
['Esc', 'Close modal / cancel'],
|
||||
['Cmd/Ctrl+S', 'Save project / commit trim'],
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Segment editor',
|
||||
items: [
|
||||
['Cmd/Ctrl+D', 'Split segment at cursor'],
|
||||
['Cmd/Ctrl+M', 'Merge with next segment'],
|
||||
['Cmd/Ctrl+Z', 'Undo'],
|
||||
['Cmd/Ctrl+Shift+Z', 'Redo'],
|
||||
['Click row', 'Primary action'],
|
||||
['Shift+click row', 'Range select'],
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Audio trimmer',
|
||||
items: [
|
||||
['Space', 'Preview play / pause'],
|
||||
['← / →', 'Nudge start handle'],
|
||||
['Ctrl+← / →', 'Nudge end handle'],
|
||||
['Shift+arrow', 'Fine nudge'],
|
||||
['Alt+arrow', 'Coarse nudge'],
|
||||
['+ / −', 'Zoom in / out'],
|
||||
['Home / End', 'Fit all / Fit selection'],
|
||||
['Enter', 'Confirm trim'],
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Dub',
|
||||
items: [
|
||||
['Cmd/Ctrl+Enter', 'Generate dub'],
|
||||
['Cmd/Ctrl+B', 'Toggle sidebar'],
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
function Kbd({ children }) {
|
||||
return <span className="kcs-kbd">{children}</span>;
|
||||
}
|
||||
|
||||
export default function KeyboardCheatsheet({ open, onClose }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const SECTIONS = [
|
||||
{
|
||||
title: t('keyboard.nav'),
|
||||
items: [
|
||||
['?', t('keyboard.nav_cheatsheet')],
|
||||
['Esc', t('keyboard.nav_closeModal')],
|
||||
['Cmd/Ctrl+S', t('keyboard.nav_save')],
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t('keyboard.segmentEditor'),
|
||||
items: [
|
||||
['Cmd/Ctrl+D', t('keyboard.seg_split')],
|
||||
['Cmd/Ctrl+M', t('keyboard.seg_merge')],
|
||||
['Cmd/Ctrl+Z', t('keyboard.seg_undo')],
|
||||
['Cmd/Ctrl+Shift+Z', t('keyboard.seg_redo')],
|
||||
['Click row', t('keyboard.seg_click')],
|
||||
['Shift+click row', t('keyboard.seg_shiftClick')],
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t('keyboard.trimmer'),
|
||||
items: [
|
||||
['Space', t('keyboard.trim_playPause')],
|
||||
['← / →', t('keyboard.trim_nudgeStart')],
|
||||
['Ctrl+← / →', t('keyboard.trim_nudgeEnd')],
|
||||
['Shift+arrow', t('keyboard.trim_fineNudge')],
|
||||
['Alt+arrow', t('keyboard.trim_coarseNudge')],
|
||||
['+ / −', t('keyboard.trim_zoomIn')],
|
||||
['Home / End', t('keyboard.trim_fitAll')],
|
||||
['Enter', t('keyboard.trim_confirm')],
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t('keyboard.dub'),
|
||||
items: [
|
||||
['Cmd/Ctrl+Enter', t('keyboard.dub_generate')],
|
||||
['Cmd/Ctrl+B', t('keyboard.dub_sidebar')],
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
if (!open) return null;
|
||||
return (
|
||||
<div onClick={onClose} className="kcs-overlay">
|
||||
@@ -56,7 +59,7 @@ export default function KeyboardCheatsheet({ open, onClose }) {
|
||||
<div className="kcs-header">
|
||||
<div className="kcs-header__left">
|
||||
<Command size={16} color="var(--chrome-accent)" />
|
||||
<h2 className="kcs-title">Keyboard shortcuts</h2>
|
||||
<h2 className="kcs-title">{t('keyboard.title')}</h2>
|
||||
</div>
|
||||
<button onClick={onClose} className="kcs-close">
|
||||
<X size={16} />
|
||||
@@ -77,7 +80,7 @@ export default function KeyboardCheatsheet({ open, onClose }) {
|
||||
<span className="kcs-key-group">
|
||||
{group.split('+').map((k) => <Kbd key={k}>{k}</Kbd>)}
|
||||
</span>
|
||||
{i < arr.length - 1 && <span className="kcs-or">or</span>}
|
||||
{i < arr.length - 1 && <span className="kcs-or">{t('keyboard.or')}</span>}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</span>
|
||||
@@ -89,7 +92,9 @@ export default function KeyboardCheatsheet({ open, onClose }) {
|
||||
</div>
|
||||
|
||||
<div className="kcs-footer">
|
||||
Press <Kbd>?</Kbd> any time to open this.
|
||||
<Trans i18nKey="keyboard.footer" components={{ 1: <Kbd /> }}>
|
||||
{'Press <1>?</1> any time to open this.'}
|
||||
</Trans>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,12 +2,16 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { copyText } from "../utils/copyText";
|
||||
import {
|
||||
ChevronUp, ChevronDown, RefreshCw, Trash2, Copy, Bug, X,
|
||||
AlertTriangle, AlertCircle, Info, FileText, Heart,
|
||||
AlertTriangle, AlertCircle, Info, FileText, Heart, Download,
|
||||
} from 'lucide-react';
|
||||
import UpdatesPanel from './UpdatesPanel';
|
||||
import UpdateStatusChip from './UpdateStatusChip';
|
||||
import toast from 'react-hot-toast';
|
||||
import { clearSystemLogs, clearTauriLogs } from '../api/system';
|
||||
import { useSystemLogs, useTauriLogs, useClearLogs, useClearTauriLogs } from '../api/hooks';
|
||||
import { getFrontendLogs, clearFrontendLogs } from '../utils/consoleBuffer';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAppStore } from '../store';
|
||||
import NetworkToggle from './NetworkToggle';
|
||||
import './LogsFooter.css';
|
||||
|
||||
@@ -23,6 +27,7 @@ const SOURCES = [
|
||||
{ id: 'backend', label: 'Backend', icon: FileText },
|
||||
{ id: 'frontend', label: 'Frontend', icon: FileText },
|
||||
{ id: 'tauri', label: 'Tauri', icon: FileText },
|
||||
{ id: 'updates', label: 'Updates', icon: Download },
|
||||
// Notifications used to live here as a 4th pill but that duplicated the
|
||||
// header's bell+badge (single source of truth for notifications). The
|
||||
// footer is logs-only now; bell handles notifications.
|
||||
@@ -137,6 +142,7 @@ export default function LogsFooter() {
|
||||
localStorage.removeItem('omnivoice.logs.collapsed');
|
||||
}
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
const { t } = useTranslation();
|
||||
const [height, setHeight] = useState(() => {
|
||||
const v = Number(localStorage.getItem(LS_HEIGHT));
|
||||
return Number.isFinite(v) && v >= MIN_H && v <= MAX_H ? v : 300;
|
||||
@@ -250,6 +256,7 @@ export default function LogsFooter() {
|
||||
backend: countLevels(lines.backend),
|
||||
frontend: countLevels(lines.frontend),
|
||||
tauri: countLevels(lines.tauri),
|
||||
updates: { error: 0, warn: 0, total: 0 },
|
||||
notifications: {
|
||||
error: notifications.filter(n => n.level === 'error').length,
|
||||
warn: notifications.filter(n => n.level === 'warn').length,
|
||||
@@ -285,9 +292,9 @@ export default function LogsFooter() {
|
||||
else if (active === 'tauri') await clearTauriLogs();
|
||||
else if (active === 'frontend') clearFrontendLogs();
|
||||
setLines(prev => ({ ...prev, [active]: [] }));
|
||||
toast.success(`${active} log cleared`);
|
||||
toast.success(t('logs.log_cleared', { source: active }));
|
||||
} catch (e) {
|
||||
toast.error(`Clear failed: ${e?.message || e}`);
|
||||
toast.error(t('logs.clear_failed', { message: e?.message || e }));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -295,9 +302,9 @@ export default function LogsFooter() {
|
||||
try {
|
||||
const raw = (lines[active] || []).join('\n');
|
||||
await copyText(raw);
|
||||
toast.success(`Copied ${active} log`);
|
||||
toast.success(t('logs.log_copied', { source: active }));
|
||||
} catch (e) {
|
||||
toast.error(`Copy failed: ${e?.message || e}`);
|
||||
toast.error(t('logs.copy_failed', { message: e?.message || e }));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -321,9 +328,9 @@ export default function LogsFooter() {
|
||||
}).join('\n\n');
|
||||
try {
|
||||
await copyText(header + body);
|
||||
toast.success('Diagnostic report copied — paste it into a GitHub issue.');
|
||||
toast.success(t('logs.report_copied'));
|
||||
} catch (e) {
|
||||
toast.error(`Report failed: ${e?.message || e}`);
|
||||
toast.error(t('logs.report_failed', { message: e?.message || e }));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -339,7 +346,7 @@ export default function LogsFooter() {
|
||||
ref={dragRef}
|
||||
className="logs-footer__resize"
|
||||
onMouseDown={onDragStart}
|
||||
title="Drag to resize"
|
||||
title={t('logs.drag_resize')}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -352,13 +359,13 @@ export default function LogsFooter() {
|
||||
type="button"
|
||||
className="logs-footer__toggle"
|
||||
onClick={() => setCollapsed(c => !c)}
|
||||
title={collapsed ? 'Expand logs' : 'Collapse logs'}
|
||||
aria-label={collapsed ? 'Expand logs panel' : 'Collapse logs panel'}
|
||||
title={collapsed ? t('logs.expand') : t('logs.collapse')}
|
||||
aria-label={collapsed ? t('logs.expand_aria') : t('logs.collapse_aria')}
|
||||
aria-expanded={!collapsed}
|
||||
>
|
||||
{collapsed ? <ChevronUp size={12} /> : <ChevronDown size={12} />}
|
||||
</button>
|
||||
<span className="logs-footer__title">Logs</span>
|
||||
<span className="logs-footer__title">{t('logs.title')}</span>
|
||||
{SOURCES.map(s => (
|
||||
<SourcePill
|
||||
key={s.id}
|
||||
@@ -372,30 +379,31 @@ export default function LogsFooter() {
|
||||
<div className="logs-footer__right">
|
||||
{!collapsed && (
|
||||
<div className="logs-footer__actions">
|
||||
<button className="logs-footer__icon-btn" onClick={refreshAll} disabled={loading} title="Refresh" aria-label="Refresh logs">
|
||||
<button className="logs-footer__icon-btn" onClick={refreshAll} disabled={loading} title={t('logs.refresh')} aria-label={t('logs.refresh_aria')}>
|
||||
<RefreshCw size={12} className={loading ? 'spinner' : ''} />
|
||||
</button>
|
||||
<button className="logs-footer__icon-btn" onClick={onCopy} title="Copy visible log" aria-label="Copy visible log">
|
||||
<button className="logs-footer__icon-btn" onClick={onCopy} title={t('logs.copy_visible')} aria-label={t('logs.copy_visible_aria')}>
|
||||
<Copy size={12} />
|
||||
</button>
|
||||
<button className="logs-footer__icon-btn" onClick={onClear} title="Clear" aria-label="Clear log">
|
||||
<button className="logs-footer__icon-btn" onClick={onClear} title={t('logs.clear')} aria-label={t('logs.clear_aria')}>
|
||||
<Trash2 size={12} />
|
||||
</button>
|
||||
<button className="logs-footer__icon-btn logs-footer__icon-btn--report" onClick={onReportIssue} title="Report issue (copy diagnostic)" aria-label="Report issue">
|
||||
<button className="logs-footer__icon-btn logs-footer__icon-btn--report" onClick={onReportIssue} title={t('logs.report_issue')} aria-label={t('logs.report_issue_aria')}>
|
||||
<Bug size={12} />
|
||||
</button>
|
||||
<button className="logs-footer__icon-btn" onClick={() => setCollapsed(true)} title="Close" aria-label="Close logs panel">
|
||||
<button className="logs-footer__icon-btn" onClick={() => setCollapsed(true)} title={t('logs.close')} aria-label={t('logs.close_aria')}>
|
||||
<X size={12} />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<UpdateStatusChip onOpen={() => openTo('updates')} />
|
||||
<NetworkToggle />
|
||||
<button
|
||||
type="button"
|
||||
className="logs-footer__discord"
|
||||
onClick={() => { import('../api/external').then(m => m.openExternal('https://discord.gg/bzQavDfVV9')); }}
|
||||
title="Join our Discord"
|
||||
aria-label="Join our Discord community"
|
||||
title={t('logs.join_discord')}
|
||||
aria-label={t('logs.join_discord_aria')}
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.947 2.418-2.157 2.418z"/></svg>
|
||||
</button>
|
||||
@@ -403,19 +411,25 @@ export default function LogsFooter() {
|
||||
type="button"
|
||||
className="logs-footer__donate"
|
||||
onClick={() => useAppStore.getState().setMode?.('donate')}
|
||||
title="Support this project"
|
||||
aria-label="Support this project"
|
||||
title={t('logs.support_project')}
|
||||
aria-label={t('logs.support_project_aria')}
|
||||
>
|
||||
<DonateHeart />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!collapsed && active !== 'notifications' && (
|
||||
{!collapsed && active === 'updates' && (
|
||||
<div className="logs-footer__body">
|
||||
<UpdatesPanel />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!collapsed && active !== 'notifications' && active !== 'updates' && (
|
||||
<div ref={scrollRef} className="logs-footer__body">
|
||||
{current.length === 0 && (
|
||||
<div className="logs-footer__empty">
|
||||
{active === 'frontend' ? 'No frontend console output yet.' : 'No lines.'}
|
||||
{active === 'frontend' ? t('logs.empty_frontend_short') : t('logs.empty_lines')}
|
||||
</div>
|
||||
)}
|
||||
{current.map((line, i) => {
|
||||
@@ -434,7 +448,7 @@ export default function LogsFooter() {
|
||||
<div className="logs-footer__body logs-footer__notif-body">
|
||||
{notifications.length === 0 ? (
|
||||
<div className="logs-footer__empty">
|
||||
✅ All clear — no issues detected
|
||||
{t('logs.all_clear')}
|
||||
</div>
|
||||
) : (
|
||||
notifications.map(notif => (
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useState, useMemo, useRef, useEffect } from 'react';
|
||||
import { X, Search, Globe, Plus } from 'lucide-react';
|
||||
import { POPULAR_LANGS } from '../utils/constants';
|
||||
import { LANG_CODES } from '../utils/languages';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import './MultiLangPicker.css';
|
||||
|
||||
/**
|
||||
@@ -15,6 +16,7 @@ export default function MultiLangPicker({
|
||||
onChange, // (newSelected) => void
|
||||
disabled = false,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [dropOpen, setDropOpen] = useState(false);
|
||||
const [query, setQuery] = useState('');
|
||||
const dropRef = useRef(null);
|
||||
@@ -89,7 +91,7 @@ export default function MultiLangPicker({
|
||||
type="button"
|
||||
className="multi-lang__add"
|
||||
onClick={() => setDropOpen(!dropOpen)}
|
||||
title="Add language"
|
||||
title={t('dub.add_language')}
|
||||
>
|
||||
<Plus size={10} />
|
||||
</button>
|
||||
@@ -98,7 +100,7 @@ export default function MultiLangPicker({
|
||||
|
||||
{selected.length > 0 && (
|
||||
<div className="multi-lang__summary">
|
||||
{selected.length} language{selected.length > 1 ? 's' : ''} selected
|
||||
{t('dub.languages_selected', { count: selected.length })}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -110,14 +112,14 @@ export default function MultiLangPicker({
|
||||
ref={inputRef}
|
||||
value={query}
|
||||
onChange={e => setQuery(e.target.value)}
|
||||
placeholder="Search languages…"
|
||||
placeholder={t('dub.search_languages')}
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
<div className="multi-lang__list">
|
||||
{popularFiltered.length > 0 && (
|
||||
<>
|
||||
<div className="multi-lang__section">Popular</div>
|
||||
<div className="multi-lang__section">{t('dub.popular')}</div>
|
||||
{popularFiltered.map(item => (
|
||||
<button
|
||||
key={item.code}
|
||||
@@ -131,7 +133,7 @@ export default function MultiLangPicker({
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
<div className="multi-lang__section">All Languages</div>
|
||||
<div className="multi-lang__section">{t('dub.all_languages')}</div>
|
||||
{filteredLangs.slice(0, 50).map(lc => (
|
||||
<button
|
||||
key={lc.code}
|
||||
@@ -145,11 +147,11 @@ export default function MultiLangPicker({
|
||||
))}
|
||||
{filteredLangs.length > 50 && (
|
||||
<div className="multi-lang__more">
|
||||
+{filteredLangs.length - 50} more — type to narrow
|
||||
{t('dub.more_to_narrow', { count: filteredLangs.length - 50 })}
|
||||
</div>
|
||||
)}
|
||||
{filteredLangs.length === 0 && popularFiltered.length === 0 && (
|
||||
<div className="multi-lang__empty">No matches</div>
|
||||
<div className="multi-lang__empty">{t('dub.no_matches')}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -54,8 +54,8 @@ export default function NavRail({ mode, setMode, side = 'left', onFlipSide }) {
|
||||
))}
|
||||
<button
|
||||
onClick={onFlipSide}
|
||||
title={`Move rail to the ${side === 'left' ? 'right' : 'left'}`}
|
||||
aria-label="Flip rail side"
|
||||
title={side === 'left' ? t('nav.move_rail_right') : t('nav.move_rail_left')}
|
||||
aria-label={t('nav.flip_rail')}
|
||||
className="rail-btn rail-flip"
|
||||
>
|
||||
<ArrowLeftRight size={15} />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// frontend/src/components/NetworkToggle.jsx
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { copyText } from "../utils/copyText";
|
||||
import QRCode from 'qrcode';
|
||||
import { Wifi, WifiOff, Copy, ExternalLink } from 'lucide-react';
|
||||
@@ -9,6 +10,7 @@ import { openExternal } from '../api/external';
|
||||
import './NetworkToggle.css';
|
||||
|
||||
export default function NetworkToggle() {
|
||||
const { t } = useTranslation();
|
||||
const [st, setSt] = useState({ enabled: false });
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
@@ -38,17 +40,17 @@ export default function NetworkToggle() {
|
||||
const enable = async () => {
|
||||
setBusy(true);
|
||||
try { setSt(await apiPost('/system/network/enable')); setConfirming(false); setOpen(true); }
|
||||
catch (e) { toast.error(`Could not enable sharing: ${e.message}`); }
|
||||
catch (e) { toast.error(t('network.enable_error', { message: e.message })); }
|
||||
finally { setBusy(false); }
|
||||
};
|
||||
const disable = async () => {
|
||||
setBusy(true);
|
||||
try { await apiPost('/system/network/disable'); await refresh(); setOpen(false); }
|
||||
catch (e) { toast.error(`Could not disable: ${e.message}`); }
|
||||
catch (e) { toast.error(t('network.disable_error', { message: e.message })); }
|
||||
finally { setBusy(false); }
|
||||
};
|
||||
|
||||
const copy = (text) => { copyText(text); toast.success('Copied'); };
|
||||
const copy = (text) => { copyText(text); toast.success(t('network.copied')); };
|
||||
|
||||
return (
|
||||
<div className="net-toggle">
|
||||
@@ -56,31 +58,30 @@ export default function NetworkToggle() {
|
||||
className={`net-toggle__pill ${st.enabled ? 'net-toggle__pill--on' : ''}`}
|
||||
onClick={st.enabled ? () => setOpen((o) => !o) : () => setConfirming((c) => !c)}
|
||||
disabled={busy}
|
||||
title={st.enabled ? 'Sharing on — click for details' : 'Share on your network'}
|
||||
title={st.enabled ? t('network.sharing_on_title') : t('network.share_on_network')}
|
||||
>
|
||||
{st.enabled ? <Wifi size={12} /> : <WifiOff size={12} />}
|
||||
<span>{busy ? 'Switching…' : st.enabled ? 'Network' : 'Local'}</span>
|
||||
<span>{busy ? t('network.switching') : st.enabled ? t('network.network') : t('network.local')}</span>
|
||||
</button>
|
||||
|
||||
{!st.enabled && confirming && (
|
||||
<div className="net-toggle__panel net-toggle__panel--confirm">
|
||||
<div className="net-toggle__panel-title">Share on your network?</div>
|
||||
<div className="net-toggle__panel-title">{t('network.share_confirm_title')}</div>
|
||||
<p className="net-toggle__hint">
|
||||
Other devices on your Wi-Fi/Ethernet will be able to reach OmniVoice
|
||||
using the access PIN shown once it's on.
|
||||
{t('network.share_confirm_hint')}
|
||||
</p>
|
||||
<div className="net-toggle__confirm-actions">
|
||||
<button type="button" className="net-toggle__cancel" onClick={() => setConfirming(false)} disabled={busy}>Cancel</button>
|
||||
<button type="button" className="net-toggle__enable" onClick={enable} disabled={busy}>{busy ? 'Enabling…' : 'Enable'}</button>
|
||||
<button type="button" className="net-toggle__cancel" onClick={() => setConfirming(false)} disabled={busy}>{t('common.cancel')}</button>
|
||||
<button type="button" className="net-toggle__enable" onClick={enable} disabled={busy}>{busy ? t('network.enabling') : t('network.enable')}</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{st.enabled && open && (
|
||||
<div className="net-toggle__panel">
|
||||
<div className="net-toggle__panel-title">Shared on your network</div>
|
||||
<div className="net-toggle__panel-title">{t('network.shared_title')}</div>
|
||||
{(st.lan_addresses || []).length === 0 && (
|
||||
<p className="net-toggle__hint">No reachable network interface — connect to Wi-Fi/Ethernet.</p>
|
||||
<p className="net-toggle__hint">{t('network.no_interface')}</p>
|
||||
)}
|
||||
{(st.lan_addresses || []).map((ip) => {
|
||||
const url = `http://${ip}:${st.share_port}/?pin=${st.pin}`;
|
||||
@@ -89,18 +90,19 @@ export default function NetworkToggle() {
|
||||
<div className="net-toggle__row-main">
|
||||
<code className="net-toggle__addr">{ip}:{st.share_port}</code>
|
||||
<div className="net-toggle__row-actions">
|
||||
<button type="button" className="net-toggle__iconbtn" onClick={() => copy(url)} aria-label={`Copy ${ip}`} title="Copy link"><Copy size={12} /></button>
|
||||
<button type="button" className="net-toggle__iconbtn" onClick={() => openExternal(url)} aria-label={`Open ${ip}`} title="Open in browser"><ExternalLink size={12} /></button>
|
||||
<button type="button" className="net-toggle__iconbtn" onClick={() => copy(url)} aria-label={`Copy ${ip}`} title={t('network.copy_link')}><Copy size={12} /></button>
|
||||
<button type="button" className="net-toggle__iconbtn" onClick={() => openExternal(url)} aria-label={`Open ${ip}`} title={t('network.open_in_browser')}><ExternalLink size={12} /></button>
|
||||
</div>
|
||||
</div>
|
||||
{qrs[ip] && <img className="net-toggle__qr" src={qrs[ip]} alt={`QR for ${ip}`} width={104} height={104} />}
|
||||
{qrs[ip] && <img className="net-toggle__qr" src={qrs[ip]} alt={t('network.qr_alt', { ip })} width={104} height={104} />}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div className="net-toggle__pin">PIN: <strong>{st.pin}</strong></div>
|
||||
<button type="button" className="net-toggle__off" onClick={disable} disabled={busy}>Stop sharing</button>
|
||||
<div className="net-toggle__pin">{t('network.pin')} <strong>{st.pin}</strong></div>
|
||||
<button type="button" className="net-toggle__off" onClick={disable} disabled={busy}>{t('network.stop_sharing')}</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CheckCircle, AlertTriangle, XCircle, Loader } from 'lucide-react';
|
||||
import { usePreflight, useModelStatus } from '../api/hooks';
|
||||
import './ReadinessChecklist.css';
|
||||
@@ -26,6 +27,7 @@ const StatusIcon = ({ status, size = 14 }) => {
|
||||
};
|
||||
|
||||
export default function ReadinessChecklist({ compact = false, showWhenAllPass = false }) {
|
||||
const { t } = useTranslation();
|
||||
const { data: preflight, isLoading: preflightLoading } = usePreflight();
|
||||
const { data: modelData, isLoading: modelLoading } = useModelStatus();
|
||||
|
||||
@@ -40,16 +42,16 @@ export default function ReadinessChecklist({ compact = false, showWhenAllPass =
|
||||
const modelErr = modelData?.error || null;
|
||||
const modelCheck = {
|
||||
id: 'asr-model',
|
||||
label: 'ASR Model',
|
||||
label: t('readiness.asr_model'),
|
||||
status: modelStatus === 'ready' ? 'pass'
|
||||
: modelStatus === 'loading' ? 'loading'
|
||||
: modelStatus === 'error' || modelData?.sub_stage === 'error' ? 'fail'
|
||||
: 'warn',
|
||||
detail: modelStatus === 'ready' ? 'Loaded and ready'
|
||||
: modelStatus === 'loading' ? (modelDetail || 'Loading… (this may take 1-2 minutes on first run)')
|
||||
: (modelData?.sub_stage === 'error' ? (modelErr || 'Failed to load') : 'Not loaded yet — will load on first transcription'),
|
||||
detail: modelStatus === 'ready' ? t('readiness.loaded_ready')
|
||||
: modelStatus === 'loading' ? (modelDetail || t('readiness.loading_first_run'))
|
||||
: (modelData?.sub_stage === 'error' ? (modelErr || t('readiness.failed_to_load')) : t('readiness.not_loaded_yet')),
|
||||
fix: (modelStatus === 'error' || modelData?.sub_stage === 'error')
|
||||
? (modelErr ? `Error: ${modelErr}. Check logs and try restarting.` : 'Check logs for model loading errors. Try restarting.')
|
||||
? (modelErr ? t('readiness.error_check_logs', { error: modelErr }) : t('readiness.check_logs_restart'))
|
||||
: null,
|
||||
};
|
||||
checks.push(modelCheck);
|
||||
@@ -68,16 +70,16 @@ export default function ReadinessChecklist({ compact = false, showWhenAllPass =
|
||||
// LLM configuration (check for translate endpoint)
|
||||
const llmCheck = {
|
||||
id: 'llm',
|
||||
label: 'LLM (Cinematic)',
|
||||
label: t('readiness.llm_cinematic'),
|
||||
status: 'warn',
|
||||
detail: 'Configure TRANSLATE_BASE_URL for Cinematic translation quality',
|
||||
fix: 'Set TRANSLATE_BASE_URL and TRANSLATE_API_KEY environment variables. Works with Ollama, OpenAI, LM Studio, etc.',
|
||||
detail: t('readiness.llm_configure'),
|
||||
fix: t('readiness.llm_set_env'),
|
||||
};
|
||||
// If we have preflight and there's a network check passing, LLM is at least possible
|
||||
if (preflight?.checks) {
|
||||
const netCheck = preflight.checks.find(c => c.id === 'network');
|
||||
if (netCheck?.status === 'pass') {
|
||||
llmCheck.detail = 'Optional — set TRANSLATE_BASE_URL for Cinematic quality';
|
||||
llmCheck.detail = t('readiness.llm_optional');
|
||||
}
|
||||
}
|
||||
checks.push(llmCheck);
|
||||
@@ -95,7 +97,7 @@ export default function ReadinessChecklist({ compact = false, showWhenAllPass =
|
||||
<div className="readiness-checklist">
|
||||
<div className="readiness-checklist__title">
|
||||
<span className="readiness-checklist__title-icon">🔍</span>
|
||||
Checking system…
|
||||
{t('readiness.checking_system')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -108,7 +110,7 @@ export default function ReadinessChecklist({ compact = false, showWhenAllPass =
|
||||
return (
|
||||
<div className="readiness-checklist__all-pass">
|
||||
<CheckCircle size={14} />
|
||||
All systems ready
|
||||
{t('readiness.all_ready')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -137,7 +139,7 @@ export default function ReadinessChecklist({ compact = false, showWhenAllPass =
|
||||
<span className="readiness-checklist__title-icon">
|
||||
{anyFail ? '⚠️' : '✅'}
|
||||
</span>
|
||||
System Readiness
|
||||
{t('readiness.system_readiness')}
|
||||
</div>
|
||||
<ul className="readiness-checklist__list">
|
||||
{checks.map(check => (
|
||||
@@ -156,3 +158,4 @@ export default function ReadinessChecklist({ compact = false, showWhenAllPass =
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import { Bug } from 'lucide-react';
|
||||
import { Button } from '../ui';
|
||||
import { openExternal } from '../api/external';
|
||||
import { API } from '../api/client';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const APP_VERSION = (typeof __APP_VERSION__ !== 'undefined' && __APP_VERSION__) || 'unknown';
|
||||
|
||||
@@ -73,7 +74,9 @@ async function captureContext() {
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export default function ReportBugButton({ size = 'sm', variant = 'subtle', label = 'Report a bug' }) {
|
||||
export default function ReportBugButton({ size = 'sm', variant = 'subtle', label }) {
|
||||
const { t } = useTranslation();
|
||||
const displayLabel = label || t('reportBug.label');
|
||||
const [building, setBuilding] = useState(false);
|
||||
|
||||
const handleClick = async () => {
|
||||
@@ -112,9 +115,9 @@ export default function ReportBugButton({ size = 'sm', variant = 'subtle', label
|
||||
onClick={handleClick}
|
||||
loading={building}
|
||||
leading={!building && <Bug size={12} />}
|
||||
title="Opens a prefilled GitHub Issues page in your browser. Nothing is sent until you click Submit."
|
||||
title={t('reportBug.title')}
|
||||
>
|
||||
{label}
|
||||
{displayLabel}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Search, ChevronDown, Check, Star, Clock } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const MAX_DISPLAY = 200;
|
||||
|
||||
@@ -32,6 +33,7 @@ export default function SearchableSelect({
|
||||
buttonClassName = 'input-base',
|
||||
size = 'md',
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [query, setQuery] = useState('');
|
||||
const [highlight, setHighlight] = useState(0);
|
||||
@@ -161,7 +163,7 @@ export default function SearchableSelect({
|
||||
<input
|
||||
ref={inputRef}
|
||||
className="ss-search-input"
|
||||
placeholder="Search…"
|
||||
placeholder={t('common.search')}
|
||||
value={query}
|
||||
onChange={e => { setQuery(e.target.value); setHighlight(0); }}
|
||||
onKeyDown={onKey}
|
||||
@@ -170,12 +172,12 @@ export default function SearchableSelect({
|
||||
|
||||
<div ref={listRef} className="ss-list">
|
||||
{flatItems.length === 0 && (
|
||||
<div className="ss-empty">No matches</div>
|
||||
<div className="ss-empty">{t('common.no_matches')}</div>
|
||||
)}
|
||||
|
||||
{pinned.length > 0 && (
|
||||
<div className="ss-group-label">
|
||||
{recents.length ? <><Clock size={9}/> Recent & Popular</> : <><Star size={9}/> Popular</>}
|
||||
{recents.length ? <><Clock size={9}/> {t('common.recent_and_popular')}</> : <><Star size={9}/> {t('common.popular_label')}</>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -204,7 +206,7 @@ export default function SearchableSelect({
|
||||
})}
|
||||
|
||||
{!query && filtered.length > MAX_DISPLAY && (
|
||||
<div className="ss-more">Showing {MAX_DISPLAY} of {filtered.length}. Type to search…</div>
|
||||
<div className="ss-more">{t('common.showing_of', { shown: MAX_DISPLAY, total: filtered.length })}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { clearDubHistory } from '../api/dub';
|
||||
import { clearHistory as clearGenHistory } from '../api/generate';
|
||||
import { Button } from '../ui';
|
||||
import { useAppStore } from '../store';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import './Sidebar.css';
|
||||
import { askConfirm } from '../utils/dialog';
|
||||
|
||||
@@ -59,6 +60,7 @@ export default function Sidebar(props) {
|
||||
const dubStep = useAppStore(s => s.dubStep);
|
||||
const activeProjectId = useAppStore(s => s.activeProjectId);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const [sbQuery, setSbQuery] = useState('');
|
||||
const qLower = sbQuery.trim().toLowerCase();
|
||||
const matchesSearch = (s) => !qLower || (s || '').toLowerCase().includes(qLower);
|
||||
@@ -79,12 +81,12 @@ export default function Sidebar(props) {
|
||||
), [exportHistory, qLower]);
|
||||
|
||||
const handleClearHistory = async () => {
|
||||
if (!(await askConfirm(`Clear all ${history.length + dubHistory.length} history items? This cannot be undone.`))) return;
|
||||
if (!(await askConfirm(t('sidebar.clear_confirm', { count: history.length + dubHistory.length })))) return;
|
||||
await clearGenHistory();
|
||||
await clearDubHistory();
|
||||
await loadHistory();
|
||||
await loadDubHistory();
|
||||
toast.success('History cleared');
|
||||
toast.success(t('sidebar.history_cleared'));
|
||||
};
|
||||
|
||||
const tabCount = {
|
||||
@@ -92,7 +94,7 @@ export default function Sidebar(props) {
|
||||
history: history.length + dubHistory.length,
|
||||
downloads: exportHistory.length,
|
||||
};
|
||||
const tabLabel = { projects: 'Drive', history: 'History', downloads: 'Exports' };
|
||||
const tabLabel = { projects: t('sidebar.tab_drive'), history: t('sidebar.tab_history'), downloads: t('sidebar.tab_exports') };
|
||||
|
||||
return (
|
||||
<div className={`glass-panel history-panel sidebar ${isSidebarCollapsed ? 'is-collapsed' : ''}`}>
|
||||
@@ -158,7 +160,7 @@ export default function Sidebar(props) {
|
||||
leading={<Save size={13} />}
|
||||
className={`sidebar__save-btn sidebar__save-btn--full ${activeProjectId ? 'is-active-project' : ''}`}
|
||||
>
|
||||
{activeProjectId ? 'Save Dub Project' : 'Save as New Dub Project'}
|
||||
{activeProjectId ? t('sidebar.save_project') : t('sidebar.save_new_project')}
|
||||
</Button>
|
||||
)
|
||||
)}
|
||||
@@ -168,7 +170,7 @@ export default function Sidebar(props) {
|
||||
className="sidebar__section-title"
|
||||
onClick={() => setIsSidebarProjectsCollapsed(!isSidebarProjectsCollapsed)}
|
||||
>
|
||||
<span>{mode === 'dub' ? 'Dub Projects' : (mode === 'clone' ? 'Voice Clones' : 'Designed Voices')}</span>
|
||||
<span>{mode === 'dub' ? t('sidebar.dub_projects') : (mode === 'clone' ? t('sidebar.voice_clones') : t('sidebar.designed_voices'))}</span>
|
||||
{isSidebarProjectsCollapsed ? <ChevronDown size={12} /> : <ChevronUp size={12} />}
|
||||
</div>
|
||||
)}
|
||||
@@ -180,8 +182,8 @@ export default function Sidebar(props) {
|
||||
{filteredProjects.length === 0 ? (
|
||||
<EmptyState
|
||||
icon={Film}
|
||||
title="No saved dub projects"
|
||||
hint="Upload a video and click Save to keep your work."
|
||||
title={t('sidebar.no_dub_projects')}
|
||||
hint={t('sidebar.no_dub_hint')}
|
||||
/>
|
||||
) : (
|
||||
filteredProjects.map(proj => (
|
||||
@@ -191,7 +193,7 @@ export default function Sidebar(props) {
|
||||
>
|
||||
<div className="history-row-head">
|
||||
<span className="history-kind history-kind--audio">
|
||||
<Film size={9} /> Dub
|
||||
<Film size={9} /> {t('sidebar.dub_label')}
|
||||
</span>
|
||||
<span className="history-meta" title={new Date(proj.updated_at * 1000).toLocaleString()}>
|
||||
{timeAgo(proj.updated_at * 1000)}
|
||||
@@ -208,7 +210,7 @@ export default function Sidebar(props) {
|
||||
</div>
|
||||
<div className="history-actions">
|
||||
<button className="history-action-btn accent" onClick={(e) => { e.stopPropagation(); loadProject(proj.id); }}>
|
||||
<FolderOpen size={10} /> Open
|
||||
<FolderOpen size={10} /> {t('sidebar.open')}
|
||||
</button>
|
||||
<button className="history-action-btn danger history-action-icon" onClick={(e) => { e.stopPropagation(); deleteProject(proj.id); }} title="Delete">
|
||||
<Trash2 size={10} />
|
||||
@@ -225,8 +227,8 @@ export default function Sidebar(props) {
|
||||
{filteredProfiles.filter(p => mode === "clone" ? !p.instruct : !!p.instruct).length === 0 ? (
|
||||
<EmptyState
|
||||
icon={mode === 'clone' ? Fingerprint : Wand2}
|
||||
title={`No ${mode === 'clone' ? 'voice clones' : 'designed voices'} yet`}
|
||||
hint={mode === 'clone' ? 'Record or upload audio, then click Save as Voice Profile.' : 'Generate a voice and save it from History.'}
|
||||
title={`${mode === 'clone' ? t('sidebar.no_clones') : t('sidebar.no_designs')}`}
|
||||
hint={mode === 'clone' ? t('sidebar.no_clones_hint') : t('sidebar.no_designs_hint')}
|
||||
/>
|
||||
) : (
|
||||
(mode === 'clone' ? filteredProfiles.filter(p => !p.instruct) : filteredProfiles.filter(p => !!p.instruct)).map(proj => {
|
||||
@@ -240,9 +242,9 @@ export default function Sidebar(props) {
|
||||
>
|
||||
<div className="history-row-head">
|
||||
<span className="history-kind" style={{ color: accent, borderColor: `${accent}40` }}>
|
||||
<KindIcon size={9} /> {proj.is_locked ? 'Locked' : (mode === 'clone' ? 'Clone' : 'Design')}
|
||||
<KindIcon size={9} /> {proj.is_locked ? t('sidebar.locked') : (mode === 'clone' ? t('sidebar.clone_label') : t('sidebar.design_label'))}
|
||||
</span>
|
||||
{proj.is_locked ? <span className="history-meta history-meta--locked">consistent</span> : null}
|
||||
{proj.is_locked ? <span className="history-meta history-meta--locked">{t('sidebar.consistent')}</span> : null}
|
||||
</div>
|
||||
<div className="history-title">{proj.name}</div>
|
||||
{proj.instruct ? <div className="history-subtitle history-subtitle--italic">{proj.instruct}</div> : null}
|
||||
@@ -261,7 +263,7 @@ export default function Sidebar(props) {
|
||||
</button>
|
||||
)}
|
||||
<button className="history-action-btn" onClick={(e) => { e.stopPropagation(); handleSelectProfile(proj); }}>
|
||||
<Check size={10} /> Select
|
||||
<Check size={10} /> {t('sidebar.select')}
|
||||
</button>
|
||||
{onOpenVoicePreview && (
|
||||
<button
|
||||
@@ -269,7 +271,7 @@ export default function Sidebar(props) {
|
||||
onClick={(e) => { e.stopPropagation(); onOpenVoicePreview(proj.id); }}
|
||||
title="Open interactive voice preview"
|
||||
>
|
||||
<Volume2 size={10} /> Try
|
||||
<Volume2 size={10} /> {t('sidebar.try_voice')}
|
||||
</button>
|
||||
)}
|
||||
{proj.is_locked ? (
|
||||
@@ -320,12 +322,12 @@ export default function Sidebar(props) {
|
||||
{/* ── HISTORY TAB ── */}
|
||||
{sidebarTab === 'history' && (
|
||||
<>
|
||||
{!isSidebarCollapsed && <div className="sidebar__subtitle">Generation history · Stored in SQLite</div>}
|
||||
{!isSidebarCollapsed && <div className="sidebar__subtitle">{t('sidebar.history_subtitle')}</div>}
|
||||
{(history.length + dubHistory.length) === 0 ? (
|
||||
<EmptyState
|
||||
icon={History}
|
||||
title="No generation history"
|
||||
hint="Synthesize audio or dub a video — results will appear here."
|
||||
title={t('sidebar.no_history')}
|
||||
hint={t('sidebar.no_history_hint')}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
@@ -335,7 +337,7 @@ export default function Sidebar(props) {
|
||||
>
|
||||
<div className="history-row-head">
|
||||
<span className="history-kind history-kind--audio">
|
||||
<Film size={9} /> Dub
|
||||
<Film size={9} /> {t('sidebar.dub_label')}
|
||||
</span>
|
||||
<span className="history-meta">{item.segments_count} segs · {Math.round(item.duration || 0)}s</span>
|
||||
</div>
|
||||
@@ -345,7 +347,7 @@ export default function Sidebar(props) {
|
||||
</div>
|
||||
<div className="history-actions">
|
||||
<button className="history-action-btn accent" onClick={(e) => { e.stopPropagation(); restoreDubHistory(item); }}>
|
||||
<FolderOpen size={10} /> Open
|
||||
<FolderOpen size={10} /> {t('sidebar.open')}
|
||||
</button>
|
||||
<button className="history-action-btn danger history-action-icon" onClick={(e) => { e.stopPropagation(); deleteHistory(item.id, 'dub'); }} title="Delete">
|
||||
<Trash2 size={10} />
|
||||
@@ -380,12 +382,12 @@ export default function Sidebar(props) {
|
||||
{item.audio_path ? (
|
||||
<div className="history-actions">
|
||||
<button className="history-action-btn accent" onClick={(e) => { e.stopPropagation(); handleSaveHistoryAsProfile(item); }}>
|
||||
<Save size={10} /> Save
|
||||
<Save size={10} /> {t('sidebar.save_label')}
|
||||
</button>
|
||||
{item.profile_id ? (
|
||||
<button className="history-action-btn accent history-action-icon"
|
||||
onClick={(e) => { e.stopPropagation(); handleLockProfile(item.profile_id, item.id, item.seed); }}
|
||||
title="Lock voice identity">
|
||||
title={t('sidebar.lock_identity')}>
|
||||
<Lock size={10} />
|
||||
</button>
|
||||
) : null}
|
||||
@@ -435,7 +437,7 @@ export default function Sidebar(props) {
|
||||
leading={<Trash2 size={10} />}
|
||||
className="sidebar__clear"
|
||||
>
|
||||
Clear History
|
||||
{t('sidebar.clear_history')}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
@@ -444,12 +446,12 @@ export default function Sidebar(props) {
|
||||
{/* ── DOWNLOADS TAB ── */}
|
||||
{sidebarTab === 'downloads' && (
|
||||
<>
|
||||
{!isSidebarCollapsed && <div className="sidebar__subtitle">Recent Exports</div>}
|
||||
{!isSidebarCollapsed && <div className="sidebar__subtitle">{t('sidebar.recent_exports')}</div>}
|
||||
{exportHistory.length === 0 ? (
|
||||
<EmptyState
|
||||
icon={DownloadCloud}
|
||||
title="No downloaded outputs"
|
||||
hint="Export a file via Tauri to see it tracked here."
|
||||
title={t('sidebar.no_exports')}
|
||||
hint={t('sidebar.no_exports_hint')}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
@@ -470,10 +472,10 @@ export default function Sidebar(props) {
|
||||
<span className="history-meta">{timeAgo(item.created_at * 1000)}</span>
|
||||
</div>
|
||||
<div className="history-title">{item.filename}</div>
|
||||
<div className="history-subtitle">in {parentFolder}</div>
|
||||
<div className="history-subtitle">{t('sidebar.in_folder', { folder: parentFolder })}</div>
|
||||
<div className="history-actions">
|
||||
<button className="history-action-btn accent" onClick={(e) => { e.stopPropagation(); revealInFolder(item.destination_path); }}>
|
||||
<FolderOpen size={10} /> Show in folder
|
||||
<FolderOpen size={10} /> {t('sidebar.show_in_folder')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -459,3 +459,68 @@
|
||||
font-size: var(--text-xs);
|
||||
padding: 3px 6px;
|
||||
}
|
||||
|
||||
/* ── Redesign: grouped toolbar, chapter bars, readable column ──────── */
|
||||
|
||||
/* Center the editor at a comfortable reading width (header + script align). */
|
||||
.stories-editor { max-width: 1040px; margin-inline: auto; }
|
||||
|
||||
/* Toolbar: logical clusters (project · content · output) with thin dividers. */
|
||||
.stories-editor__actions { flex-wrap: wrap; }
|
||||
.stories-editor__group { display: inline-flex; align-items: center; gap: 4px; }
|
||||
.stories-editor__divider {
|
||||
align-self: stretch;
|
||||
width: 1px;
|
||||
min-height: 18px;
|
||||
margin: 0 4px;
|
||||
background: var(--color-border);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* Chapter = a section heading, not a voiced line. */
|
||||
.stories-chapter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 16px 0 4px;
|
||||
padding: 7px 10px;
|
||||
border-radius: var(--radius-md, 8px);
|
||||
border-left: 3px solid var(--color-accent);
|
||||
background: var(--color-bg-elevated, rgba(255, 255, 255, 0.03));
|
||||
}
|
||||
.stories-chapter--dragover { outline: 1px dashed var(--color-accent); outline-offset: 2px; }
|
||||
.stories-chapter__grip { display: flex; color: var(--color-fg-dim, #a89884); cursor: grab; opacity: 0.4; }
|
||||
.stories-chapter:hover .stories-chapter__grip { opacity: 1; }
|
||||
.stories-chapter__icon { flex: none; color: var(--color-accent); }
|
||||
.stories-chapter__title {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-family: inherit;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.01em;
|
||||
color: var(--color-fg);
|
||||
padding: 2px 0;
|
||||
}
|
||||
.stories-chapter__title::placeholder { color: var(--color-fg-dim, #a89884); font-weight: 600; }
|
||||
.stories-chapter__del {
|
||||
flex: none;
|
||||
display: flex;
|
||||
padding: 4px;
|
||||
border-radius: 6px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--color-fg-dim, #a89884);
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
}
|
||||
.stories-chapter:hover .stories-chapter__del { opacity: 0.7; }
|
||||
.stories-chapter__del:hover { opacity: 1; color: var(--color-danger, #fb4934); }
|
||||
|
||||
/* Quiet a line's secondary actions until the row is hovered or active. */
|
||||
.stories-track__actions { opacity: 0.5; transition: opacity 0.12s ease; }
|
||||
.stories-track:hover .stories-track__actions,
|
||||
.stories-track--active .stories-track__actions { opacity: 1; }
|
||||
|
||||
@@ -37,6 +37,14 @@ function download(blob, filename) {
|
||||
setTimeout(() => URL.revokeObjectURL(url), 10000);
|
||||
}
|
||||
|
||||
// A chapter line is any track whose text is a markdown heading (`# …`). It
|
||||
// renders as a section bar (no voice/tune/preview), and storyExport keys its
|
||||
// chapter cues off the same prefix — keep the two in sync.
|
||||
// Lenient on purpose: a heading with an empty title is still `# ` (or `#`), and
|
||||
// it must stay a chapter while the user edits the title — otherwise clearing the
|
||||
// text would flip the bar back into a voiced line card mid-edit.
|
||||
const isChapterText = (s) => /^\s*#{1,6}(\s|$)/.test(s || '');
|
||||
|
||||
// Sentence-aware splitter for the "Paste & auto-split" panel. Walks the text
|
||||
// and breaks at the closest sentence boundary that keeps each chunk under
|
||||
// `maxChars`. Falls back to whitespace, then to the hard cap.
|
||||
@@ -207,7 +215,7 @@ export default function StoriesEditor({ profiles = [] }) {
|
||||
const openProject = useCallback((id) => { loadProject(id); setProjectsOpen(false); }, [loadProject]);
|
||||
|
||||
const addChapter = useCallback(() => {
|
||||
const n = tracks.filter((tk) => /^#{1,6}\s+/.test((tk.text || '').trim())).length + 1;
|
||||
const n = tracks.filter((tk) => isChapterText(tk.text)).length + 1;
|
||||
setTracks((prev) => [...prev, makeTrack('narrator', `# ${t('stories.chapterN', { n })}`)]);
|
||||
}, [tracks, setTracks, t]);
|
||||
|
||||
@@ -393,40 +401,56 @@ export default function StoriesEditor({ profiles = [] }) {
|
||||
</div>
|
||||
<div className="stories-editor__actions">
|
||||
<input ref={fileInputRef} type="file" accept=".txt,.srt,text/plain" onChange={onImportFile} hidden />
|
||||
<Button size="sm" variant="ghost" onClick={() => setProjectsOpen((v) => !v)} aria-label={t('stories.projects')}>
|
||||
<Folder size={13} /> {currentProject ? currentProject.name : t('stories.projects')}
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost" onClick={() => setCastOpen((v) => !v)} aria-label={t('stories.cast')}>
|
||||
<Users size={13} /> {t('stories.cast')}
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost" onClick={() => fileInputRef.current && fileInputRef.current.click()} aria-label={t('stories.import')}>
|
||||
<Upload size={13} /> {t('stories.import')}
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost" onClick={() => setSplitOpen((v) => !v)} aria-label={t('stories.pasteSplit')}>
|
||||
<Scissors size={13} /> {t('stories.pasteSplit')}
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost" onClick={addTrack} aria-label={t('stories.addLine')}>
|
||||
<Plus size={13} /> {t('stories.addLine')}
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost" onClick={addChapter} aria-label={t('stories.addChapter')}>
|
||||
<Bookmark size={13} /> {t('stories.addChapter')}
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost" onClick={exportStemsAll} disabled={tracks.length === 0 || exporting} aria-label={t('stories.stems')}>
|
||||
<Layers size={13} /> {t('stories.stems')}
|
||||
</Button>
|
||||
<select
|
||||
className="stories-editor__format"
|
||||
value={exportFormat}
|
||||
onChange={(e) => setExportFormat(e.target.value)}
|
||||
aria-label={t('stories.format')}
|
||||
title={t('stories.format')}
|
||||
>
|
||||
<option value="wav">WAV</option>
|
||||
<option value="mp3">MP3</option>
|
||||
</select>
|
||||
<Button size="sm" onClick={generateAll} disabled={tracks.length === 0 || exporting}>
|
||||
<Download size={13} /> {exporting ? `${exportPct}%` : t('stories.generateAll')}
|
||||
</Button>
|
||||
|
||||
{/* Project */}
|
||||
<div className="stories-editor__group">
|
||||
<Button size="sm" variant="ghost" onClick={() => setProjectsOpen((v) => !v)} aria-label={t('stories.projects')}>
|
||||
<Folder size={13} /> {currentProject ? currentProject.name : t('stories.projects')}
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost" onClick={() => setCastOpen((v) => !v)} aria-label={t('stories.cast')}>
|
||||
<Users size={13} /> {t('stories.cast')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<span className="stories-editor__divider" aria-hidden="true" />
|
||||
|
||||
{/* Content */}
|
||||
<div className="stories-editor__group">
|
||||
<Button size="sm" variant="ghost" onClick={() => fileInputRef.current && fileInputRef.current.click()} aria-label={t('stories.import')}>
|
||||
<Upload size={13} /> {t('stories.import')}
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost" onClick={() => setSplitOpen((v) => !v)} aria-label={t('stories.pasteSplit')}>
|
||||
<Scissors size={13} /> {t('stories.pasteSplit')}
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost" onClick={addTrack} aria-label={t('stories.addLine')}>
|
||||
<Plus size={13} /> {t('stories.addLine')}
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost" onClick={addChapter} aria-label={t('stories.addChapter')}>
|
||||
<Bookmark size={13} /> {t('stories.addChapter')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<span className="stories-editor__divider" aria-hidden="true" />
|
||||
|
||||
{/* Output */}
|
||||
<div className="stories-editor__group">
|
||||
<Button size="sm" variant="ghost" onClick={exportStemsAll} disabled={tracks.length === 0 || exporting} aria-label={t('stories.stems')}>
|
||||
<Layers size={13} /> {t('stories.stems')}
|
||||
</Button>
|
||||
<select
|
||||
className="stories-editor__format"
|
||||
value={exportFormat}
|
||||
onChange={(e) => setExportFormat(e.target.value)}
|
||||
aria-label={t('stories.format')}
|
||||
title={t('stories.format')}
|
||||
>
|
||||
<option value="wav">WAV</option>
|
||||
<option value="mp3">MP3</option>
|
||||
</select>
|
||||
<Button size="sm" onClick={generateAll} disabled={tracks.length === 0 || exporting}>
|
||||
<Download size={13} /> {exporting ? `${exportPct}%` : t('stories.generateAll')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -552,6 +576,53 @@ export default function StoriesEditor({ profiles = [] }) {
|
||||
) : (
|
||||
<div className="stories-editor__tracks" role="list">
|
||||
{tracks.map((track) => {
|
||||
const dragProps = {
|
||||
draggable: true,
|
||||
onDragStart: (e) => { dragId.current = track.id; e.dataTransfer.effectAllowed = 'move'; },
|
||||
onDragOver: (e) => { e.preventDefault(); if (dragOver !== track.id) setDragOver(track.id); },
|
||||
onDragLeave: () => setDragOver((d) => (d === track.id ? null : d)),
|
||||
onDrop: (e) => {
|
||||
e.preventDefault();
|
||||
if (dragId.current != null && dragId.current !== track.id) {
|
||||
setTracks((prev) => reorder(prev, dragId.current, track.id));
|
||||
}
|
||||
dragId.current = null;
|
||||
setDragOver(null);
|
||||
},
|
||||
};
|
||||
|
||||
// Chapters render as a section bar — no voice / tune / preview.
|
||||
if (isChapterText(track.text)) {
|
||||
const title = track.text.replace(/^#{1,6}\s*/, '');
|
||||
return (
|
||||
<div
|
||||
key={track.id}
|
||||
role="listitem"
|
||||
className={['stories-chapter', dragOver === track.id ? 'stories-chapter--dragover' : ''].filter(Boolean).join(' ')}
|
||||
{...dragProps}
|
||||
>
|
||||
<div className="stories-chapter__grip" aria-hidden="true"><GripVertical size={14} /></div>
|
||||
<Bookmark size={15} className="stories-chapter__icon" aria-hidden="true" />
|
||||
<input
|
||||
className="stories-chapter__title"
|
||||
value={title}
|
||||
onChange={(e) => updateTrack(track.id, 'text', `# ${e.target.value}`)}
|
||||
placeholder={t('stories.addChapter')}
|
||||
aria-label={t('stories.addChapter')}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="stories-chapter__del"
|
||||
onClick={(e) => { e.stopPropagation(); removeTrack(track.id); }}
|
||||
title={t('stories.removeLine')}
|
||||
aria-label={t('stories.removeLine')}
|
||||
>
|
||||
<Trash2 size={13} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const member = castMember(cast, track.character);
|
||||
const inheritedId = member && member.profileId;
|
||||
const inheritedName = inheritedId ? profileName(inheritedId) : null;
|
||||
@@ -566,18 +637,7 @@ export default function StoriesEditor({ profiles = [] }) {
|
||||
dragOver === track.id ? 'stories-track--dragover' : '',
|
||||
].filter(Boolean).join(' ')}
|
||||
onClick={() => setActiveTrack(track.id)}
|
||||
draggable
|
||||
onDragStart={(e) => { dragId.current = track.id; e.dataTransfer.effectAllowed = 'move'; }}
|
||||
onDragOver={(e) => { e.preventDefault(); if (dragOver !== track.id) setDragOver(track.id); }}
|
||||
onDragLeave={() => setDragOver((d) => (d === track.id ? null : d))}
|
||||
onDrop={(e) => {
|
||||
e.preventDefault();
|
||||
if (dragId.current != null && dragId.current !== track.id) {
|
||||
setTracks((prev) => reorder(prev, dragId.current, track.id));
|
||||
}
|
||||
dragId.current = null;
|
||||
setDragOver(null);
|
||||
}}
|
||||
{...dragProps}
|
||||
>
|
||||
<div className="stories-track__grip" aria-hidden="true"><GripVertical size={14} /></div>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { apiPost } from '../api/client';
|
||||
import './SupertonicLicenseDialog.css';
|
||||
@@ -35,6 +36,7 @@ const LICENSE_URLS = {
|
||||
};
|
||||
|
||||
export default function SupertonicLicenseDialog({ open, onClose, onAccepted }) {
|
||||
const { t } = useTranslation();
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
// Escape closes the dialog ‑‑ mirrors browser-standard modal UX.
|
||||
@@ -54,16 +56,16 @@ export default function SupertonicLicenseDialog({ open, onClose, onAccepted }) {
|
||||
engine_id: 'supertonic3',
|
||||
accepted: true,
|
||||
});
|
||||
toast.success('Supertonic-3 license accepted.');
|
||||
toast.success(t('license.accepted_toast'));
|
||||
onAccepted?.();
|
||||
onClose?.();
|
||||
} catch (e) {
|
||||
const msg = e?.message || String(e);
|
||||
toast.error(`Failed to record license acceptance: ${msg}`);
|
||||
toast.error(t('license.accept_error', { message: msg }));
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
}, [onAccepted, onClose]);
|
||||
}, [onAccepted, onClose, t]);
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
@@ -81,21 +83,18 @@ export default function SupertonicLicenseDialog({ open, onClose, onAccepted }) {
|
||||
>
|
||||
<div className="supertonic-license__card">
|
||||
<h2 id="supertonic-license-title" className="supertonic-license__title">
|
||||
Supertonic-3 — License Acceptance
|
||||
{t('license.title')}
|
||||
</h2>
|
||||
|
||||
<p className="supertonic-license__intro">
|
||||
Supertonic-3 ships under two distinct licenses. Please review
|
||||
both before enabling the engine.
|
||||
{t('license.intro')}
|
||||
</p>
|
||||
|
||||
<div className="supertonic-license__sections">
|
||||
<section className="supertonic-license__section">
|
||||
<h3>SDK Code · MIT</h3>
|
||||
<h3>{t('license.sdk_heading')}</h3>
|
||||
<p>
|
||||
The Python inference SDK (
|
||||
<code>supertonic</code>
|
||||
) is MIT-licensed. Permissive use, including commercial.
|
||||
{t('license.sdk_desc')}
|
||||
</p>
|
||||
<a
|
||||
href={LICENSE_URLS.code}
|
||||
@@ -103,17 +102,14 @@ export default function SupertonicLicenseDialog({ open, onClose, onAccepted }) {
|
||||
rel="noopener noreferrer"
|
||||
className="supertonic-license__link"
|
||||
>
|
||||
Read the MIT license →
|
||||
{t('license.read_mit')}
|
||||
</a>
|
||||
</section>
|
||||
|
||||
<section className="supertonic-license__section">
|
||||
<h3>Model Weights · OpenRAIL-M</h3>
|
||||
<h3>{t('license.model_heading')}</h3>
|
||||
<p>
|
||||
The Supertonic-3 model weights are released under the
|
||||
OpenRAIL-M license. This license restricts use to
|
||||
non-malicious purposes ‑‑ see the linked license for the
|
||||
full set of use-based restrictions.
|
||||
{t('license.model_desc')}
|
||||
</p>
|
||||
<a
|
||||
href={LICENSE_URLS.model}
|
||||
@@ -121,16 +117,13 @@ export default function SupertonicLicenseDialog({ open, onClose, onAccepted }) {
|
||||
rel="noopener noreferrer"
|
||||
className="supertonic-license__link"
|
||||
>
|
||||
Read the OpenRAIL-M license →
|
||||
{t('license.read_openrail')}
|
||||
</a>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<p className="supertonic-license__footer">
|
||||
Clicking Accept records your acceptance in OmniVoice's
|
||||
local settings and enables the engine. Your acceptance is
|
||||
stored on this machine only ‑‑ nothing is reported to
|
||||
Supertone Inc. or any third party.
|
||||
{t('license.footer')}
|
||||
</p>
|
||||
|
||||
<div className="supertonic-license__actions">
|
||||
@@ -140,7 +133,7 @@ export default function SupertonicLicenseDialog({ open, onClose, onAccepted }) {
|
||||
onClick={onClose}
|
||||
disabled={submitting}
|
||||
>
|
||||
Cancel
|
||||
{t('common.cancel')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -149,10 +142,11 @@ export default function SupertonicLicenseDialog({ open, onClose, onAccepted }) {
|
||||
disabled={submitting}
|
||||
autoFocus
|
||||
>
|
||||
{submitting ? 'Saving…' : 'Accept'}
|
||||
{submitting ? t('license.saving') : t('license.accept')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
.update-badge {
|
||||
position: fixed;
|
||||
top: 38px;
|
||||
right: 12px;
|
||||
z-index: 200;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.update-badge__btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: rgba(184, 187, 38, 0.14);
|
||||
border: 1px solid rgba(184, 187, 38, 0.45);
|
||||
color: #b8bb26;
|
||||
font-family: inherit;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
.update-badge__btn:hover { background: rgba(184, 187, 38, 0.22); }
|
||||
.update-badge__btn--ready {
|
||||
background: rgba(131, 165, 152, 0.18);
|
||||
border-color: rgba(131, 165, 152, 0.5);
|
||||
color: #8ec07c;
|
||||
}
|
||||
.update-badge__progress {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: var(--chrome-bg, #1d2021);
|
||||
border: 1px solid var(--chrome-border, #3a3a3a);
|
||||
color: #ebdbb2;
|
||||
font-size: 11px;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
}
|
||||
.update-badge__bar { width: 60px; height: 3px; background: rgba(255, 255, 255, 0.12); border-radius: 2px; overflow: hidden; }
|
||||
.update-badge__bar span { display: block; height: 100%; background: #b8bb26; }
|
||||
@@ -1,47 +0,0 @@
|
||||
// Non-blocking auto-update surface: a small pill that appears when an update is
|
||||
// available / downloading / ready. Replaces the old blocking ask() dialog so an
|
||||
// update never interrupts in-flight work — the user installs when they choose,
|
||||
// and the action is gated while a dub job is running.
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Download, Loader, RotateCw } from 'lucide-react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useAppStore } from '../store';
|
||||
import { installUpdate } from '../utils/updater';
|
||||
import './UpdateBadge.css';
|
||||
|
||||
export default function UpdateBadge() {
|
||||
const { t } = useTranslation();
|
||||
const status = useAppStore((s) => s.updateStatus);
|
||||
const version = useAppStore((s) => s.updateVersion);
|
||||
const progress = useAppStore((s) => s.updateProgress);
|
||||
const dubStep = useAppStore((s) => s.dubStep);
|
||||
|
||||
if (status === 'idle' || status === 'checking' || status === 'error') return null;
|
||||
|
||||
const busy = dubStep === 'generating';
|
||||
const onInstall = () => {
|
||||
if (busy) { toast(t('update.busy'), { icon: '⏳' }); return; }
|
||||
installUpdate(useAppStore.getState());
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="update-badge" role="status">
|
||||
{status === 'available' && (
|
||||
<button type="button" className="update-badge__btn" onClick={onInstall} title={t('update.install_hint')}>
|
||||
<Download size={12} /> {t('update.available', { version: version || '' })} · {t('update.install')}
|
||||
</button>
|
||||
)}
|
||||
{status === 'downloading' && (
|
||||
<span className="update-badge__progress">
|
||||
<Loader size={12} className="spinner" /> {t('update.downloading', { pct: Math.round(progress) })}
|
||||
<span className="update-badge__bar"><span style={{ width: `${progress}%` }} /></span>
|
||||
</span>
|
||||
)}
|
||||
{status === 'ready' && (
|
||||
<button type="button" className="update-badge__btn update-badge__btn--ready" onClick={onInstall}>
|
||||
<RotateCw size={12} /> {t('update.restart')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/* frontend/src/components/UpdateStatusChip.css */
|
||||
.update-chip {
|
||||
display: inline-flex; align-items: center; gap: 4px;
|
||||
height: 20px; padding: 0 8px;
|
||||
background: none; border: 1px solid transparent; border-radius: 999px;
|
||||
font: inherit; font-size: 11px; font-weight: 600; cursor: pointer;
|
||||
color: var(--text-dim, #a89984);
|
||||
}
|
||||
.update-chip:hover { color: #ebdbb2; }
|
||||
.update-chip--idle { opacity: 0.75; }
|
||||
.update-chip--available { color: #b8bb26; border-color: rgba(184, 187, 38, 0.5); }
|
||||
.update-chip--downloading { color: #83a598; }
|
||||
.update-chip--ready { color: #b8bb26; border-color: rgba(184, 187, 38, 0.6); }
|
||||
.update-chip--error { color: #fb4934; border-color: rgba(251, 73, 52, 0.5); }
|
||||
.update-chip .spinner { animation: spin 1s linear infinite; }
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
@@ -0,0 +1,57 @@
|
||||
// frontend/src/components/UpdateStatusChip.jsx
|
||||
// Persistent update indicator that lives in the LogsFooter bar (replaces the
|
||||
// old floating UpdateBadge). Shows current version when idle; morphs into
|
||||
// available / downloading / ready / error. Click opens the Updates panel.
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Check, ArrowUp, Loader, RotateCw, AlertTriangle } from 'lucide-react';
|
||||
import { useAppStore } from '../store';
|
||||
import { chipPresentation } from '../utils/updatePresentation';
|
||||
import { installUpdate } from '../utils/updater';
|
||||
import toast from 'react-hot-toast';
|
||||
import './UpdateStatusChip.css';
|
||||
|
||||
const ICONS = { check: Check, up: ArrowUp, spin: Loader, restart: RotateCw, alert: AlertTriangle };
|
||||
|
||||
export default function UpdateStatusChip({ onOpen }) {
|
||||
const { t } = useTranslation();
|
||||
const status = useAppStore((s) => s.updateStatus);
|
||||
const version = useAppStore((s) => s.updateVersion);
|
||||
const appVersion = useAppStore((s) => s.appVersion);
|
||||
const progress = useAppStore((s) => s.updateProgress);
|
||||
const dubStep = useAppStore((s) => s.dubStep);
|
||||
|
||||
const p = chipPresentation(status, { appVersion, version, progress });
|
||||
if (!p) return null;
|
||||
const Icon = ICONS[p.icon] || Check;
|
||||
|
||||
const labelText = {
|
||||
idle: p.label,
|
||||
available: t('update.available', { version: p.label }),
|
||||
downloading: t('update.downloading', { pct: p.label.replace('%', '') }),
|
||||
ready: t('update.restart'),
|
||||
error: t('update.failed'),
|
||||
}[p.variant];
|
||||
|
||||
// ready stays one-click (preserve today's behavior); others open the panel.
|
||||
const onClick = () => {
|
||||
if (p.variant === 'ready') {
|
||||
// Don't relaunch out from under an in-flight dub/transcription job.
|
||||
if (dubStep === 'generating') { toast(t('update.busy'), { icon: '⏳' }); return; }
|
||||
installUpdate(useAppStore.getState());
|
||||
return;
|
||||
}
|
||||
onOpen?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={`update-chip update-chip--${p.variant}`}
|
||||
onClick={onClick}
|
||||
title={t('updates.tab')}
|
||||
>
|
||||
<Icon size={12} className={p.icon === 'spin' ? 'spinner' : ''} />
|
||||
<span className="update-chip__label">{labelText}</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/* frontend/src/components/UpdatesPanel.css */
|
||||
.updates-panel { padding: 8px 12px; overflow-y: auto; font-size: 12px; color: #d5c4a1; }
|
||||
.updates-panel__live { display: flex; align-items: center; min-height: 26px; }
|
||||
.updates-panel__cta {
|
||||
display: inline-flex; align-items: center; gap: 6px;
|
||||
background: rgba(184,187,38,0.14); border: 1px solid rgba(184,187,38,0.5);
|
||||
color: #b8bb26; border-radius: 999px; padding: 3px 10px; font: inherit; cursor: pointer;
|
||||
}
|
||||
.updates-panel__ok, .updates-panel__err, .updates-panel__progress { display: inline-flex; align-items: center; gap: 8px; }
|
||||
.updates-panel__err { color: #fb4934; }
|
||||
.updates-panel__link { background: none; border: none; color: #83a598; cursor: pointer; font: inherit; display: inline-flex; align-items: center; gap: 3px; }
|
||||
.updates-panel__icon { background: none; border: none; color: #fb4934; cursor: pointer; padding: 0 2px; }
|
||||
.updates-panel__bar { display: inline-block; width: 80px; height: 3px; background: rgba(255,255,255,0.12); border-radius: 2px; overflow: hidden; }
|
||||
.updates-panel__bar span { display: block; height: 100%; background: #b8bb26; }
|
||||
.updates-panel__channel { display: flex; align-items: center; gap: 10px; margin: 8px 0; }
|
||||
.updates-panel__seg { display: inline-flex; border: 1px solid var(--chrome-border, #3a3a3a); border-radius: 6px; overflow: hidden; }
|
||||
.updates-panel__segbtn { background: none; border: none; color: var(--text-dim, #a89984); padding: 2px 10px; font: inherit; cursor: pointer; }
|
||||
.updates-panel__segbtn.is-active { background: rgba(131,165,152,0.18); color: #ebdbb2; }
|
||||
.updates-panel__rel-head { font-weight: 600; color: var(--text-dim, #a89984); margin: 6px 0 4px; }
|
||||
.updates-panel__rel-empty { color: var(--text-dim, #a89984); display: inline-flex; gap: 8px; align-items: center; padding: 6px 0; }
|
||||
.updates-panel__rel { border-top: 1px solid var(--chrome-border, #2a2a2a); padding: 6px 0; }
|
||||
.updates-panel__rel.is-current { background: rgba(184,187,38,0.06); }
|
||||
.updates-panel__rel-row { display: flex; align-items: center; gap: 8px; }
|
||||
.updates-panel__rel-ver { font-weight: 600; }
|
||||
.updates-panel__rel-tag { font-size: 10px; color: #b8bb26; border: 1px solid rgba(184,187,38,0.5); border-radius: 999px; padding: 0 6px; }
|
||||
.updates-panel__rel-pre { font-size: 10px; color: #d79921; border: 1px solid rgba(215,153,33,0.5); border-radius: 999px; padding: 0 6px; }
|
||||
.updates-panel__rel-date { margin-left: auto; color: var(--text-dim, #a89984); }
|
||||
.updates-panel__rel-notes { white-space: pre-wrap; overflow-wrap: break-word; margin: 4px 0 0; font-size: 11px; line-height: 1.4; color: #bdae93; }
|
||||
@@ -0,0 +1,117 @@
|
||||
// frontend/src/components/UpdatesPanel.jsx
|
||||
// Body of the LogsFooter "Updates" tab: live update row + channel switcher +
|
||||
// GitHub releases (changelog/history) list. Data is transient (releasesSlice).
|
||||
import { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Download, RotateCw, AlertTriangle, RefreshCw, X } from 'lucide-react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useAppStore } from '../store';
|
||||
import { installUpdate, checkForUpdate } from '../utils/updater';
|
||||
import { prepareReleases } from '../utils/updatePresentation';
|
||||
import { setChannel } from '../utils/channelControl';
|
||||
import './UpdatesPanel.css';
|
||||
|
||||
export default function UpdatesPanel() {
|
||||
const { t } = useTranslation();
|
||||
const status = useAppStore((s) => s.updateStatus);
|
||||
const version = useAppStore((s) => s.updateVersion);
|
||||
const error = useAppStore((s) => s.updateError);
|
||||
const progress = useAppStore((s) => s.updateProgress);
|
||||
const appVersion = useAppStore((s) => s.appVersion);
|
||||
const channel = useAppStore((s) => s.updateChannel);
|
||||
const releases = useAppStore((s) => s.releases);
|
||||
const releasesStatus = useAppStore((s) => s.releasesStatus);
|
||||
const loadReleases = useAppStore((s) => s.loadReleases);
|
||||
const dismissUpdate = useAppStore((s) => s.dismissUpdate);
|
||||
const dubStep = useAppStore((s) => s.dubStep);
|
||||
|
||||
useEffect(() => { loadReleases(channel); }, [channel, loadReleases]);
|
||||
|
||||
const busy = dubStep === 'generating';
|
||||
const onInstall = () => {
|
||||
if (busy) { toast(t('update.busy'), { icon: '⏳' }); return; }
|
||||
installUpdate(useAppStore.getState());
|
||||
};
|
||||
const rows = prepareReleases(releases, channel, appVersion);
|
||||
|
||||
return (
|
||||
<div className="updates-panel">
|
||||
<div className="updates-panel__live">
|
||||
{status === 'available' && (
|
||||
<button className="updates-panel__cta" onClick={onInstall}>
|
||||
<Download size={13} /> {t('update.available', { version: version || '' })} · {t('update.install')}
|
||||
</button>
|
||||
)}
|
||||
{status === 'downloading' && (
|
||||
<span className="updates-panel__progress">
|
||||
{t('update.downloading', { pct: Math.round(progress) })}
|
||||
<span className="updates-panel__bar"><span style={{ width: `${progress}%` }} /></span>
|
||||
</span>
|
||||
)}
|
||||
{status === 'ready' && (
|
||||
<button className="updates-panel__cta" onClick={onInstall}>
|
||||
<RotateCw size={13} /> {t('update.restart')}
|
||||
</button>
|
||||
)}
|
||||
{status === 'error' && (
|
||||
<span className="updates-panel__err">
|
||||
<AlertTriangle size={13} /> {error || t('update.failed')}
|
||||
<button className="updates-panel__link" onClick={onInstall}>{t('update.retry')}</button>
|
||||
<button className="updates-panel__icon" onClick={dismissUpdate} aria-label={t('update.dismiss')}><X size={13} /></button>
|
||||
</span>
|
||||
)}
|
||||
{(status === 'idle' || status === 'checking') && (
|
||||
<span className="updates-panel__ok">
|
||||
{t('updates.up_to_date', { version: appVersion || '' })}
|
||||
<button className="updates-panel__link" onClick={() => checkForUpdate(useAppStore.getState())}>
|
||||
<RefreshCw size={12} /> {t('updates.check_now')}
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="updates-panel__channel">
|
||||
<span>{t('about.update_channel')}</span>
|
||||
<div className="updates-panel__seg" role="radiogroup" aria-label={t('about.update_channel')}>
|
||||
{['stable', 'preview'].map((c) => (
|
||||
<button
|
||||
key={c}
|
||||
type="button"
|
||||
role="radio"
|
||||
aria-checked={channel === c}
|
||||
className={`updates-panel__segbtn ${channel === c ? 'is-active' : ''}`}
|
||||
onClick={() => setChannel(useAppStore.getState(), c).catch((e) => toast(t('settings.channel_set_failed', { message: e?.message || e }), { icon: '⚠️' }))}
|
||||
>
|
||||
{t(`about.channel_${c}`)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="updates-panel__releases">
|
||||
<div className="updates-panel__rel-head">{t('updates.releases')}</div>
|
||||
{releasesStatus === 'error' && (
|
||||
<div className="updates-panel__rel-empty">
|
||||
{t('updates.load_error')}
|
||||
<button className="updates-panel__link" onClick={() => loadReleases(channel)}>{t('updates.retry_load')}</button>
|
||||
</div>
|
||||
)}
|
||||
{releasesStatus === 'loading' && <div className="updates-panel__rel-empty">{t('updates.loading')}</div>}
|
||||
{releasesStatus === 'loaded' && rows.length === 0 && (
|
||||
<div className="updates-panel__rel-empty">{t('updates.none')}</div>
|
||||
)}
|
||||
{rows.map((r) => (
|
||||
<div key={r.name || r.version} className={`updates-panel__rel ${r.current ? 'is-current' : ''}`}>
|
||||
<div className="updates-panel__rel-row">
|
||||
<span className="updates-panel__rel-ver">v{r.version}</span>
|
||||
{r.current && <span className="updates-panel__rel-tag">{t('updates.current')}</span>}
|
||||
{r.prerelease && <span className="updates-panel__rel-pre">{t('updates.prerelease')}</span>}
|
||||
<span className="updates-panel__rel-date">{r.date}</span>
|
||||
</div>
|
||||
{r.notes && <pre className="updates-panel__rel-notes">{r.notes}</pre>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useRef, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Volume2, Play, Square, Loader, X, Mic } from 'lucide-react';
|
||||
import { generateSpeech } from '../api/generate';
|
||||
import { PRESETS } from '../utils/constants';
|
||||
@@ -12,7 +13,6 @@ import './VoicePreview.css';
|
||||
* sentence, hits Play → hears TTS output instantly (8 inference steps for
|
||||
* speed). The result is disposable — it doesn't save to history.
|
||||
*/
|
||||
const DEFAULT_TEXT = 'Hello! This is a preview of how I sound in this voice.';
|
||||
|
||||
export default function VoicePreview({
|
||||
open,
|
||||
@@ -21,7 +21,8 @@ export default function VoicePreview({
|
||||
initialProfileId = '',
|
||||
fileToMediaUrl,
|
||||
}) {
|
||||
const [text, setText] = useState(DEFAULT_TEXT);
|
||||
const { t } = useTranslation();
|
||||
const [text, setText] = useState(() => t('voicePreview.default_text'));
|
||||
const [voiceId, setVoiceId] = useState(initialProfileId);
|
||||
const [audioUrl, setAudioUrl] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -106,13 +107,13 @@ export default function VoicePreview({
|
||||
<div className="voice-preview">
|
||||
<div className="voice-preview__head">
|
||||
<span className="voice-preview__title">
|
||||
<Volume2 size={13} /> Voice Preview
|
||||
<Volume2 size={13} /> {t('voicePreview.title')}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="voice-preview__close"
|
||||
onClick={onClose}
|
||||
aria-label="Close preview"
|
||||
aria-label={t('voicePreview.close')}
|
||||
>
|
||||
<X size={12} />
|
||||
</button>
|
||||
@@ -124,23 +125,23 @@ export default function VoicePreview({
|
||||
value={voiceId}
|
||||
onChange={e => setVoiceId(e.target.value)}
|
||||
>
|
||||
<option value="">Default voice</option>
|
||||
<option value="">{t('voicePreview.default_voice')}</option>
|
||||
{profiles.filter(p => !p.instruct).length > 0 && (
|
||||
<optgroup label="Clone Profiles">
|
||||
<optgroup label={t('voicePreview.clone_profiles')}>
|
||||
{profiles.filter(p => !p.instruct).map(p => (
|
||||
<option key={p.id} value={p.id}>{p.name}</option>
|
||||
))}
|
||||
</optgroup>
|
||||
)}
|
||||
{profiles.filter(p => !!p.instruct).length > 0 && (
|
||||
<optgroup label="Designed Voices">
|
||||
<optgroup label={t('voicePreview.designed_voices')}>
|
||||
{profiles.filter(p => !!p.instruct).map(p => (
|
||||
<option key={p.id} value={p.id}>{p.name}</option>
|
||||
))}
|
||||
</optgroup>
|
||||
)}
|
||||
{PRESETS.length > 0 && (
|
||||
<optgroup label="Presets">
|
||||
<optgroup label={t('voicePreview.presets')}>
|
||||
{PRESETS.map(p => (
|
||||
<option key={p.id} value={`preset:${p.id}`}>{p.name}</option>
|
||||
))}
|
||||
@@ -153,7 +154,7 @@ export default function VoicePreview({
|
||||
value={text}
|
||||
onChange={e => setText(e.target.value)}
|
||||
rows={2}
|
||||
placeholder="Type something to hear…"
|
||||
placeholder={t('voicePreview.placeholder')}
|
||||
spellCheck={false}
|
||||
/>
|
||||
|
||||
@@ -173,7 +174,7 @@ export default function VoicePreview({
|
||||
<div className="voice-preview__foot">
|
||||
{loading ? (
|
||||
<Button variant="ghost" size="sm" onClick={handleStop} leading={<Square size={10} />}>
|
||||
Stop
|
||||
{t('voicePreview.stop')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
@@ -184,11 +185,12 @@ export default function VoicePreview({
|
||||
loading={loading}
|
||||
leading={!loading && <Play size={10} />}
|
||||
>
|
||||
{audioUrl ? 'Regenerate' : 'Preview'}
|
||||
{audioUrl ? t('voicePreview.regenerate') : t('voicePreview.preview')}
|
||||
</Button>
|
||||
)}
|
||||
<span className="voice-preview__hint">8 steps · fast preview</span>
|
||||
<span className="voice-preview__hint">{t('voicePreview.hint')}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import { copyText } from "../../utils/copyText";
|
||||
import QRCode from 'qrcode';
|
||||
import { Wifi, Globe, Copy, ExternalLink } from 'lucide-react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { apiJson, apiPost } from '../../api/client';
|
||||
import { openExternal } from '../../api/external';
|
||||
import NetworkToggle from '../NetworkToggle';
|
||||
@@ -29,6 +30,7 @@ import './SharingPanel.css';
|
||||
const TAILSCALE_DOWNLOAD_URL = 'https://tailscale.com/download';
|
||||
|
||||
export default function SharingPanel() {
|
||||
const { t } = useTranslation();
|
||||
const [status, setStatus] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [busy, setBusy] = useState(false);
|
||||
@@ -62,16 +64,16 @@ export default function SharingPanel() {
|
||||
const saveSharePort = async () => {
|
||||
const n = Number(sharePortInput);
|
||||
if (!Number.isInteger(n) || n < 1024 || n > 65535) {
|
||||
toast.error('Enter a port between 1024 and 65535');
|
||||
toast.error(t('sharing.port_error'));
|
||||
return;
|
||||
}
|
||||
setSavingPort(true);
|
||||
try {
|
||||
await apiPost('/system/set-env', { key: 'OMNIVOICE_SHARE_PORT', value: String(n) });
|
||||
setPorts((p) => (p ? { ...p, share_port_base: n } : p));
|
||||
toast.success('LAN-share port saved — applies next time you enable sharing');
|
||||
toast.success(t('sharing.port_saved'));
|
||||
} catch (e) {
|
||||
toast.error(`Could not save port: ${e.message}`);
|
||||
toast.error(t('sharing.port_save_failed', { message: e.message }));
|
||||
} finally {
|
||||
setSavingPort(false);
|
||||
}
|
||||
@@ -120,13 +122,13 @@ export default function SharingPanel() {
|
||||
if (r?.ok) {
|
||||
setUrl(r.url || '');
|
||||
setNote(r.note || '');
|
||||
toast.success('Tailscale serve enabled');
|
||||
toast.success(t('sharing.tailscale_enabled'));
|
||||
await refresh();
|
||||
} else {
|
||||
toast.error(r?.error || 'Could not enable Tailscale');
|
||||
toast.error(r?.error || t('sharing.tailscale_enable_failed'));
|
||||
}
|
||||
} catch (e) {
|
||||
toast.error(`Could not enable Tailscale: ${e.message}`);
|
||||
toast.error(t('sharing.tailscale_enable_error', { message: e.message }));
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
@@ -137,21 +139,21 @@ export default function SharingPanel() {
|
||||
try {
|
||||
const r = await apiPost('/system/tailscale/disable');
|
||||
if (r && r.ok === false) {
|
||||
toast.error(r.error || 'Could not disable Tailscale');
|
||||
toast.error(r.error || t('sharing.tailscale_disable_failed'));
|
||||
} else {
|
||||
setUrl('');
|
||||
setNote('');
|
||||
toast.success('Tailscale serve disabled');
|
||||
toast.success(t('sharing.tailscale_disabled'));
|
||||
}
|
||||
await refresh();
|
||||
} catch (e) {
|
||||
toast.error(`Could not disable Tailscale: ${e.message}`);
|
||||
toast.error(t('sharing.tailscale_disable_error', { message: e.message }));
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
};
|
||||
|
||||
const copy = (text) => { copyText(text); toast.success('Copied'); };
|
||||
const copy = (text) => { copyText(text); toast.success(t('sharing.copied')); };
|
||||
|
||||
const installed = !!status?.installed;
|
||||
const running = !!status?.running;
|
||||
@@ -159,23 +161,20 @@ export default function SharingPanel() {
|
||||
return (
|
||||
<section className="sharingpanel" aria-labelledby="sharingpanel-heading">
|
||||
<h3 id="sharingpanel-heading" className="sharingpanel__title">
|
||||
<Wifi size={14} /> Sharing & Remote Access
|
||||
<Wifi size={14} /> {t('sharing.title')}
|
||||
</h3>
|
||||
|
||||
<p className="sharingpanel__help">
|
||||
Expose this running OmniVoice instance to your other machines without
|
||||
restarting it. Loopback-only is the default — nothing is shared until
|
||||
you turn it on here.
|
||||
{t('sharing.help')}
|
||||
</p>
|
||||
|
||||
{/* ── LAN sharing ──────────────────────────────────────────────── */}
|
||||
<div className="sharingpanel__section" data-testid="sharing-lan">
|
||||
<h4 className="sharingpanel__subtitle">
|
||||
<Wifi size={12} /> Local network
|
||||
<Wifi size={12} /> {t('sharing.local_network')}
|
||||
</h4>
|
||||
<p className="sharingpanel__subhelp">
|
||||
Share on your Wi-Fi / Ethernet with a one-time access PIN. Other
|
||||
devices scan the QR code or open the link.
|
||||
{t('sharing.local_help')}
|
||||
</p>
|
||||
<NetworkToggle />
|
||||
</div>
|
||||
@@ -184,27 +183,26 @@ export default function SharingPanel() {
|
||||
{ports && (
|
||||
<div className="sharingpanel__section" data-testid="sharing-ports">
|
||||
<h4 className="sharingpanel__subtitle">
|
||||
<Globe size={12} /> Ports
|
||||
<Globe size={12} /> {t('sharing.ports_title')}
|
||||
</h4>
|
||||
<p className="sharingpanel__subhelp">
|
||||
These are set via environment variables read at startup. Change the
|
||||
backend or UI port by setting the variable and restarting OmniVoice.
|
||||
{t('sharing.ports_help')}
|
||||
</p>
|
||||
|
||||
<div className="sharingpanel__row">
|
||||
<span>Backend port</span>
|
||||
<span>{t('sharing.backend_port')}</span>
|
||||
<code className="sharingpanel__addr" data-testid="port-backend">{ports.backend_port}</code>
|
||||
<code className="sharingpanel__envname">OMNIVOICE_PORT</code>
|
||||
</div>
|
||||
|
||||
<div className="sharingpanel__row">
|
||||
<span>UI port</span>
|
||||
<span>{t('sharing.ui_port')}</span>
|
||||
<code className="sharingpanel__addr" data-testid="port-ui">{ports.ui_port}</code>
|
||||
<code className="sharingpanel__envname">OMNIVOICE_UI_PORT</code>
|
||||
</div>
|
||||
|
||||
<div className="sharingpanel__row">
|
||||
<label htmlFor="share-port-input">LAN-share port</label>
|
||||
<label htmlFor="share-port-input">{t('sharing.lan_share_port')}</label>
|
||||
<input
|
||||
id="share-port-input"
|
||||
type="number"
|
||||
@@ -223,12 +221,11 @@ export default function SharingPanel() {
|
||||
disabled={savingPort}
|
||||
data-testid="port-share-save"
|
||||
>
|
||||
{savingPort ? 'Saving…' : 'Save'}
|
||||
{savingPort ? t('sharing.saving') : t('common.save')}
|
||||
</button>
|
||||
</div>
|
||||
<p className="sharingpanel__note">
|
||||
Backend and UI ports apply on restart. The LAN-share port applies
|
||||
next time you enable sharing.
|
||||
{t('sharing.ports_note')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -236,18 +233,17 @@ export default function SharingPanel() {
|
||||
{/* ── Tailscale ────────────────────────────────────────────────── */}
|
||||
<div className="sharingpanel__section" data-testid="sharing-tailscale">
|
||||
<h4 className="sharingpanel__subtitle">
|
||||
<Globe size={12} /> Tailscale (private remote access)
|
||||
<Globe size={12} /> {t('sharing.tailscale_title')}
|
||||
</h4>
|
||||
|
||||
{loading && !status && (
|
||||
<p className="sharingpanel__subhelp">Checking for Tailscale…</p>
|
||||
<p className="sharingpanel__subhelp">{t('sharing.tailscale_checking')}</p>
|
||||
)}
|
||||
|
||||
{status && !installed && (
|
||||
<div className="sharingpanel__tailscale-absent" data-testid="tailscale-absent">
|
||||
<p className="sharingpanel__subhelp">
|
||||
Tailscale not detected. Install it to reach OmniVoice securely
|
||||
from anywhere on your private tailnet.
|
||||
{t('sharing.tailscale_absent')}
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
@@ -255,7 +251,7 @@ export default function SharingPanel() {
|
||||
onClick={() => openExternal(TAILSCALE_DOWNLOAD_URL)}
|
||||
data-testid="tailscale-install"
|
||||
>
|
||||
<ExternalLink size={12} /> Install Tailscale
|
||||
<ExternalLink size={12} /> {t('sharing.tailscale_install')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -264,8 +260,8 @@ export default function SharingPanel() {
|
||||
<div className="sharingpanel__tailscale-present">
|
||||
<p className="sharingpanel__subhelp">
|
||||
{running
|
||||
? 'Tailscale is running. Serve OmniVoice over your private tailnet.'
|
||||
: 'Tailscale is installed but not logged in. Start and sign in to Tailscale first.'}
|
||||
? t('sharing.tailscale_running')
|
||||
: t('sharing.tailscale_not_logged_in')}
|
||||
</p>
|
||||
|
||||
{!url ? (
|
||||
@@ -276,7 +272,7 @@ export default function SharingPanel() {
|
||||
disabled={busy}
|
||||
data-testid="tailscale-enable"
|
||||
>
|
||||
{busy ? 'Enabling…' : 'Enable Tailscale serve'}
|
||||
{busy ? t('sharing.tailscale_enabling') : t('sharing.tailscale_enable_btn')}
|
||||
</button>
|
||||
) : (
|
||||
<div className="sharingpanel__tailscale-url">
|
||||
@@ -286,8 +282,8 @@ export default function SharingPanel() {
|
||||
type="button"
|
||||
className="sharingpanel__iconbtn"
|
||||
onClick={() => copy(url)}
|
||||
aria-label="Copy Tailscale URL"
|
||||
title="Copy link"
|
||||
aria-label={t('sharing.tailscale_copy')}
|
||||
title={t('sharing.tailscale_copy')}
|
||||
data-testid="tailscale-copy"
|
||||
>
|
||||
<Copy size={12} />
|
||||
@@ -296,8 +292,8 @@ export default function SharingPanel() {
|
||||
type="button"
|
||||
className="sharingpanel__iconbtn"
|
||||
onClick={() => openExternal(url)}
|
||||
aria-label="Open Tailscale URL"
|
||||
title="Open in browser"
|
||||
aria-label={t('sharing.tailscale_open')}
|
||||
title={t('sharing.tailscale_open')}
|
||||
data-testid="tailscale-open"
|
||||
>
|
||||
<ExternalLink size={12} />
|
||||
@@ -308,7 +304,7 @@ export default function SharingPanel() {
|
||||
<img
|
||||
className="sharingpanel__qr"
|
||||
src={qr}
|
||||
alt="QR code for the Tailscale URL"
|
||||
alt={t('sharing.tailscale_qr_alt')}
|
||||
width={104}
|
||||
height={104}
|
||||
/>
|
||||
@@ -320,7 +316,7 @@ export default function SharingPanel() {
|
||||
disabled={busy}
|
||||
data-testid="tailscale-disable"
|
||||
>
|
||||
{busy ? 'Disabling…' : 'Stop Tailscale serve'}
|
||||
{busy ? t('sharing.tailscale_disabling') : t('sharing.tailscale_disable_btn')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -10,6 +10,8 @@ import { apiPost } from '../api/client';
|
||||
import { API } from '../api/client';
|
||||
import { playPing, isTauri } from '../utils/media';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import i18next from 'i18next';
|
||||
const t = i18next.t.bind(i18next);
|
||||
|
||||
/**
|
||||
* Encapsulates the entire dub pipeline workflow:
|
||||
@@ -206,24 +208,24 @@ export default function useDubWorkflow({ loadProjects, loadProfiles, loadDubHist
|
||||
dubClientJobIdRef.current = clientJobId;
|
||||
setDubJobId(clientJobId);
|
||||
const inputType = useAppStore.getState().dubInputType || 'video'; // #119
|
||||
useAppStore.getState().showPill('loading-model', inputType === 'audio' ? 'Preparing audio…' : 'Preparing video…', { cancellable: true });
|
||||
useAppStore.getState().showPill('loading-model', inputType === 'audio' ? t('dub_workflow.preparing_audio') : t('dub_workflow.preparing_video'), { cancellable: true });
|
||||
try {
|
||||
const data = await dubUpload(dubVideoFile, clientJobId, { signal: ctrl.signal, inputType });
|
||||
setDubJobId(data.job_id); if (data.filename) setDubFilename(data.filename);
|
||||
setDubTaskId(data.task_id); setDubPrepStage('extract');
|
||||
useAppStore.getState().showPill('loading-model', 'Extracting audio & scenes…', { cancellable: true });
|
||||
useAppStore.getState().showPill('loading-model', t('dub_workflow.extracting_audio_scenes'), { cancellable: true });
|
||||
await _waitForPrep(data.task_id, ctrl);
|
||||
setDubStep('transcribing'); setDubPrepStage(null);
|
||||
setTranscribeStart(Date.now()); setDubSegments([]);
|
||||
useAppStore.getState().showPill('transcribing', 'Transcribing audio…', { cancellable: true });
|
||||
useAppStore.getState().showPill('transcribing', t('dub_workflow.transcribing_audio'), { cancellable: true });
|
||||
await _waitForTranscribe(data.job_id, ctrl);
|
||||
setTranscribeStart(null); setDubStep('editing');
|
||||
useAppStore.getState().completePill('Transcription complete');
|
||||
useAppStore.getState().completePill(t('dub_workflow.transcription_complete'));
|
||||
loadProjects(); loadProfiles();
|
||||
} catch (err) {
|
||||
setDubPrepStage(null);
|
||||
if (err.name === 'AbortError') { toast('Upload cancelled'); setDubStep('idle'); useAppStore.getState().dismissPill(); }
|
||||
else { setDubError(err.message); setDubStep('idle'); toast.error('Upload failed: ' + err.message); useAppStore.getState().errorPill(err.message); }
|
||||
if (err.name === 'AbortError') { toast(t('dub_workflow.upload_cancelled')); setDubStep('idle'); useAppStore.getState().dismissPill(); }
|
||||
else { setDubError(err.message); setDubStep('idle'); toast.error(t('dub_workflow.upload_failed', { message: err.message })); useAppStore.getState().errorPill(err.message); }
|
||||
setTranscribeStart(null);
|
||||
} finally { dubAbortCtrlRef.current = null; }
|
||||
}, [setDubStep, setDubError, setDubFailure, setDubTracks, setDubPrepStage, setDubJobId, setDubFilename, setDubTaskId, setDubSegments, _waitForPrep, _waitForTranscribe, loadProjects, loadProfiles]);
|
||||
@@ -238,24 +240,24 @@ export default function useDubWorkflow({ loadProjects, loadProfiles, loadDubHist
|
||||
const clientJobId = Math.random().toString(36).slice(2, 10);
|
||||
dubClientJobIdRef.current = clientJobId;
|
||||
setDubJobId(clientJobId);
|
||||
useAppStore.getState().showPill('loading-model', 'Downloading video…', { cancellable: true });
|
||||
useAppStore.getState().showPill('loading-model', t('dub_workflow.downloading_video'), { cancellable: true });
|
||||
try {
|
||||
const data = await dubIngestUrl(clean, clientJobId, { signal: ctrl.signal, fetchSubs: !!opts.fetchSubs, subLangs: opts.subLangs });
|
||||
setDubJobId(data.job_id); setDubTaskId(data.task_id);
|
||||
useAppStore.getState().showPill('loading-model', 'Extracting audio & scenes…', { cancellable: true });
|
||||
useAppStore.getState().showPill('loading-model', t('dub_workflow.extracting_audio_scenes'), { cancellable: true });
|
||||
await _waitForPrep(data.task_id, ctrl);
|
||||
setDubStep('transcribing'); setDubPrepStage(null);
|
||||
setTranscribeStart(Date.now()); setDubSegments([]);
|
||||
useAppStore.getState().showPill('transcribing', 'Transcribing audio…', { cancellable: true });
|
||||
useAppStore.getState().showPill('transcribing', t('dub_workflow.transcribing_audio'), { cancellable: true });
|
||||
await _waitForTranscribe(data.job_id, ctrl);
|
||||
setTranscribeStart(null); setDubStep('editing');
|
||||
useAppStore.getState().completePill('Transcription complete');
|
||||
useAppStore.getState().completePill(t('dub_workflow.transcription_complete'));
|
||||
loadProjects(); loadProfiles();
|
||||
toast.success('Ingested ' + clean.slice(0, 60));
|
||||
toast.success(t('dub_workflow.ingested', { url: clean.slice(0, 60) }));
|
||||
} catch (err) {
|
||||
setDubPrepStage(null);
|
||||
if (err.name === 'AbortError') { toast('Ingest cancelled'); setDubStep('idle'); useAppStore.getState().dismissPill(); }
|
||||
else { setDubError(err.message); setDubStep('idle'); toast.error('URL ingest failed: ' + err.message); useAppStore.getState().errorPill(err.message); }
|
||||
if (err.name === 'AbortError') { toast(t('dub_workflow.ingest_cancelled')); setDubStep('idle'); useAppStore.getState().dismissPill(); }
|
||||
else { setDubError(err.message); setDubStep('idle'); toast.error(t('dub_workflow.ingest_failed', { message: err.message })); useAppStore.getState().errorPill(err.message); }
|
||||
setTranscribeStart(null);
|
||||
} finally { dubAbortCtrlRef.current = null; }
|
||||
}, [setDubStep, setDubError, setDubFailure, setDubTracks, setDubPrepStage, setDubJobId, setDubTaskId, setDubSegments, _waitForPrep, _waitForTranscribe, loadProjects, loadProfiles]);
|
||||
@@ -277,14 +279,14 @@ export default function useDubWorkflow({ loadProjects, loadProfiles, loadDubHist
|
||||
setTranscribeStart(null); setDubStep('editing'); loadProjects();
|
||||
} catch (err) {
|
||||
setTranscribeStart(null);
|
||||
if (err.name === 'AbortError') { toast('Retry cancelled'); setDubStep('idle'); }
|
||||
else { setDubError(err.message); setDubStep('idle'); toast.error('Transcription failed: ' + err.message); }
|
||||
if (err.name === 'AbortError') { toast(t('dub_workflow.retry_cancelled')); setDubStep('idle'); }
|
||||
else { setDubError(err.message); setDubStep('idle'); toast.error(t('dub_workflow.transcription_failed', { message: err.message })); }
|
||||
} finally { dubAbortCtrlRef.current = null; }
|
||||
}, [dubJobId, setDubError, setDubSegments, setDubStep, _waitForTranscribe, loadProjects]);
|
||||
|
||||
const handleDubImportSrt = useCallback(async (file) => {
|
||||
if (!dubJobId) {
|
||||
toast.error('Upload or ingest a video first — there is no job to attach subtitles to.');
|
||||
toast.error(t('dub_workflow.import_srt_no_job'));
|
||||
return;
|
||||
}
|
||||
if (!file) return;
|
||||
@@ -298,14 +300,14 @@ export default function useDubWorkflow({ loadProjects, loadProfiles, loadDubHist
|
||||
})));
|
||||
setDubStep('editing');
|
||||
const stats = res?.stats || {};
|
||||
const noteParts = [`Imported ${stats.imported ?? segs.length} cue(s) from ${file.name || '.srt'}`];
|
||||
if (stats.skipped_malformed) noteParts.push(`${stats.skipped_malformed} skipped (malformed)`);
|
||||
if (stats.dropped_overlap) noteParts.push(`${stats.dropped_overlap} dropped (overlap)`);
|
||||
if (stats.clamped_to_duration) noteParts.push(`${stats.clamped_to_duration} clamped to media length`);
|
||||
const noteParts = [t('dub_workflow.imported_cues', { count: stats.imported ?? segs.length, file: file.name || '.srt' })];
|
||||
if (stats.skipped_malformed) noteParts.push(t('dub_workflow.skipped_malformed', { count: stats.skipped_malformed }));
|
||||
if (stats.dropped_overlap) noteParts.push(t('dub_workflow.dropped_overlap', { count: stats.dropped_overlap }));
|
||||
if (stats.clamped_to_duration) noteParts.push(t('dub_workflow.clamped_to_duration', { count: stats.clamped_to_duration }));
|
||||
toast.success(noteParts.join(' · '), { duration: 6000 });
|
||||
loadProjects();
|
||||
} catch (err) {
|
||||
const msg = err?.message || 'SRT import failed';
|
||||
const msg = err?.message || t('dub_workflow.srt_import_failed');
|
||||
setDubError(msg);
|
||||
toast.error(msg);
|
||||
}
|
||||
@@ -318,8 +320,8 @@ export default function useDubWorkflow({ loadProjects, loadProfiles, loadDubHist
|
||||
const data = await dubCleanupSegments(dubJobId);
|
||||
setDubSegments(data.segments || []);
|
||||
const delta = before - (data.after ?? data.segments.length);
|
||||
toast.success(delta > 0 ? `Cleaned ${delta} fragment${delta === 1 ? '' : 's'}` : 'Segments already clean');
|
||||
} catch (err) { toast.error('Clean up failed: ' + err.message); }
|
||||
toast.success(delta > 0 ? t('dub_workflow.cleaned', { count: delta }) : t('dub_workflow.segments_clean'));
|
||||
} catch (err) { toast.error(t('dub_workflow.cleanup_failed', { message: err.message })); }
|
||||
}, [dubJobId, dubSegments, setDubSegments]);
|
||||
|
||||
const handleTranslateAll = useCallback(async () => {
|
||||
@@ -361,16 +363,16 @@ export default function useDubWorkflow({ loadProjects, loadProfiles, loadDubHist
|
||||
};
|
||||
}));
|
||||
if (data.cinematic_skipped === 'no-llm-configured') {
|
||||
toast('Cinematic quality needs an LLM — set TRANSLATE_BASE_URL + TRANSLATE_API_KEY (Ollama works locally). Falling back to Fast.', { icon: 'ℹ️', duration: 7000 });
|
||||
toast(t('dub_workflow.cinematic_no_llm'), { icon: 'ℹ️', duration: 7000 });
|
||||
}
|
||||
if (errors.length) {
|
||||
const unique = [...new Set(errors.map(e => e.error))];
|
||||
toast.error(`${errors.length}/${data.translated.length} segment${errors.length === 1 ? '' : 's'} failed: ${unique[0].slice(0, 120)}`, { duration: 6000 });
|
||||
toast.error(t('dub_workflow.translate_errors', { errorCount: errors.length, totalCount: data.translated.length, firstError: unique[0].slice(0, 120) }), { duration: 6000 });
|
||||
} else {
|
||||
const qLabel = data.quality_used === 'cinematic' ? ' (Cinematic)' : '';
|
||||
toast.success(`Translated ${data.translated.length} segment${data.translated.length === 1 ? '' : 's'} → ${data.target_lang}${qLabel}`);
|
||||
const qLabel = data.quality_used === 'cinematic' ? t('dub_workflow.translated_cinematic_suffix') : '';
|
||||
toast.success(t('dub_workflow.translated_segments', { count: data.translated.length, lang: data.target_lang }) + qLabel);
|
||||
}
|
||||
} catch (err) { setDubError('Translation failed: ' + err.message); }
|
||||
} catch (err) { setDubError(t('dub_workflow.translation_failed', { message: err.message })); }
|
||||
setIsTranslating(false);
|
||||
}, [dubSegments, dubLangCode, translateProvider, translateQuality, glossaryTerms, setIsTranslating, setDubSegments, setDubError]);
|
||||
|
||||
@@ -380,7 +382,7 @@ export default function useDubWorkflow({ loadProjects, loadProfiles, loadDubHist
|
||||
setDubStep('generating');
|
||||
setDubProgress({ current: 0, total: dubSegments.length, text: '' });
|
||||
setDubError('');
|
||||
const genLabel = regenOnly ? `Regenerating ${regenOnly.length} segment${regenOnly.length > 1 ? 's' : ''}…` : 'Generating dub…';
|
||||
const genLabel = regenOnly ? t('dub_workflow.regenerating', { count: regenOnly.length }) : t('dub_workflow.generating_dub');
|
||||
useAppStore.getState().showPill('generating', genLabel, { cancellable: true });
|
||||
try {
|
||||
const body = {
|
||||
@@ -423,7 +425,7 @@ export default function useDubWorkflow({ loadProjects, loadProfiles, loadDubHist
|
||||
if (evt.type === 'progress') {
|
||||
setDubProgress({ current: evt.current + 1, total: evt.total, text: evt.text });
|
||||
useAppStore.getState().setPillProgress(Math.round(((evt.current + 1) / evt.total) * 100));
|
||||
useAppStore.getState().setPillLabel(`Generating dub… ${evt.current + 1}/${evt.total}`);
|
||||
useAppStore.getState().setPillLabel(t('dub_workflow.generating_progress', { current: evt.current + 1, total: evt.total }));
|
||||
} else if (evt.type === 'done') {
|
||||
sawDone = true;
|
||||
setDubStep('done');
|
||||
@@ -449,7 +451,7 @@ export default function useDubWorkflow({ loadProjects, loadProfiles, loadDubHist
|
||||
try { const plan = await apiPost('/tools/incremental', { segments: dubSegments.map(s => ({ id: String(s.id), text: s.text, target_lang: s.target_lang, profile_id: s.profile_id, instruct: s.instruct, speed: s.speed, direction: s.direction })) }); setLastGenFingerprints(plan.fingerprints || {}); } catch (err) { console.warn('Incremental plan fallback failed:', err); }
|
||||
}
|
||||
} else if (evt.type === 'cancelled') {
|
||||
wasCancelled = true; setDubStep('editing'); setDubError('Generation aborted.'); toast('Dubbing aborted', { icon: '⏹' });
|
||||
wasCancelled = true; setDubStep('editing'); setDubError(t('dub_workflow.generation_aborted')); toast(t('dub_workflow.dubbing_aborted'), { icon: '⏹' });
|
||||
} else if (evt.type === 'error') setDubError(p => p + `\nSeg ${evt.segment}: ${evt.error}`);
|
||||
} catch (err) { console.warn('Dub generate SSE handler failed:', err); }
|
||||
}
|
||||
@@ -457,10 +459,10 @@ export default function useDubWorkflow({ loadProjects, loadProfiles, loadDubHist
|
||||
}
|
||||
setDubTaskId(null);
|
||||
if (!wasCancelled) {
|
||||
if (!sawDone) throw new Error('Generation stream ended before completion');
|
||||
if (!sawDone) throw new Error(t('dub_workflow.generation_stream_ended'));
|
||||
if (dubStep !== 'done') setDubStep('done');
|
||||
loadDubHistory(); loadProjects(); playPing();
|
||||
useAppStore.getState().completePill('Dub complete');
|
||||
useAppStore.getState().completePill(t('dub_workflow.dub_complete'));
|
||||
} else { useAppStore.getState().dismissPill(); }
|
||||
} catch (err) {
|
||||
setDubError(err.message); setDubStep('editing'); setDubTaskId(null);
|
||||
@@ -476,7 +478,7 @@ export default function useDubWorkflow({ loadProjects, loadProfiles, loadDubHist
|
||||
await tasksCancel(dubTaskId);
|
||||
} catch (e) {
|
||||
setDubStep(prevStep);
|
||||
toast.error('Failed to stop');
|
||||
toast.error(t('dub_workflow.stop_failed'));
|
||||
}
|
||||
}, [dubTaskId, dubStep, setDubStep]);
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import { probeAudioDuration } from '../utils/format';
|
||||
import { CLONE_MAX_SECONDS, PRESETS } from '../utils/constants';
|
||||
import { buildDesignInstruct } from '../utils/voiceInstruct';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import i18next from 'i18next';
|
||||
const t = i18next.t.bind(i18next);
|
||||
|
||||
/**
|
||||
* Encapsulates TTS generation logic, streaming response handling,
|
||||
@@ -44,7 +46,7 @@ export default function useTTS({ selectedProfile, setSelectedProfile, loadHistor
|
||||
if (dur && dur > CLONE_MAX_SECONDS) {
|
||||
setPendingTrimFile(file);
|
||||
setSelectedProfile(null);
|
||||
toast(`Audio is ${dur.toFixed(1)}s — trim to ≤${CLONE_MAX_SECONDS}s for best cloning`);
|
||||
toast(t('tts_errors.trim_hint', { duration: dur.toFixed(1), max: CLONE_MAX_SECONDS }));
|
||||
return;
|
||||
}
|
||||
setRefAudio(file);
|
||||
@@ -65,8 +67,8 @@ export default function useTTS({ selectedProfile, setSelectedProfile, loadHistor
|
||||
}, [text, insertTag]);
|
||||
|
||||
const handleGenerate = useCallback(async () => {
|
||||
if (!text.trim()) return toast.error("Please enter text");
|
||||
if (mode === 'clone' && !refAudio && !selectedProfile) return toast.error("Upload an audio or select a voice profile");
|
||||
if (!text.trim()) return toast.error(t('tts_errors.enter_text'));
|
||||
if (mode === 'clone' && !refAudio && !selectedProfile) return toast.error(t('tts_errors.upload_or_select'));
|
||||
setIsGenerating(true);
|
||||
setGenerationTime(0);
|
||||
const st = Date.now();
|
||||
@@ -112,10 +114,10 @@ export default function useTTS({ selectedProfile, setSelectedProfile, loadHistor
|
||||
// the same category" (#114).
|
||||
const { instruct: finalInstruct, unsupported, duplicates } = buildDesignInstruct(vdStates, instruct);
|
||||
if (unsupported.length) {
|
||||
toast(`Ignored unsupported instruct: ${unsupported.join(', ')}`, { icon: '⚠️' });
|
||||
toast(t('tts_errors.ignored_unsupported', { items: unsupported.join(', ') }), { icon: '⚠️' });
|
||||
}
|
||||
if (duplicates.length) {
|
||||
toast(`Ignored (category already set): ${duplicates.join(', ')}`, { icon: '⚠️' });
|
||||
toast(t('tts_errors.ignored_duplicate', { items: duplicates.join(', ') }), { icon: '⚠️' });
|
||||
}
|
||||
if (finalInstruct) formData.append("instruct", finalInstruct);
|
||||
if (selectedProfile) {
|
||||
@@ -155,8 +157,8 @@ export default function useTTS({ selectedProfile, setSelectedProfile, loadHistor
|
||||
playPing();
|
||||
} catch (err) {
|
||||
const msg = err?.name === 'AbortError'
|
||||
? 'Generation timed out — the model may still be downloading. Check Settings → Logs, then try again.'
|
||||
: ("Error: " + err.message);
|
||||
? t('tts_errors.timeout')
|
||||
: t('tts_errors.error_prefix', { message: err.message });
|
||||
toast.error(msg);
|
||||
} finally {
|
||||
if (abortTimer) clearTimeout(abortTimer);
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
{
|
||||
"update": {
|
||||
"available": "يتوفّر التحديث {{version}}",
|
||||
"install": "تثبيت وإعادة التشغيل",
|
||||
"install_hint": "نزّل التحديث وأعد التشغيل إلى الإصدار الجديد",
|
||||
"downloading": "جارٍ التحديث… {{pct}}%",
|
||||
"restart": "أعد التشغيل للتحديث",
|
||||
"busy": "أكمل الدبلجة أولاً — ثم ثبّت التحديث."
|
||||
},
|
||||
"nav": {
|
||||
"clone": "استنساخ",
|
||||
"design": "تصميم",
|
||||
@@ -16,7 +8,10 @@
|
||||
"launchpad": "لوحة الإطلاق",
|
||||
"gallery": "معرض",
|
||||
"transcripts": "النصوص",
|
||||
"omnidrive": "أومني درايف"
|
||||
"omnidrive": "أومني درايف",
|
||||
"move_rail_right": "حرك السكة إلى اليمين",
|
||||
"move_rail_left": "حرك السكة إلى اليسار",
|
||||
"flip_rail": "الوجه الجانب السكك الحديدية"
|
||||
},
|
||||
"settings": {
|
||||
"general": "عام",
|
||||
@@ -45,7 +40,40 @@
|
||||
"ffmpeg_missing": "لم يتم العثور عليه",
|
||||
"ffmpeg_current": "المسار الحالي",
|
||||
"ffmpeg_desc": "قم بتعيين مسار ffmpeg مخصص في حالة فشل الاكتشاف التلقائي.",
|
||||
"ffmpeg_saved": "تم تعيين مسار FFmpeg - أعد تشغيل الواجهة الخلفية للتطبيق."
|
||||
"ffmpeg_saved": "تم تعيين مسار FFmpeg - أعد تشغيل الواجهة الخلفية للتطبيق.",
|
||||
"diagnostics_copied": "تم نسخ بيانات التشخيص، ولصقها في تقرير المشكلة.",
|
||||
"updater_desktop": "يعمل التحديث فقط في تطبيق سطح المكتب.",
|
||||
"latest_version": "أنت على الإصدار الأحدث.",
|
||||
"logs_load_failed": "فشل تحميل السجلات: {{message}}",
|
||||
"clear_frontend_confirm": "هل تريد مسح المخزن المؤقت لسجل الواجهة الأمامية في الذاكرة؟",
|
||||
"clear_frontend_title": "مسح السجلات",
|
||||
"frontend_logs_cleared": "تم مسح سجلات الواجهة الأمامية",
|
||||
"clear_tauri_confirm": "هل تريد اقتطاع ملفات السجل من جانب Tauri؟ سيستمر نظام التشغيل في كتابة إدخالات جديدة.",
|
||||
"clear_tauri_title": "مسح سجلات Tauri",
|
||||
"nothing_to_clear": "لا يوجد شيء لمسحه - لا يوجد ملف سجل Tauri على القرص حتى الآن.",
|
||||
"cleared_tauri_one": "تم مسح {{count}} ملف سجل Tauri",
|
||||
"cleared_tauri_other": "تم مسح {{count}} ملفات سجل Tauri",
|
||||
"clear_tauri_failed": "فشل في مسح سجلات Tauri: {{message}}",
|
||||
"clear_backend_confirm": "هل تريد مسح وقت تشغيل الواجهة الخلفية + سجلات الأعطال؟ لا يمكن التراجع عن هذا.",
|
||||
"clear_backend_title": "مسح السجلات",
|
||||
"backend_logs_cleared": "تم مسح سجلات الواجهة الخلفية",
|
||||
"clear_backend_failed": "فشل في مسح السجلات",
|
||||
"copy_failed": "فشل النسخ: {{message}}",
|
||||
"update_check_failed": "فشل التحقق من التحديث: {{message}}",
|
||||
"save_failed": "فشل الحفظ: {{message}}",
|
||||
"clear_failed": "فشل المسح: {{message}}",
|
||||
"engine_switched": "{{family}} → {{engine}}",
|
||||
"channel_set_failed": "فشل تعيين القناة: {{message}}",
|
||||
"updater_downloading": "جارٍ التنزيل {{version}}…",
|
||||
"updater_installed": "تم التثبيت - إعادة التشغيل.",
|
||||
"updater_available_title": "التحديث متاح",
|
||||
"updater_available_body": "الإصدار {{version}} متاح.\n\n{{notes}}\n\nتحميل وتثبيت الآن؟",
|
||||
"updater_notes_fallback": "راجع ملاحظات الإصدار على GitHub.",
|
||||
"shortcut_load_failed": "تعذر تحميل الاختصار: {{message}}",
|
||||
"shortcut_set": "تم ضبط اختصار الإملاء على {{shortcut}}",
|
||||
"shortcut_register_failed": "تعذر التسجيل: {{message}}",
|
||||
"shortcut_reset": "إعادة التعيين إلى الوضع الافتراضي",
|
||||
"shortcut_reset_failed": "فشلت إعادة التعيين: {{message}}"
|
||||
},
|
||||
"bootstrap": {
|
||||
"title": "OmniVoice Studio",
|
||||
@@ -69,7 +97,13 @@
|
||||
"waiting_output": "في انتظار البيانات…",
|
||||
"auto_detect": "تحديد تلقائي",
|
||||
"suggest_lang": "تغيير إلى اللغة العربية؟",
|
||||
"select_lang": "اللغة:"
|
||||
"select_lang": "اللغة:",
|
||||
"region_global": "عالمي (مباشر)",
|
||||
"region_china": "الصين (المرآة)",
|
||||
"region_russia": "روسيا (مرآة)",
|
||||
"region_restricted": "مقيد (مرآة)",
|
||||
"unknown_error": "خطأ غير معروف",
|
||||
"retrying": "جارٍ إعادة المحاولة..."
|
||||
},
|
||||
"stories": {
|
||||
"title": "محرر القصص",
|
||||
@@ -158,7 +192,17 @@
|
||||
"reload": "فرض تحديث واجهة المستخدم",
|
||||
"backend": "الخلفية",
|
||||
"frontend": "الواجهة الأمامية",
|
||||
"tauri": "تاوري"
|
||||
"tauri": "تاوري",
|
||||
"cancelOp": "إلغاء العملية",
|
||||
"dismiss": "استبعاد",
|
||||
"dismissStatus": "تجاهل الحالة",
|
||||
"search": "بحث…",
|
||||
"no_matches": "لا توجد مباريات",
|
||||
"recent_and_popular": "الأخيرة والشعبية",
|
||||
"popular_label": "شعبية",
|
||||
"showing_of": "عرض {{shown}} من {{total}}. اكتب للبحث...",
|
||||
"yes": "نعم",
|
||||
"no": "لا"
|
||||
},
|
||||
"launchpad": {
|
||||
"greeting": "مرحبا هناك",
|
||||
@@ -180,7 +224,16 @@
|
||||
"locked": "مغلق",
|
||||
"open": "مفتوح",
|
||||
"try_it": "جربه",
|
||||
"audio_only": "الصوت فقط"
|
||||
"audio_only": "الصوت فقط",
|
||||
"stories_title": "قصص",
|
||||
"stories_desc": "كتب صوتية متعددة الأصوات - ألقي شخصياتك، وأدخل النص، وقم بالتصدير حسب الفصل.",
|
||||
"gallery_title": "معرض الصوت",
|
||||
"gallery_desc": "تصفح الأصوات المصممة الجاهزة حسب اللهجة والعمر والأسلوب - بدون إعداد.",
|
||||
"transcripts_title": "النصوص",
|
||||
"transcripts_desc": "حوّل الصوت أو الفيديو إلى نص قابل للتحرير والبحث، عبر 646 لغة.",
|
||||
"recent_files": "الملفات الأخيرة",
|
||||
"view_all_files": "عرض كافة الملفات",
|
||||
"file": "ملف"
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "موجه",
|
||||
@@ -271,11 +324,6 @@
|
||||
"outputs": "النواتج",
|
||||
"crash_log": "سجل الأعطال",
|
||||
"update_endpoint": "تحديث نقطة النهاية",
|
||||
"update_channel": "قناة التحديث",
|
||||
"channel_stable": "مستقر",
|
||||
"channel_preview": "معاينة",
|
||||
"channel_set": "تم ضبط قناة التحديث على {{channel}}",
|
||||
"channel_preview_hint": "تتابع المعاينة أحدث إصدار من main — ميزات أحدث وأقل اختبارًا. تعود إلى المستقر إذا كان هناك إصدار مستقر أحدث.",
|
||||
"yes": "نعم",
|
||||
"no": "لا",
|
||||
"web_preview": "معاينة الويب",
|
||||
@@ -284,7 +332,12 @@
|
||||
"copy_diagnostics": "نسخ التشخيص",
|
||||
"github": "أومنيفويس على جيثب",
|
||||
"model_card": "البطاقة النموذجية",
|
||||
"commercial_license": "رخصة تجارية"
|
||||
"commercial_license": "رخصة تجارية",
|
||||
"update_channel": "قناة التحديث",
|
||||
"channel_stable": "مستقر",
|
||||
"channel_preview": "معاينة",
|
||||
"channel_set": "تم ضبط قناة التحديث على {{channel}}",
|
||||
"channel_preview_hint": "تتابع المعاينة أحدث إصدار من main — ميزات أحدث وأقل اختبارًا. تعود إلى المستقر إذا كان هناك إصدار مستقر أحدث."
|
||||
},
|
||||
"privacy": {
|
||||
"desc": "كل شيء يعمل على <1>هذا الجهاز</1>. لا يغادر الصوت والفيديو والنصوص جهاز الكمبيوتر الخاص بك أبدًا ما لم تستخدم صراحةً مترجمًا عبر الإنترنت (Google، DeepL، وما إلى ذلك) أو تضغط على HuggingFace.",
|
||||
@@ -340,7 +393,30 @@
|
||||
"unavailable": "غير متاح",
|
||||
"use": "استخدم",
|
||||
"loading": "جارٍ تحميل المحركات…",
|
||||
"refresh": "تحديث"
|
||||
"refresh": "تحديث",
|
||||
"matrixTitle": "مصفوفة توافق المحرك",
|
||||
"loadFailed": "فشل تحميل المحركات: {{message}}",
|
||||
"couldNotLoad": "لا يمكن تحميل المحركات: {{message}}",
|
||||
"retry": "أعد المحاولة",
|
||||
"activeEngine": "نشط {{family}}: {{engine}}",
|
||||
"engineCompatLabel": "{{family}} توافق المحرك",
|
||||
"active": "نشط",
|
||||
"whyUnavailable": "لماذا غير متوفر؟",
|
||||
"lastError": "الخطأ الأخير: {{error}}",
|
||||
"installedAndReady": "مثبتة وجاهزة",
|
||||
"notInstalled": "غير مثبت",
|
||||
"available": "متاح",
|
||||
"subprocessTitle": "يعمل في العملية الفرعية الخاصة به + venv",
|
||||
"inProcessTitle": "يعمل في عملية OmniVoice Python",
|
||||
"testEngine": "محرك الاختبار",
|
||||
"testing": "اختبار…",
|
||||
"recheck": "أعد الفحص",
|
||||
"rechecking": "إعادة الفحص…",
|
||||
"latencyMs": "{{ms}} مللي ثانية",
|
||||
"failed": "فشل",
|
||||
"acceptLicense": "قبول الترخيص",
|
||||
"noBackends": "لم يتم تسجيل أي الواجهات الخلفية.",
|
||||
"switch_failed": "فشل في تبديل المحرك"
|
||||
},
|
||||
"capture": {
|
||||
"desc": "تعمل مفاتيح التشغيل السريع العامة فقط في تطبيق سطح المكتب. تستخدم واجهة مستخدم الويب اختصارًا <1>Ctrl+Shift+Space</1> داخل الصفحة أثناء التركيز على النافذة.",
|
||||
@@ -352,13 +428,55 @@
|
||||
"record_shortcut": "سجل الاختصار",
|
||||
"recording": "تسجيل…",
|
||||
"save": "حفظ",
|
||||
"reset_default": "إعادة التعيين إلى الوضع الافتراضي"
|
||||
"reset_default": "إعادة التعيين إلى الوضع الافتراضي",
|
||||
"listening_label": "جاري الاستماع…",
|
||||
"transcribing_label": "جارٍ النسخ…",
|
||||
"pasted": "تم لصقه",
|
||||
"no_speech": "لم يتم اكتشاف أي كلام",
|
||||
"mic_denied": "تم رفض الوصول إلى الميكروفون",
|
||||
"mic_denied_toast": "تم رفض الوصول إلى الميكروفون. {{hint}}",
|
||||
"mic_hint_mac": "macOS: افتح إعدادات النظام ← الخصوصية والأمان ← الميكروفون وقم بتمكين OmniVoice.",
|
||||
"mic_hint_windows": "Windows: افتح الإعدادات ← الخصوصية والأمان ← الميكروفون واسمح لـ OmniVoice.",
|
||||
"mic_hint_linux": "Linux: تأكد من وجود المستخدم الخاص بك في المجموعة الصوتية وأن WebView لديه حق الوصول إلى الميكروفون.",
|
||||
"transcription_failed": "فشل النسخ: {{message}}"
|
||||
},
|
||||
"logs": {
|
||||
"no_tauri_log": "لم يتم تسجيل دخول Tauri على القرص حتى الآن — قم بتشغيله عبر إصدار سطح المكتب لإنتاج واحد",
|
||||
"empty_frontend": "لم يتم التقاط أي إدخالات لوحدة التحكم الأمامية حتى الآن. تفاعل مع التطبيق — ستظهر كل وحدة تحكم* هنا.",
|
||||
"empty_tauri": "لا يوجد سجل Tauri متاح. يعمل في غلاف سطح المكتب فقط.",
|
||||
"empty_backend": "سجل وقت التشغيل فارغ. سيظهر النشاط هنا عندما تقوم الواجهة الخلفية بتسجيله."
|
||||
"empty_backend": "سجل وقت التشغيل فارغ. سيظهر النشاط هنا عندما تقوم الواجهة الخلفية بتسجيله.",
|
||||
"title": "سجلات",
|
||||
"source_backend": "الخلفية",
|
||||
"source_frontend": "الواجهة الأمامية",
|
||||
"source_tauri": "تاوري",
|
||||
"expand": "قم بتوسيع السجلات",
|
||||
"collapse": "طي السجلات",
|
||||
"expand_aria": "قم بتوسيع لوحة السجلات",
|
||||
"collapse_aria": "طي لوحة السجلات",
|
||||
"drag_resize": "اسحب لتغيير الحجم",
|
||||
"refresh": "تحديث",
|
||||
"refresh_aria": "تحديث السجلات",
|
||||
"copy_visible": "نسخ السجل المرئي",
|
||||
"copy_visible_aria": "نسخ السجل المرئي",
|
||||
"clear": "واضح",
|
||||
"clear_aria": "مسح السجل",
|
||||
"report_issue": "الإبلاغ عن مشكلة (تشخيص النسخ)",
|
||||
"report_issue_aria": "الإبلاغ عن مشكلة",
|
||||
"close": "إغلاق",
|
||||
"close_aria": "إغلاق لوحة السجلات",
|
||||
"join_discord": "انضم إلى خلافنا",
|
||||
"join_discord_aria": "انضم إلى مجتمع Discord الخاص بنا",
|
||||
"support_project": "دعم هذا المشروع",
|
||||
"support_project_aria": "دعم هذا المشروع",
|
||||
"empty_frontend_short": "لا يوجد إخراج لوحدة التحكم الأمامية حتى الآن.",
|
||||
"empty_lines": "لا خطوط.",
|
||||
"all_clear": "✅ كل شيء واضح — لم يتم اكتشاف أية مشكلات",
|
||||
"log_cleared": "تم مسح سجل {{source}}",
|
||||
"clear_failed": "فشل المسح: {{message}}",
|
||||
"log_copied": "تم نسخ سجل {{source}}",
|
||||
"copy_failed": "فشل النسخ: {{message}}",
|
||||
"report_copied": "تم نسخ التقرير التشخيصي، والصقه في مشكلة GitHub.",
|
||||
"report_failed": "فشل التقرير: {{message}}"
|
||||
},
|
||||
"voice_profile": {
|
||||
"test_text": "مرحبًا – هذا اختبار لهذا الصوت.",
|
||||
@@ -526,7 +644,17 @@
|
||||
"install_already": "تم تثبيت {{engine}} بالفعل",
|
||||
"install_ok": "{{engine}} تم التثبيت",
|
||||
"install_failed": "فشل التثبيت: {{message}}",
|
||||
"prep_elapsed": "انقضى {{time}}"
|
||||
"prep_elapsed": "انقضى {{time}}",
|
||||
"add_language": "أضف لغة",
|
||||
"search_languages": "لغات البحث...",
|
||||
"languages_selected_one": "تم تحديد لغة {{count}}",
|
||||
"languages_selected_other": "تم تحديد {{count}} اللغات",
|
||||
"more_to_narrow": "+{{count}} المزيد - اكتب للتضييق",
|
||||
"no_matches": "لا توجد مباريات",
|
||||
"diagnostic_copied": "منقول التشخيص",
|
||||
"copy_failed": "فشل النسخ",
|
||||
"open_docs": "افتح المستندات",
|
||||
"copy_diagnostic": "نسخ التشخيص"
|
||||
},
|
||||
"glossary": {
|
||||
"title": "مسرد",
|
||||
@@ -601,7 +729,17 @@
|
||||
"more_actions_title": "المزيد من الإجراءات",
|
||||
"speaker_pick": "اختر…",
|
||||
"speaker_title_detected": "مكبر الصوت - اختر مما تم اكتشافه، أو اكتب اسمًا مخصصًا",
|
||||
"speaker_title_custom": "مكبر الصوت - اكتب اسمًا (لم يتم اكتشاف أي نسخ للكتابة)"
|
||||
"speaker_title_custom": "مكبر الصوت - اكتب اسمًا (لم يتم اكتشاف أي نسخ للكتابة)",
|
||||
"time_edit_title": "انقر لتعديل وقت البدء (m:ss.s). أدخل للالتزام، Esc للإلغاء.",
|
||||
"fit_fits": "يناسب",
|
||||
"fit_fits_title": "يتناسب الصوت ذو المعدل الطبيعي داخل الفتحة.",
|
||||
"fit_overflows": "الفائض +{{seconds}}s",
|
||||
"fit_overflows_title": "كان النص المترجم أطول من الفتحة الأصلية بمقدار {{seconds}}s. كان الصوت مقصوصًا بشدة. قم بتقصير النص أو قم بتبديل التوقيت إلى \"تمدد الفيديو\".",
|
||||
"fit_stretched": "فيديو {{ratio}}×",
|
||||
"fit_stretched_title": "وضع الفيديو الممتد: تم إبطاء فيديو هذا المقطع إلى {{ratio}}× ليناسب الصوت الطبيعي المدبلج.",
|
||||
"fit_compressed_title": "يمثل صوت تحويل النص إلى كلام (TTS) {{pct}}% من الفتحة - وهو مضغوط بشدة.",
|
||||
"fit_audio_title": "الصوت مناسب داخل الفتحة.",
|
||||
"fit_ratio_title": "يمثل صوت تحويل النص إلى كلام (TTS) {{pct}}% من الفتحة."
|
||||
},
|
||||
"voice": {
|
||||
"personality": "الشخصية",
|
||||
@@ -696,10 +834,24 @@
|
||||
"status_running": "تشغيل",
|
||||
"status_done": "تم",
|
||||
"status_failed": "فشل",
|
||||
"status_cancelled": "تم الإلغاء"
|
||||
"status_cancelled": "تم الإلغاء",
|
||||
"add_to_queue_title": "إضافة مقاطع فيديو إلى قائمة الانتظار",
|
||||
"drop_hint_text": "قم بإسقاط ملفات الفيديو هنا أو انقر للتصفح",
|
||||
"drop_formats": "MP4 · MOV · MKV · ويب إم",
|
||||
"files_kicker": "الملفات ({{count}})",
|
||||
"file_size_mb": "{{size}} ميغابايت",
|
||||
"target_languages": "اللغات المستهدفة",
|
||||
"voice_kicker": "صوت",
|
||||
"default_option": "الافتراضي",
|
||||
"clone_profiles": "ملفات تعريف الاستنساخ",
|
||||
"presets": "الإعدادات المسبقة",
|
||||
"preserve_bg": "الحفاظ على صوت الخلفية (الموسيقى/FX)",
|
||||
"estimate": "{{videos}} مقاطع الفيديو × {{langs}} اللغة (اللغات) = {{jobs}} الوظيفة (الوظائف)",
|
||||
"select_files_langs": "حدد الملفات واللغات",
|
||||
"add_to_queue": "إضافة إلى قائمة الانتظار"
|
||||
},
|
||||
"gallery": {
|
||||
"title": "معرض",
|
||||
"title": "OmniVoice معرض",
|
||||
"search_placeholder": "بحث في يوتيوب...",
|
||||
"all_voices": "جميع الأصوات ({{count}})",
|
||||
"no_voices": "لا توجد أصوات بعد",
|
||||
@@ -714,6 +866,14 @@
|
||||
"youtube_results": "نتائج يوتيوب ({{count}})",
|
||||
"clone_profile": "الملف الشخصي استنساخ",
|
||||
"crop_audio": "اقتصاص الصوت",
|
||||
"cat_disney": "ديزني",
|
||||
"cat_anime": "أنيمي",
|
||||
"cat_marvel": "مارفل / دي سي",
|
||||
"cat_celebs": "المشاهير",
|
||||
"cat_politicians": "السياسيون",
|
||||
"cat_news": "مذيعي الأخبار",
|
||||
"cat_gaming": "الألعاب",
|
||||
"cat_books": "كتب / أفلام",
|
||||
"subtitle": "مئات من الأصوات المصممة الجاهزة - اختر واحدة وانطلق.",
|
||||
"zone_archetypes": "النماذج الأولية",
|
||||
"zone_imports": "وارداتي",
|
||||
@@ -818,7 +978,6 @@
|
||||
"badge": "رخصة تجارية",
|
||||
"hero_title": "شحن أصوات الذكاء الاصطناعي في الإنتاج",
|
||||
"hero_desc": "OmniVoice Studio متاح المصدر بموجب ترخيص المصدر الوظيفي (FSL). يمكن لمعظم المستخدمين التقييم وإنشاء نماذج أولية وحتى النشر داخليًا بدون اتفاقية تجارية. لا تحتاج إلى ترخيص تجاري إلا إذا كنت تقوم ببناء منتج أو خدمة منافسة، أو إذا كانت حالة الاستخدام الخاصة بك تقع خارج حدود FSL.",
|
||||
"hero_note": "يتطلب إنشاء منتج أو خدمة منافسة أو نشرها على نطاق واسع (على سبيل المثال، خدمة واجهة برمجة تطبيقات الدفع لكل استخدام) ترخيصًا تجاريًا. ستتوفر مستويات التسعير قريبًا - تواصل معنا في هذه الأثناء.",
|
||||
"why_title": "لماذا تختار الشركات OmniVoice",
|
||||
"pricing_title": "التسعير",
|
||||
"faq_title": "الأسئلة الشائعة",
|
||||
@@ -838,7 +997,8 @@
|
||||
"benefit_source": "مصدر متاح الأساسية",
|
||||
"benefit_source_desc": "الرؤية الكاملة في المكدس. التدقيق والشوكة والتكيف ضمن شروط الترخيص.",
|
||||
"benefit_lang": "646 لغة",
|
||||
"benefit_lang_desc": "قم بالنسخ والترجمة والدبلجة عبر 646 لغة بجودة تضاهي المستوى البشري."
|
||||
"benefit_lang_desc": "قم بالنسخ والترجمة والدبلجة عبر 646 لغة بجودة تضاهي المستوى البشري.",
|
||||
"hero_note": "الاستخدام الداخلي - حتى على نطاق واسع - مجاني بموجب قانون FSL؛ الترخيص التجاري مطلوب فقط لتقديم OmniVoice للآخرين كمنتج أو خدمة منافسة (واجهة برمجة تطبيقات مستضافة أو الدفع لكل استخدام، أو تطبيق مُعاد بيعه أو ذو علامة بيضاء). ستتوفر مستويات التسعير قريبًا - تواصل معنا في هذه الأثناء."
|
||||
},
|
||||
"exportModal": {
|
||||
"export": "تصدير",
|
||||
@@ -927,7 +1087,479 @@
|
||||
"dubbing_title": "شاهد الدبلجة أثناء العمل",
|
||||
"dubbing_sync": "التشغيل المتزامن",
|
||||
"dubbing_picker": "جرب لغة أخرى:",
|
||||
"dubbing_cta": "قم بتشغيل هذا على الفيديو الخاص بك →"
|
||||
"dubbing_cta": "قم بتشغيل هذا على الفيديو الخاص بك →",
|
||||
"dubbing_loading": "جارٍ تحميل العرض التوضيحي للدبلجة...",
|
||||
"dubbing_dismiss": "رفض الدبلجة التجريبية",
|
||||
"original_tag": "original",
|
||||
"dubbed_tag": "يطلق عليها اسم",
|
||||
"script_conversational": "محادثة",
|
||||
"script_technical": "المفردات التقنية",
|
||||
"script_french": "غير الإنجليزية (الفرنسية)",
|
||||
"aria_pause": "إيقاف مؤقت {{label}}",
|
||||
"aria_hear": "استمع {{label}}",
|
||||
"aria_replay": "إعادة تشغيل {{label}} من خلال الناسخ"
|
||||
},
|
||||
"direction": {
|
||||
"title": "اتجاه المقطع #{{id}}",
|
||||
"desc": "أخبر خط الأنابيب كيف يجب أن يشعر هذا الخط. تعمل اللغة الإنجليزية البسيطة — يقوم النظام بتعيين كلماتك على تصنيف مستقر (الطاقة / العاطفة / السرعة / العلاقة الحميمة / الشكليات)، ثم يقوم بربط التصنيف من خلال الترجمة السينمائية، وتحويل النص إلى كلام، والملاءمة.",
|
||||
"label": "الاتجاه",
|
||||
"lineHint": "السطر: \"{{text}}\"",
|
||||
"placeholder": "على سبيل المثال عاجل ومتفاجئ / دافئ، متفائل / هامس، حميم",
|
||||
"previewParse": "تحليل المعاينة",
|
||||
"previewFailed": "فشلت المعاينة: {{message}}",
|
||||
"clear": "واضح",
|
||||
"cancel": "إلغاء",
|
||||
"saveDirection": "حفظ الاتجاه",
|
||||
"ttsInstruct": "تعليمات تحويل النص إلى كلام:",
|
||||
"nothingParsed": "- (لم يتم تحليل أي شيء)",
|
||||
"translateHint": "تلميح الترجمة:",
|
||||
"rateBias": "تحيز المعدل:",
|
||||
"speedsUp": "يسرع",
|
||||
"slowsDown": "يبطئ",
|
||||
"taxonomyTokens": "رموز التصنيف"
|
||||
},
|
||||
"errors": {
|
||||
"title": "ضربت علامة التبويب هذه عقبة.",
|
||||
"desc": "لا تقلق، فباقي التطبيق لا يزال يعمل. يمكنك تبديل علامات التبويب أو المحاولة مرة أخرى أدناه.",
|
||||
"tryAgain": "حاول مرة أخرى",
|
||||
"openDocs": "افتح المستندات لهذا الخطأ"
|
||||
},
|
||||
"keyboard": {
|
||||
"title": "اختصارات لوحة المفاتيح",
|
||||
"footer": "اضغط على <1>?</1> في أي وقت لفتح هذا.",
|
||||
"or": "أو",
|
||||
"nav": "الملاحة",
|
||||
"nav_cheatsheet": "عرض ورقة الغش هذه",
|
||||
"nav_closeModal": "إغلاق مشروط / إلغاء",
|
||||
"nav_save": "حفظ المشروع/الالتزام بالقطع",
|
||||
"segmentEditor": "محرر المقطع",
|
||||
"seg_split": "تقسيم الجزء عند المؤشر",
|
||||
"seg_merge": "دمج مع الجزء التالي",
|
||||
"seg_undo": "تراجع",
|
||||
"seg_redo": "إعادة",
|
||||
"seg_click": "العمل الأساسي",
|
||||
"seg_shiftClick": "حدد النطاق",
|
||||
"trimmer": "أداة تشذيب الصوت",
|
||||
"trim_playPause": "معاينة التشغيل / الإيقاف المؤقت",
|
||||
"trim_nudgeStart": "دفع مقبض البداية",
|
||||
"trim_nudgeEnd": "مقبض نهاية الدفع",
|
||||
"trim_fineNudge": "دفعة جيدة",
|
||||
"trim_coarseNudge": "دفعة خشنة",
|
||||
"trim_zoomIn": "تكبير / تصغير",
|
||||
"trim_fitAll": "تناسب الجميع / اختيار مناسب",
|
||||
"trim_confirm": "تأكيد القطع",
|
||||
"dub": "يصفه",
|
||||
"dub_generate": "توليد يصفه",
|
||||
"dub_sidebar": "تبديل الشريط الجانبي"
|
||||
},
|
||||
"network": {
|
||||
"sharing_on_title": "المشاركة قيد التشغيل - انقر للحصول على التفاصيل",
|
||||
"share_on_network": "شارك على شبكتك",
|
||||
"switching": "التبديل…",
|
||||
"network": "الشبكة",
|
||||
"local": "محلي",
|
||||
"share_confirm_title": "هل تريد المشاركة على شبكتك؟",
|
||||
"share_confirm_hint": "ستتمكن الأجهزة الأخرى الموجودة على شبكة Wi-Fi/Ethernet لديك من الوصول إلى OmniVoice باستخدام رقم التعريف الشخصي (PIN) للوصول الذي يظهر بمجرد تشغيله.",
|
||||
"enabling": "جارٍ التمكين…",
|
||||
"enable": "تمكين",
|
||||
"shared_title": "تمت مشاركتها على شبكتك",
|
||||
"no_interface": "لا توجد واجهة شبكة يمكن الوصول إليها — اتصل بشبكة Wi-Fi/Ethernet.",
|
||||
"copy_link": "انسخ الرابط",
|
||||
"open_in_browser": "فتح في المتصفح",
|
||||
"qr_alt": "QR لـ {{ip}}",
|
||||
"pin": "رقم التعريف الشخصي:",
|
||||
"stop_sharing": "توقف عن المشاركة",
|
||||
"copied": "منقول",
|
||||
"enable_error": "تعذر تمكين المشاركة: {{message}}",
|
||||
"disable_error": "تعذر التعطيل: {{message}}"
|
||||
},
|
||||
"readiness": {
|
||||
"checking_system": "نظام فحص…",
|
||||
"all_ready": "جميع الأنظمة جاهزة",
|
||||
"system_readiness": "جاهزية النظام",
|
||||
"asr_model": "نموذج ASR",
|
||||
"loaded_ready": "محملة وجاهزة",
|
||||
"loading_first_run": "جارٍ التحميل... (قد يستغرق هذا من دقيقة إلى دقيقتين عند التشغيل لأول مرة)",
|
||||
"failed_to_load": "فشل التحميل",
|
||||
"not_loaded_yet": "لم يتم تحميله بعد - سيتم تحميله عند النسخ الأول",
|
||||
"error_check_logs": "خطأ: {{error}}. تحقق من السجلات وحاول إعادة التشغيل.",
|
||||
"check_logs_restart": "تحقق من السجلات بحثًا عن أخطاء تحميل النموذج. حاول إعادة التشغيل.",
|
||||
"llm_cinematic": "ماجستير في القانون (السينمائي)",
|
||||
"llm_configure": "قم بتكوين TRANSLATE_BASE_URL لجودة الترجمة السينمائية",
|
||||
"llm_set_env": "قم بتعيين متغيرات البيئة TRANSLATE_BASE_URL وTRANSLATE_API_KEY. يعمل مع Ollama وOpenAI وLM Studio وما إلى ذلك.",
|
||||
"llm_optional": "اختياري — قم بتعيين TRANSLATE_BASE_URL للجودة السينمائية"
|
||||
},
|
||||
"license": {
|
||||
"title": "Supertonic-3 — قبول الترخيص",
|
||||
"intro": "يتم شحن Supertonic-3 بموجب رخصتين مختلفتين. يرجى مراجعة كليهما قبل تمكين المحرك.",
|
||||
"sdk_heading": "كود SDK · معهد ماساتشوستس للتكنولوجيا",
|
||||
"sdk_desc": "إن مجموعة أدوات تطوير البرامج (SDK) لاستدلال Python (الأسرع من الصوت) مرخصة من معهد ماساتشوستس للتكنولوجيا. الاستخدام المسموح به، بما في ذلك الاستخدام التجاري.",
|
||||
"read_mit": "اقرأ ترخيص معهد ماساتشوستس للتكنولوجيا →",
|
||||
"model_heading": "أوزان النموذج · OpenRAIL-M",
|
||||
"model_desc": "تم إصدار أوزان طراز Supertonic-3 بموجب ترخيص OpenRAIL-M. يقيد هذا الترخيص الاستخدام للأغراض غير الضارة - راجع الترخيص المرتبط للتعرف على المجموعة الكاملة من القيود القائمة على الاستخدام.",
|
||||
"read_openrail": "اقرأ ترخيص OpenRAIL-M →",
|
||||
"footer": "يؤدي النقر فوق \"قبول\" إلى تسجيل قبولك في إعدادات OmniVoice المحلية وتمكين المحرك. يتم تخزين موافقتك على هذا الجهاز فقط - ولا يتم إبلاغ شركة Supertone Inc. أو أي طرف ثالث بأي شيء.",
|
||||
"saving": "جارٍ الحفظ…",
|
||||
"accept": "قبول",
|
||||
"accepted_toast": "تم قبول ترخيص Supertonic-3.",
|
||||
"accept_error": "فشل في تسجيل قبول الترخيص: {{message}}"
|
||||
},
|
||||
"voicePreview": {
|
||||
"title": "معاينة الصوت",
|
||||
"close": "إغلاق المعاينة",
|
||||
"default_text": "مرحبا! هذه معاينة لكيفية صوتي في هذا الصوت.",
|
||||
"default_voice": "الصوت الافتراضي",
|
||||
"clone_profiles": "ملفات تعريف الاستنساخ",
|
||||
"designed_voices": "أصوات مصممة",
|
||||
"presets": "الإعدادات المسبقة",
|
||||
"placeholder": "اكتب شيئًا لسماعه...",
|
||||
"stop": "توقف",
|
||||
"regenerate": "تجديد",
|
||||
"preview": "معاينة",
|
||||
"hint": "8 خطوات · معاينة سريعة"
|
||||
},
|
||||
"header": {
|
||||
"kicker_studio": "استوديو",
|
||||
"kicker_library": "مكتبة",
|
||||
"kicker_preferences": "التفضيلات",
|
||||
"kicker_licensing": "الترخيص",
|
||||
"label_launchpad": "لوحة الإطلاق",
|
||||
"label_clone": "استنساخ الصوت",
|
||||
"label_design": "التصميم الصوتي",
|
||||
"label_dub": "الدبلجة",
|
||||
"label_projects": "أومني درايف",
|
||||
"label_gallery": "معرض",
|
||||
"label_transcriptions": "النسخ",
|
||||
"label_settings": "الإعدادات",
|
||||
"label_enterprise": "رخصة تجارية",
|
||||
"status_ready": "جاهز",
|
||||
"status_loading": "جارٍ التحميل…",
|
||||
"status_idle": "خامل",
|
||||
"memory_management": "إدارة الذاكرة",
|
||||
"flush": "دافق",
|
||||
"loaded_models": "النماذج المحملة",
|
||||
"no_models": "لم يتم تحميل أي نماذج",
|
||||
"unload": "تفريغ",
|
||||
"flush_caches": "مسح المخابئ",
|
||||
"unload_all_flush": "تفريغ كافة + تدفق"
|
||||
},
|
||||
"sidebar": {
|
||||
"tab_drive": "قيادة",
|
||||
"tab_history": "التاريخ",
|
||||
"tab_exports": "الصادرات",
|
||||
"save_project": "احفظ مشروع Dub",
|
||||
"save_new_project": "حفظ كمشروع Dub جديد",
|
||||
"dub_projects": "مشاريع دبلجة",
|
||||
"voice_clones": "استنساخ الصوت",
|
||||
"designed_voices": "أصوات مصممة",
|
||||
"no_dub_projects": "لا توجد مشاريع دبلجة محفوظة",
|
||||
"no_dub_hint": "قم بتحميل مقطع فيديو وانقر فوق \"حفظ\" للاحتفاظ بعملك.",
|
||||
"no_clones": "لا توجد استنساخ صوتي حتى الآن",
|
||||
"no_clones_hint": "قم بتسجيل الصوت أو تحميله، ثم انقر فوق \"حفظ كملف صوتي\".",
|
||||
"no_designs": "لا توجد أصوات مصممة بعد",
|
||||
"no_designs_hint": "توليد صوت وحفظه من التاريخ.",
|
||||
"history_subtitle": "تاريخ الأجيال · مخزنة في SQLite",
|
||||
"no_history": "لا تاريخ الجيل",
|
||||
"no_history_hint": "قم بتجميع الصوت أو دبلجة مقطع فيديو — ستظهر النتائج هنا.",
|
||||
"clear_history": "مسح التاريخ",
|
||||
"clear_confirm": "هل تريد مسح جميع عناصر السجل {{count}}؟ لا يمكن التراجع عن هذا.",
|
||||
"history_cleared": "تم مسح التاريخ",
|
||||
"recent_exports": "الصادرات الأخيرة",
|
||||
"no_exports": "لا توجد مخرجات تم تنزيلها",
|
||||
"no_exports_hint": "قم بتصدير ملف عبر Tauri لمشاهدته متتبعًا هنا.",
|
||||
"show_in_folder": "عرض في المجلد",
|
||||
"open": "مفتوح",
|
||||
"select": "اختر",
|
||||
"try_voice": "حاول",
|
||||
"consistent": "متسقة",
|
||||
"locked": "مغلق",
|
||||
"clone_label": "استنساخ",
|
||||
"design_label": "التصميم",
|
||||
"dub_label": "يصفه",
|
||||
"save_label": "حفظ",
|
||||
"lock_identity": "قفل الهوية الصوتية",
|
||||
"in_folder": "في {{folder}}"
|
||||
},
|
||||
"trimmer": {
|
||||
"title": "تقليم الصوت المرجعي",
|
||||
"decoding": "فك تشفير الصوت...",
|
||||
"meta_length": "الطول {{duration}} · {{sampleRate}} هرتز",
|
||||
"meta_rendering": "تقديم الشكل الموجي {{percent}}%",
|
||||
"keyboard_hint": "التمرير = التكبير · التحول + التمرير = عموم · البديل + السحب = عموم · ⏐ ⟵ ⟶ ⏐ مفاتيح ضبط المقابض",
|
||||
"zoom_in": "تكبير (+)",
|
||||
"zoom_out": "التصغير (-)",
|
||||
"fit_all": "تناسب الجميع (الصفحة الرئيسية)",
|
||||
"fit_selection": "اختيار الملاءمة (النهاية)",
|
||||
"fit_sel_btn": "فيت سيل",
|
||||
"view_range": "عرض {{start}} → {{end}} ({{duration}})",
|
||||
"start_label": "ابدأ",
|
||||
"end_label": "نهاية",
|
||||
"length_label": "الطول",
|
||||
"too_long": ">{{max}}s",
|
||||
"too_short": "قصير جدًا",
|
||||
"length_ok": "حسنا",
|
||||
"loop_preview": "معاينة الحلقة",
|
||||
"pause": "وقفة",
|
||||
"preview_selection": "معاينة التحديد",
|
||||
"play_hint": "مساحة للعب · أدخل للتأكيد · Esc للإلغاء",
|
||||
"cancel": "إلغاء",
|
||||
"use_trimmed": "استخدام قلصت",
|
||||
"decode_failed": "فشل فك التشفير: {{message}}",
|
||||
"playback_failed": "فشل التشغيل: {{message}}",
|
||||
"audio_load_failed": "فشل تحميل الصوت",
|
||||
"unit_seconds": "ق"
|
||||
},
|
||||
"casting": {
|
||||
"title": "صب المتحدث",
|
||||
"auto_assign_title": "تعيين الأصوات تلقائيًا من نسخ السماعات المستخرجة",
|
||||
"auto_cast": "الإرسال التلقائي",
|
||||
"all_cast": "كل الممثلين",
|
||||
"segments_count": "{{count}} المقاطع",
|
||||
"assign_voice": "تخصيص صوت…",
|
||||
"from_video": "🎤 من الفيديو ({{name}})",
|
||||
"no_profiles": "لم يتم حفظ أي ملفات تعريف صوتية حتى الآن.",
|
||||
"preview_voice": "معاينة الصوت"
|
||||
},
|
||||
"checkpoint": {
|
||||
"asr_title": "النصوص جاهزة",
|
||||
"asr_cta": "ترجمة",
|
||||
"asr_hint": "قم بإصلاح أي أخطاء ASR الآن — فالأسلوب المحكم يحفظ محاولات تحويل النص إلى كلام (TTS) لاحقًا.",
|
||||
"translate_title": "الترجمات جاهزة",
|
||||
"translate_cta": "توليد يصفه",
|
||||
"translate_hint": "قم بمسح النص المستهدف. الخطوط الطويلة يتم تعزيز سرعتها؛ يمكنك أيضًا التعديل مباشرةً.",
|
||||
"done_title": "اكتمل الدبلجة",
|
||||
"done_hint": "مراجعة التوقيت ونسب المزامنة. قم بتعديل أي سطر واضغط على \"تم تغيير Regen\" لإعادة جزئية سريعة.",
|
||||
"segment_one": "{{count}} المقطع",
|
||||
"segment_other": "{{count}} المقاطع",
|
||||
"dismiss_title": "تجاهل — لن يظهر مرة أخرى في هذه المرحلة حتى إعادة التحميل"
|
||||
},
|
||||
"compare": {
|
||||
"title": "مقارنة صوت أ/ب",
|
||||
"close": "مقارنة قريبة",
|
||||
"desc": "قارن بين صوتين جنبًا إلى جنب لاتخاذ قرارات الاختيار. يبقى التطبيق تفاعليًا في الخلف.",
|
||||
"test_phrase": "عبارة اختبارية",
|
||||
"voice_a": "صوت أ",
|
||||
"voice_b": "صوت ب",
|
||||
"select_voice": "— اختر الصوت —",
|
||||
"preset_suffix": "(مسبقا)",
|
||||
"no_audio": "لا يوجد صوت بعد",
|
||||
"close_btn": "إغلاق",
|
||||
"comparing": "مقارنة…",
|
||||
"compare_btn": "قارن",
|
||||
"preparing_voice": "تحضير الصوت...",
|
||||
"generating_voice_a": "توليد صوت...",
|
||||
"generating_voice_b": "توليد صوت ب...",
|
||||
"comparison_complete": "المقارنة كاملة!",
|
||||
"play_failed": "فشل التشغيل: {{message}}"
|
||||
},
|
||||
"models": {
|
||||
"hf_token_set_toast": "مجموعة الرموز المميزة HuggingFace - تمكين التنزيلات الأسرع",
|
||||
"hf_token_save_failed": "فشل حفظ الرمز المميز",
|
||||
"install_started": "بدأ التثبيت — التقدم في الصف",
|
||||
"delete_confirm": "هل تريد حذف {{repoId}}؟ ويمكنك إعادة تثبيته لاحقًا.",
|
||||
"delete_confirm_title": "حذف النموذج",
|
||||
"deleted": "تم حذفه {{repoId}}",
|
||||
"reinstall_confirm": "هل تريد إعادة تثبيت {{repoId}}؟ سيؤدي هذا إلى حذف النسخة الحالية وتنزيلها مرة أخرى.",
|
||||
"reinstall_confirm_title": "إعادة تثبيت النموذج",
|
||||
"reinstalling": "إعادة التثبيت",
|
||||
"recommended_installed": "النماذج الموصى بها مثبتة بالفعل.",
|
||||
"started_downloading_one": "بدأ تنزيل نموذج {{count}}",
|
||||
"started_downloading_other": "بدأ تنزيل نماذج {{count}}",
|
||||
"install_failed": "فشل التثبيت: {{message}}",
|
||||
"removing_cached": "جارٍ إزالة المراجعات المخزنة مؤقتًا...",
|
||||
"resolving_metadata": "حل البيانات الوصفية الريبو",
|
||||
"retry_attempt": "إعادة المحاولة {{attempt}} — {{error}}",
|
||||
"connecting_hf": "جارٍ الاتصال بـ HuggingFace…",
|
||||
"resolving_files_one": "جارٍ حل ملف {{count}}...",
|
||||
"resolving_files_other": "جارٍ حل ملفات {{count}}...",
|
||||
"resolving_files_active": "حل {{count}} ملف (ملفات)... · {{file}}",
|
||||
"measuring": "قياس…",
|
||||
"files_progress": "{{done}}/{{total}} الملفات",
|
||||
"install_error": "فشل التثبيت: {{error}}",
|
||||
"view_on_hf": "عرض على HuggingFace",
|
||||
"install_btn": "تثبيت",
|
||||
"reinstall_btn": "أعد التثبيت",
|
||||
"downloading": "جاري التحميل",
|
||||
"deleting": "حذف",
|
||||
"working": "العمل",
|
||||
"installed": "مثبتة",
|
||||
"not_installed": "غير مثبت",
|
||||
"required_tag": "مطلوب",
|
||||
"delete_btn": "حذف",
|
||||
"hf_token_btn": "رمز HF",
|
||||
"hf_set_title": "قم بتعيين رمز HuggingFace للتنزيل بشكل أسرع",
|
||||
"get_token": "الحصول على رمز →",
|
||||
"reco_installed_for": "الحزمة الموصى بها المثبتة لـ {{device}}",
|
||||
"reco_for": "موصى به لـ {{device}}",
|
||||
"starting": "جارٍ البدء…",
|
||||
"required_size": "مطلوب ~{{size}} غيغابايت",
|
||||
"all_size": "الكل ~{{size}} غيغابايت",
|
||||
"req_tag": "مطلوب",
|
||||
"search_placeholder": "نماذج البحث...",
|
||||
"search_label": "نماذج البحث",
|
||||
"no_matches": "لا توجد نماذج تتطابق مع المرشحات الخاصة بك.",
|
||||
"sort_by": "فرز حسب {{column}}",
|
||||
"column_model": "نموذج",
|
||||
"column_role": "الدور",
|
||||
"column_size": "الحجم",
|
||||
"column_status": "الحالة",
|
||||
"ready_badge": "جاهز",
|
||||
"loading_badge": "جارٍ التحميل…",
|
||||
"idle_badge": "خامل",
|
||||
"started_downloading_required_one": "بدأ تنزيل النموذج المطلوب {{count}}",
|
||||
"started_downloading_required_other": "بدأ تنزيل {{count}} النماذج المطلوبة"
|
||||
},
|
||||
"enterprise_faq": {
|
||||
"q_internal_tools": "هل أحتاج إلى ترخيص للأدوات الداخلية؟",
|
||||
"a_internal_tools": "يعد الاستخدام الداخلي من قبل الموظفين والمقاولين لديك غرضًا مسموحًا به بموجب قانون FSL - ولا يلزم الحصول على ترخيص. يلزم الحصول على ترخيص تجاري عندما تجعل OmniVoice متاحًا للآخرين كجزء من منتج أو خدمة منافسة (إعادة البيع، SaaS المستضافة، العلامة البيضاء).",
|
||||
"q_try_before": "هل يمكنني المحاولة قبل الالتزام؟",
|
||||
"a_try_before": "نعم. التطبيق الكامل مجاني للتنزيل والتشغيل محليًا للتقييم بموجب FSL. عندما تكون مستعدًا لمناقشة النشر التجاري، راسلنا عبر البريد الإلكتروني وسنعمل على التفاصيل معًا.",
|
||||
"q_watermark": "ماذا عن العلامة المائية؟",
|
||||
"a_watermark": "يتم تضمين العلامة المائية AudioSeal غير المرئية بشكل افتراضي. يمكن للمرخصين التجاريين تعطيله في الإعدادات → الخصوصية. يتضمن الاستخدام المجاني/الشخصي دائمًا العلامة المائية.",
|
||||
"q_apache": "هل أصبح المصدر Apache 2.0؟",
|
||||
"a_apache": "نعم. يتحول كل إصدار تلقائيًا إلى ترخيص Apache، الإصدار 2.0 في الذكرى السنوية الثانية لنشره. وهذا يعني أن إصدار اليوم هو Apache 2.0 خلال عامين، ولا يلزم اتخاذ أي إجراء من جانبنا - وتضمن FSL ذلك بشكل لا رجعة فيه."
|
||||
},
|
||||
"gallery_extra": {
|
||||
"save_prompt": "أدخل اسمًا لملف التعريف الصوتي هذا:",
|
||||
"saved_as_profile": "تم حفظ الصوت كملف شخصي!",
|
||||
"save_profile_failed": "فشل حفظ الملف الشخصي",
|
||||
"download_failed": "فشل التنزيل: {{message}}",
|
||||
"trim_load_failed": "فشل تحميل الصوت للاقتطاع: {{message}}",
|
||||
"upload_crop_failed": "فشل تحميل الصوت المقصوص: {{message}}"
|
||||
},
|
||||
"dub_workflow": {
|
||||
"preparing_audio": "جارٍ تحضير الصوت…",
|
||||
"preparing_video": "جارٍ تحضير الفيديو…",
|
||||
"extracting_audio_scenes": "استخراج الصوت والمشاهد...",
|
||||
"transcribing_audio": "جارٍ تحويل الصوت إلى نص…",
|
||||
"transcription_complete": "اكتمل النسخ",
|
||||
"upload_cancelled": "تم إلغاء التحميل",
|
||||
"upload_failed": "فشل التحميل: {{message}}",
|
||||
"downloading_video": "جارٍ تنزيل الفيديو…",
|
||||
"ingested": "تم تناولها {{url}}",
|
||||
"ingest_cancelled": "تم إلغاء الاستيعاب",
|
||||
"ingest_failed": "فشل استيعاب عنوان URL: {{message}}",
|
||||
"retry_cancelled": "تم إلغاء إعادة المحاولة",
|
||||
"transcription_failed": "فشل النسخ: {{message}}",
|
||||
"import_srt_no_job": "قم بتحميل مقطع فيديو أو استيعابه أولاً - لا توجد مهمة لإرفاق الترجمة بها.",
|
||||
"imported_cues": "تم استيراد {{count}} إشارة (إشارات) من {{file}}",
|
||||
"skipped_malformed": "تم تخطي {{count}} (مشوه)",
|
||||
"dropped_overlap": "تم إسقاط {{count}} (تداخل)",
|
||||
"clamped_to_duration": "{{count}} مثبت بطول الوسائط",
|
||||
"srt_import_failed": "فشل استيراد SRT",
|
||||
"cleaned_one": "تم تنظيف جزء {{count}}",
|
||||
"cleaned_other": "تنظيف {{count}} شظايا",
|
||||
"segments_clean": "قطاعات نظيفة بالفعل",
|
||||
"cleanup_failed": "فشل التنظيف: {{message}}",
|
||||
"cinematic_no_llm": "تحتاج الجودة السينمائية إلى شهادة LLM - قم بتعيين TRANSLATE_BASE_URL + TRANSLATE_API_KEY (تعمل Ollama محليًا). العودة إلى سريع.",
|
||||
"translate_errors": "{{errorCount}}/{{totalCount}} فشل المقطع (المقاطع): {{firstError}}",
|
||||
"translated_segments": "تمت ترجمة {{count}} مقطع (مقاطع) → {{lang}}",
|
||||
"translated_cinematic_suffix": "(سينمائي)",
|
||||
"translation_failed": "فشلت الترجمة: {{message}}",
|
||||
"regenerating": "جارٍ إعادة إنشاء {{count}} مقطع (مقاطع)...",
|
||||
"generating_dub": "جارٍ إنشاء الدبلجة…",
|
||||
"generating_progress": "جارٍ إنشاء الدبلجة… {{current}}/{{total}}",
|
||||
"generation_aborted": "تم إجهاض الجيل.",
|
||||
"dubbing_aborted": "تم إلغاء الدبلجة",
|
||||
"generation_stream_ended": "انتهى تيار الإنشاء قبل اكتماله",
|
||||
"dub_complete": "اكتمل الدبلجة",
|
||||
"stop_failed": "فشل في التوقف",
|
||||
"save_first": "الرجاء النقر فوق \"تحميل ونسخ\" أولاً حتى تتم معالجة الفيديو على الخادم قبل حفظه.",
|
||||
"project_saved": "تم حفظ المشروع",
|
||||
"project_created": "تم إنشاء المشروع",
|
||||
"save_failed": "فشل الحفظ: {{message}}",
|
||||
"opened_project": "مفتوح: {{name}}",
|
||||
"delete_project_confirm": "هل تريد حذف هذا المشروع؟ لا يمكن التراجع عن هذا.",
|
||||
"project_deleted": "تم حذف المشروع",
|
||||
"delete_history_confirm": "هل تريد حذف عنصر السجل هذا؟",
|
||||
"history_deleted": "تم حذف عنصر السجل",
|
||||
"restored_state": "تمت استعادة حالة الجيل السابق",
|
||||
"upgrading_preview": "جارٍ ترقية مقطع (مقاطع) جودة المعاينة {{count}} إلى الجودة الكاملة..."
|
||||
},
|
||||
"tts_errors": {
|
||||
"enter_text": "الرجاء إدخال النص",
|
||||
"upload_or_select": "قم بتحميل ملف صوتي أو حدد ملفًا صوتيًا",
|
||||
"trim_hint": "الصوت هو {{duration}}s — قم بقصه إلى ≥{{max}}s للحصول على أفضل استنساخ",
|
||||
"timeout": "انتهت مهلة الجيل - ربما لا يزال النموذج قيد التنزيل. تحقق من الإعدادات ← السجلات، ثم حاول مرة أخرى.",
|
||||
"error_prefix": "خطأ: {{message}}",
|
||||
"ignored_unsupported": "تم تجاهل التعليمات غير المدعومة: {{items}}",
|
||||
"ignored_duplicate": "تم التجاهل (تم تعيين الفئة بالفعل): {{items}}"
|
||||
},
|
||||
"sharing": {
|
||||
"title": "المشاركة والوصول عن بعد",
|
||||
"help": "اعرض مثيل OmniVoice الجاري تشغيله على أجهزتك الأخرى دون إعادة تشغيله. الاسترجاع فقط هو الخيار الافتراضي — لا تتم مشاركة أي شيء حتى تقوم بتشغيله هنا.",
|
||||
"local_network": "الشبكة المحلية",
|
||||
"local_help": "شارك على شبكة Wi-Fi / Ethernet الخاصة بك باستخدام رقم التعريف الشخصي (PIN) للوصول لمرة واحدة. تقوم الأجهزة الأخرى بمسح رمز الاستجابة السريعة أو فتح الرابط.",
|
||||
"ports_title": "الموانئ",
|
||||
"ports_help": "يتم تعيينها عبر متغيرات البيئة التي تتم قراءتها عند بدء التشغيل. قم بتغيير الواجهة الخلفية أو منفذ واجهة المستخدم عن طريق تعيين المتغير وإعادة تشغيل OmniVoice.",
|
||||
"backend_port": "منفذ الخلفية",
|
||||
"ui_port": "منفذ واجهة المستخدم",
|
||||
"lan_share_port": "منفذ مشاركة LAN",
|
||||
"port_error": "أدخل منفذًا بين 1024 و65535",
|
||||
"port_saved": "تم حفظ منفذ مشاركة LAN - ينطبق في المرة التالية التي تقوم فيها بتمكين المشاركة",
|
||||
"port_save_failed": "تعذر حفظ المنفذ: {{message}}",
|
||||
"saving": "جارٍ الحفظ…",
|
||||
"ports_note": "يتم تطبيق منافذ الواجهة الخلفية وواجهة المستخدم عند إعادة التشغيل. يتم تطبيق منفذ مشاركة LAN في المرة التالية التي تقوم فيها بتمكين المشاركة.",
|
||||
"tailscale_title": "Tailscale (الوصول عن بعد الخاص)",
|
||||
"tailscale_checking": "جارٍ التحقق من مقياس الذيل…",
|
||||
"tailscale_absent": "لم يتم الكشف عن مقياس الذيل. قم بتثبيته للوصول إلى OmniVoice بشكل آمن من أي مكان على شبكتك الخاصة.",
|
||||
"tailscale_install": "تثبيت Tailscale",
|
||||
"tailscale_running": "مقياس الذيل قيد التشغيل. قم بخدمة OmniVoice عبر الشبكة الخلفية الخاصة بك.",
|
||||
"tailscale_not_logged_in": "تم تثبيت Tailscale ولكن لم يتم تسجيل الدخول. ابدأ وقم بتسجيل الدخول إلى Tailscale أولاً.",
|
||||
"tailscale_enabled": "تم تمكين خدمة Tailscale",
|
||||
"tailscale_enable_failed": "لا يمكن تمكين Tailscale",
|
||||
"tailscale_enable_error": "تعذر تمكين Tailscale: {{message}}",
|
||||
"tailscale_disabled": "تم تعطيل خدمة Tailscale",
|
||||
"tailscale_disable_failed": "لا يمكن تعطيل Tailscale",
|
||||
"tailscale_disable_error": "تعذر تعطيل Tailscale: {{message}}",
|
||||
"tailscale_enabling": "جارٍ التمكين…",
|
||||
"tailscale_enable_btn": "تمكين خدمة Tailscale",
|
||||
"tailscale_disabling": "جارٍ التعطيل...",
|
||||
"tailscale_disable_btn": "إيقاف خدمة Tailscale",
|
||||
"tailscale_copy": "انسخ الرابط",
|
||||
"tailscale_open": "فتح في المتصفح",
|
||||
"copied": "منقول",
|
||||
"tailscale_qr_alt": "رمز الاستجابة السريعة لعنوان URL لـ Tailscale"
|
||||
},
|
||||
"reportBug": {
|
||||
"label": "الإبلاغ عن خطأ",
|
||||
"title": "يفتح صفحة مشكلات GitHub المملوءة مسبقًا في متصفحك. لا يتم إرسال أي شيء حتى تقوم بالنقر فوق إرسال."
|
||||
},
|
||||
"app": {
|
||||
"loading": "جارٍ التحميل…",
|
||||
"trimmed_loaded": "تم تحميل الصوت المقطوع",
|
||||
"toast_exported": "تم التصدير: {{name}}",
|
||||
"toast_export_failed": "فشل التصدير: {{message}}",
|
||||
"toast_open_folder_failed": "لا يمكن فتح المجلد: {{message}}",
|
||||
"toast_saving": "جارٍ حفظ {{name}}...",
|
||||
"toast_saved": "تم الحفظ: {{path}}",
|
||||
"toast_save_error": "خطأ في الحفظ: {{message}}",
|
||||
"toast_processing": "معالجة {{name}}...",
|
||||
"toast_downloaded": "تم التنزيل {{name}}",
|
||||
"toast_download_error": "خطأ في التنزيل: {{message}}",
|
||||
"toast_upload_first": "الرجاء النقر فوق \"تحميل ونسخ\" أولاً حتى تتم معالجة الفيديو على الخادم قبل حفظه.",
|
||||
"toast_save_failed": "فشل الحفظ: {{message}}",
|
||||
"toast_opened": "مفتوح: {{name}}",
|
||||
"toast_project_deleted": "تم حذف المشروع",
|
||||
"toast_restored_state": "تمت استعادة حالة الجيل السابق",
|
||||
"toast_history_deleted": "تم حذف عنصر السجل",
|
||||
"toast_flushed": "متدفق — ذاكرة الوصول العشوائي {{ram}}G · VRAM {{vram}}G{{unloaded}}",
|
||||
"toast_model_unloaded": "· تم تفريغ النموذج",
|
||||
"toast_flush_failed": "فشل التدفق: {{message}}",
|
||||
"toast_project_saved": "تم حفظ المشروع",
|
||||
"toast_project_created": "تم إنشاء المشروع"
|
||||
},
|
||||
"update": {
|
||||
"available": "يتوفّر التحديث {{version}}",
|
||||
"install": "تثبيت وإعادة التشغيل",
|
||||
"install_hint": "نزّل التحديث وأعد التشغيل إلى الإصدار الجديد",
|
||||
"downloading": "جارٍ التحديث… {{pct}}%",
|
||||
"restart": "أعد التشغيل للتحديث",
|
||||
"busy": "أكمل الدبلجة أولاً — ثم ثبّت التحديث.",
|
||||
"whats_new": "ما هو الجديد",
|
||||
"failed": "فشل التحديث",
|
||||
"retry": "أعد المحاولة",
|
||||
"dismiss": "استبعاد"
|
||||
},
|
||||
"archetypes": {
|
||||
"featured": "مميز",
|
||||
@@ -945,5 +1577,25 @@
|
||||
"facet_accent": "لهجة",
|
||||
"facet_lang": "اللغة",
|
||||
"facet_whisper": "الهمس"
|
||||
},
|
||||
"support": {
|
||||
"tab_support": "الدعم",
|
||||
"tab_license": "رخصة تجارية",
|
||||
"toggle_label": "الدعم أو الترخيص التجاري",
|
||||
"other_ways": "طرق أخرى للمساعدة",
|
||||
"star_github": "نجمة على جيثب",
|
||||
"join_discord": "انضم إلى الفتنة"
|
||||
},
|
||||
"updates": {
|
||||
"tab": "التحديثات",
|
||||
"up_to_date": "محدث · v{{version}}",
|
||||
"check_now": "تحقق الآن",
|
||||
"releases": "الإصدارات",
|
||||
"current": "الحالي",
|
||||
"prerelease": "معاينة",
|
||||
"loading": "جارٍ تحميل الإصدارات…",
|
||||
"none": "لم يتم العثور على أي إصدارات",
|
||||
"load_error": "تعذر تحميل الإصدارات (غير متصل؟)",
|
||||
"retry_load": "أعد المحاولة"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,4 @@
|
||||
{
|
||||
"update": {
|
||||
"available": "Update {{version}} verfügbar",
|
||||
"install": "Installieren und neu starten",
|
||||
"install_hint": "Update herunterladen und in die neue Version neu starten",
|
||||
"downloading": "Wird aktualisiert… {{pct}} %",
|
||||
"restart": "Zum Aktualisieren neu starten",
|
||||
"busy": "Beende zuerst deine Synchronisation – dann installiere das Update."
|
||||
},
|
||||
"nav": {
|
||||
"clone": "Klonen",
|
||||
"design": "Designen",
|
||||
@@ -16,7 +8,10 @@
|
||||
"launchpad": "Launchpad",
|
||||
"gallery": "Galerie",
|
||||
"transcripts": "Transkripte",
|
||||
"omnidrive": "OmniDrive"
|
||||
"omnidrive": "OmniDrive",
|
||||
"move_rail_right": "Schiene nach rechts verschieben",
|
||||
"move_rail_left": "Schiene nach links verschieben",
|
||||
"flip_rail": "Schienenseite umdrehen"
|
||||
},
|
||||
"settings": {
|
||||
"general": "Allgemein",
|
||||
@@ -45,7 +40,40 @@
|
||||
"ffmpeg_missing": "Nicht gefunden",
|
||||
"ffmpeg_current": "Aktueller Pfad",
|
||||
"ffmpeg_desc": "Legen Sie einen benutzerdefinierten ffmpeg-Pfad fest, wenn die automatische Erkennung fehlschlägt.",
|
||||
"ffmpeg_saved": "FFmpeg-Pfad festgelegt – Backend zur Anwendung neu starten."
|
||||
"ffmpeg_saved": "FFmpeg-Pfad festgelegt – Backend zur Anwendung neu starten.",
|
||||
"diagnostics_copied": "Diagnose kopiert – in Ihren Problembericht einfügen.",
|
||||
"updater_desktop": "Updater läuft nur in der Desktop-App.",
|
||||
"latest_version": "Sie sind auf der neuesten Version.",
|
||||
"logs_load_failed": "Protokolle konnten nicht geladen werden: {{message}}",
|
||||
"clear_frontend_confirm": "Den In-Memory-Frontend-Protokollpuffer löschen?",
|
||||
"clear_frontend_title": "Protokolle löschen",
|
||||
"frontend_logs_cleared": "Frontend-Protokolle gelöscht",
|
||||
"clear_tauri_confirm": "Die Tauri-seitigen Protokolldateien kürzen? Das Betriebssystem schreibt weiterhin neue Einträge.",
|
||||
"clear_tauri_title": "Tauri-Protokolle löschen",
|
||||
"nothing_to_clear": "Nichts zu löschen – noch keine Tauri-Protokolldatei auf der Festplatte.",
|
||||
"cleared_tauri_one": "{{count}} Tauri-Protokolldatei gelöscht",
|
||||
"cleared_tauri_other": "{{count}} Tauri-Protokolldateien gelöscht",
|
||||
"clear_tauri_failed": "Tauri-Protokolle konnten nicht gelöscht werden: {{message}}",
|
||||
"clear_backend_confirm": "Backend-Laufzeit- und Absturzprotokolle löschen? Dies kann nicht rückgängig gemacht werden.",
|
||||
"clear_backend_title": "Protokolle löschen",
|
||||
"backend_logs_cleared": "Backend-Protokolle gelöscht",
|
||||
"clear_backend_failed": "Protokolle konnten nicht gelöscht werden",
|
||||
"copy_failed": "Kopieren fehlgeschlagen: {{message}}",
|
||||
"update_check_failed": "Update-Prüfung fehlgeschlagen: {{message}}",
|
||||
"save_failed": "Speichern fehlgeschlagen: {{message}}",
|
||||
"clear_failed": "Löschen fehlgeschlagen: {{message}}",
|
||||
"engine_switched": "{{family}} → {{engine}}",
|
||||
"channel_set_failed": "Kanal konnte nicht festgelegt werden: {{message}}",
|
||||
"updater_downloading": "{{version}} wird heruntergeladen…",
|
||||
"updater_installed": "Installiert – Neustart.",
|
||||
"updater_available_title": "Update verfügbar",
|
||||
"updater_available_body": "Version {{version}} ist verfügbar.\n\n{{notes}}\n\nJetzt herunterladen und installieren?",
|
||||
"updater_notes_fallback": "Siehe Versionshinweise auf GitHub.",
|
||||
"shortcut_load_failed": "Verknüpfung konnte nicht geladen werden: {{message}}",
|
||||
"shortcut_set": "Diktatverknüpfung auf {{shortcut}} festgelegt",
|
||||
"shortcut_register_failed": "Konnte nicht registriert werden: {{message}}",
|
||||
"shortcut_reset": "Auf Standard zurücksetzen",
|
||||
"shortcut_reset_failed": "Zurücksetzen fehlgeschlagen: {{message}}"
|
||||
},
|
||||
"bootstrap": {
|
||||
"title": "OmniVoice Studio",
|
||||
@@ -70,7 +98,13 @@
|
||||
"suggest_lang": "Auf Deutsch umstellen?",
|
||||
"select_lang": "Sprache:",
|
||||
"lines_one": "{{count}} Zeile",
|
||||
"lines_other": "{{count}} Zeilen"
|
||||
"lines_other": "{{count}} Zeilen",
|
||||
"region_global": "Global (direkt)",
|
||||
"region_china": "China (Spiegel)",
|
||||
"region_russia": "Russland (Spiegel)",
|
||||
"region_restricted": "Eingeschränkt (Spiegel)",
|
||||
"unknown_error": "Unbekannter Fehler",
|
||||
"retrying": "Erneuter Versuch…"
|
||||
},
|
||||
"stories": {
|
||||
"title": "Story-Editor",
|
||||
@@ -159,7 +193,17 @@
|
||||
"reload": "Neuladen der Benutzeroberfläche erzwingen",
|
||||
"backend": "Backend",
|
||||
"frontend": "Frontend",
|
||||
"tauri": "Tauri"
|
||||
"tauri": "Tauri",
|
||||
"cancelOp": "Vorgang abbrechen",
|
||||
"dismiss": "Entlassen",
|
||||
"dismissStatus": "Status entlassen",
|
||||
"search": "Suchen…",
|
||||
"no_matches": "Keine Übereinstimmungen",
|
||||
"recent_and_popular": "Neu und beliebt",
|
||||
"popular_label": "Beliebt",
|
||||
"showing_of": "Zeigt {{shown}} von {{total}}. Geben Sie Folgendes ein, um zu suchen …",
|
||||
"yes": "Ja",
|
||||
"no": "Nein"
|
||||
},
|
||||
"launchpad": {
|
||||
"greeting": "Hallo",
|
||||
@@ -181,7 +225,16 @@
|
||||
"locked": "GESPERRT",
|
||||
"open": "Offen",
|
||||
"try_it": "Probieren Sie es aus",
|
||||
"audio_only": "Nur Audio"
|
||||
"audio_only": "Nur Audio",
|
||||
"stories_title": "Geschichten",
|
||||
"stories_desc": "Mehrsprachige Hörbücher – Besetzung Ihrer Charaktere, Einfügen eines Skripts, Export nach Kapitel.",
|
||||
"gallery_title": "Sprachgalerie",
|
||||
"gallery_desc": "Durchsuchen Sie vorgefertigte Stimmen nach Akzent, Alter und Stil – ohne Einrichtung.",
|
||||
"transcripts_title": "Transkripte",
|
||||
"transcripts_desc": "Verwandeln Sie Audio oder Video in bearbeitbaren, durchsuchbaren Text – in 646 Sprachen.",
|
||||
"recent_files": "Aktuelle Dateien",
|
||||
"view_all_files": "Alle Dateien anzeigen",
|
||||
"file": "Datei"
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "Prompt",
|
||||
@@ -272,11 +325,6 @@
|
||||
"outputs": "Ausgänge",
|
||||
"crash_log": "Absturzprotokoll",
|
||||
"update_endpoint": "Endpunkt aktualisieren",
|
||||
"update_channel": "Update-Kanal",
|
||||
"channel_stable": "Stabil",
|
||||
"channel_preview": "Vorschau",
|
||||
"channel_set": "Update-Kanal auf {{channel}} gesetzt",
|
||||
"channel_preview_hint": "Die Vorschau folgt dem neuesten main-Build – neuere Funktionen, weniger getestet. Wechselt zurück zu Stabil, wenn eine stabile Version voraus ist.",
|
||||
"yes": "ja",
|
||||
"no": "Nein",
|
||||
"web_preview": "Webvorschau",
|
||||
@@ -285,7 +333,12 @@
|
||||
"copy_diagnostics": "Diagnose kopieren",
|
||||
"github": "OmniVoice auf GitHub",
|
||||
"model_card": "Modellkarte",
|
||||
"commercial_license": "Kommerzielle Lizenz"
|
||||
"commercial_license": "Kommerzielle Lizenz",
|
||||
"update_channel": "Update-Kanal",
|
||||
"channel_stable": "Stabil",
|
||||
"channel_preview": "Vorschau",
|
||||
"channel_set": "Update-Kanal auf {{channel}} gesetzt",
|
||||
"channel_preview_hint": "Die Vorschau folgt dem neuesten main-Build – neuere Funktionen, weniger getestet. Wechselt zurück zu Stabil, wenn eine stabile Version voraus ist."
|
||||
},
|
||||
"privacy": {
|
||||
"desc": "Alles läuft auf <1>dieser Maschine</1>. Ihre Audio-, Video- und Transkripte verlassen niemals Ihren Computer, es sei denn, Sie verwenden ausdrücklich einen Online-Übersetzer (Google, DeepL usw.) oder pushen an HuggingFace.",
|
||||
@@ -341,7 +394,30 @@
|
||||
"unavailable": "nicht verfügbar",
|
||||
"use": "Benutzen",
|
||||
"loading": "Motoren werden geladen…",
|
||||
"refresh": "Aktualisieren"
|
||||
"refresh": "Aktualisieren",
|
||||
"matrixTitle": "Engine-Kompatibilitätsmatrix",
|
||||
"loadFailed": "Fehler beim Laden der Engines: {{message}}",
|
||||
"couldNotLoad": "Engines konnten nicht geladen werden: {{message}}",
|
||||
"retry": "Versuchen Sie es noch einmal",
|
||||
"activeEngine": "Aktiv {{family}}: {{engine}}",
|
||||
"engineCompatLabel": "{{family}} Motorkompatibilität",
|
||||
"active": "aktiv",
|
||||
"whyUnavailable": "Warum nicht verfügbar?",
|
||||
"lastError": "Letzter Fehler: {{error}}",
|
||||
"installedAndReady": "Installiert und fertig",
|
||||
"notInstalled": "Nicht installiert",
|
||||
"available": "Verfügbar",
|
||||
"subprocessTitle": "Läuft in einem eigenen Unterprozess + venv",
|
||||
"inProcessTitle": "Wird im OmniVoice-Python-Prozess ausgeführt",
|
||||
"testEngine": "Testmotor",
|
||||
"testing": "Testen…",
|
||||
"recheck": "Überprüfen Sie es noch einmal",
|
||||
"rechecking": "Nochmals prüfen…",
|
||||
"latencyMs": "{{ms}} ms",
|
||||
"failed": "gescheitert",
|
||||
"acceptLicense": "Lizenz akzeptieren",
|
||||
"noBackends": "Keine Backends registriert.",
|
||||
"switch_failed": "Motorwechsel fehlgeschlagen"
|
||||
},
|
||||
"capture": {
|
||||
"desc": "Globale Hotkeys funktionieren nur in der Desktop-App. Die Web-Benutzeroberfläche verwendet eine In-Page-Verknüpfung <1>Strg+Umschalt+Leertaste</1>, während das Fenster den Fokus hat.",
|
||||
@@ -353,13 +429,55 @@
|
||||
"record_shortcut": "Verknüpfung aufzeichnen",
|
||||
"recording": "Aufnahme…",
|
||||
"save": "Speichern",
|
||||
"reset_default": "Auf Standard zurücksetzen"
|
||||
"reset_default": "Auf Standard zurücksetzen",
|
||||
"listening_label": "Zuhören…",
|
||||
"transcribing_label": "Transkribieren…",
|
||||
"pasted": "Eingefügt",
|
||||
"no_speech": "Keine Sprache erkannt",
|
||||
"mic_denied": "Mikrofonzugriff verweigert",
|
||||
"mic_denied_toast": "Mikrofonzugriff verweigert. {{hint}}",
|
||||
"mic_hint_mac": "macOS: Öffnen Sie Systemeinstellungen → Datenschutz und Sicherheit → Mikrofon und aktivieren Sie OmniVoice.",
|
||||
"mic_hint_windows": "Windows: Öffnen Sie Einstellungen → Datenschutz und Sicherheit → Mikrofon und erlauben Sie OmniVoice.",
|
||||
"mic_hint_linux": "Linux: Überprüfen Sie, ob sich Ihr Benutzer in der Audiogruppe befindet und WebView Zugriff auf das Mikrofon hat.",
|
||||
"transcription_failed": "Transkription fehlgeschlagen: {{message}}"
|
||||
},
|
||||
"logs": {
|
||||
"no_tauri_log": "Noch kein Tauri-Log auf der Festplatte – starten Sie es über den Desktop-Build, um eines zu erstellen",
|
||||
"empty_frontend": "Es wurden noch keine Frontend-Konsoleneinträge erfasst. Interagieren Sie mit der App – jede Konsole.* wird hier angezeigt.",
|
||||
"empty_tauri": "Kein Tauri-Protokoll verfügbar. Läuft nur in der Desktop-Shell.",
|
||||
"empty_backend": "Das Laufzeitprotokoll ist leer. Die Aktivität wird hier angezeigt, wenn das Backend sie protokolliert."
|
||||
"empty_backend": "Das Laufzeitprotokoll ist leer. Die Aktivität wird hier angezeigt, wenn das Backend sie protokolliert.",
|
||||
"title": "Protokolle",
|
||||
"source_backend": "Backend",
|
||||
"source_frontend": "Frontend",
|
||||
"source_tauri": "Tauri",
|
||||
"expand": "Protokolle erweitern",
|
||||
"collapse": "Protokolle ausblenden",
|
||||
"expand_aria": "Erweitern Sie den Protokollbereich",
|
||||
"collapse_aria": "Protokollbereich ausblenden",
|
||||
"drag_resize": "Ziehen Sie, um die Größe zu ändern",
|
||||
"refresh": "Aktualisieren",
|
||||
"refresh_aria": "Protokolle aktualisieren",
|
||||
"copy_visible": "Sichtbares Protokoll kopieren",
|
||||
"copy_visible_aria": "Sichtbares Protokoll kopieren",
|
||||
"clear": "Klar",
|
||||
"clear_aria": "Protokoll löschen",
|
||||
"report_issue": "Problem melden (Diagnose kopieren)",
|
||||
"report_issue_aria": "Problem melden",
|
||||
"close": "Schließen",
|
||||
"close_aria": "Schließen Sie das Protokollfenster",
|
||||
"join_discord": "Treten Sie unserem Discord bei",
|
||||
"join_discord_aria": "Treten Sie unserer Discord-Community bei",
|
||||
"support_project": "Unterstützen Sie dieses Projekt",
|
||||
"support_project_aria": "Unterstützen Sie dieses Projekt",
|
||||
"empty_frontend_short": "Noch keine Frontend-Konsolenausgabe.",
|
||||
"empty_lines": "Keine Linien.",
|
||||
"all_clear": "✅ Alles klar – keine Probleme festgestellt",
|
||||
"log_cleared": "{{source}}-Protokoll gelöscht",
|
||||
"clear_failed": "Löschen fehlgeschlagen: {{message}}",
|
||||
"log_copied": "{{source}}-Protokoll kopiert",
|
||||
"copy_failed": "Kopieren fehlgeschlagen: {{message}}",
|
||||
"report_copied": "Diagnosebericht kopiert – fügen Sie ihn in ein GitHub-Problem ein.",
|
||||
"report_failed": "Bericht fehlgeschlagen: {{message}}"
|
||||
},
|
||||
"voice_profile": {
|
||||
"test_text": "Hallo, dies ist ein Test dieser Stimme.",
|
||||
@@ -527,7 +645,17 @@
|
||||
"install_already": "{{engine}} wurde bereits installiert",
|
||||
"install_ok": "{{engine}} installiert",
|
||||
"install_failed": "Installation fehlgeschlagen: {{message}}",
|
||||
"prep_elapsed": "{{time}} abgelaufen"
|
||||
"prep_elapsed": "{{time}} abgelaufen",
|
||||
"add_language": "Sprache hinzufügen",
|
||||
"search_languages": "Sprachen suchen…",
|
||||
"languages_selected_one": "{{count}} Sprache ausgewählt",
|
||||
"languages_selected_other": "{{count}} Sprachen ausgewählt",
|
||||
"more_to_narrow": "+{{count}} mehr – zum Eingrenzen eingeben",
|
||||
"no_matches": "Keine Übereinstimmungen",
|
||||
"diagnostic_copied": "Diagnose kopiert",
|
||||
"copy_failed": "Das Kopieren ist fehlgeschlagen",
|
||||
"open_docs": "Dokumente öffnen",
|
||||
"copy_diagnostic": "Diagnose kopieren"
|
||||
},
|
||||
"glossary": {
|
||||
"title": "Glossar",
|
||||
@@ -602,7 +730,17 @@
|
||||
"more_actions_title": "Weitere Aktionen",
|
||||
"speaker_pick": "Wählen Sie…",
|
||||
"speaker_title_detected": "Lautsprecher: Wählen Sie einen der erkannten Namen aus oder geben Sie einen benutzerdefinierten Namen ein",
|
||||
"speaker_title_custom": "Sprecher – Geben Sie einen Namen ein (keine Diarisierungsklone erkannt)"
|
||||
"speaker_title_custom": "Sprecher – Geben Sie einen Namen ein (keine Diarisierungsklone erkannt)",
|
||||
"time_edit_title": "Klicken Sie hier, um die Startzeit (m:ss.s) zu bearbeiten. Geben Sie zum Festschreiben die Eingabetaste ein, zum Abbrechen die Esc-Taste.",
|
||||
"fit_fits": "Passt",
|
||||
"fit_fits_title": "Audio mit natürlicher Geschwindigkeit passt in den Steckplatz.",
|
||||
"fit_overflows": "Überläufe +{{seconds}}s",
|
||||
"fit_overflows_title": "Der übersetzte Text war um {{seconds}}s länger als der ursprüngliche Text. Der Ton war stark gekürzt; kürzen Sie den Text oder stellen Sie das Timing auf „Video dehnen“ um.",
|
||||
"fit_stretched": "Video {{ratio}}×",
|
||||
"fit_stretched_title": "Modus „Video dehnen“: Das Video dieses Segments wurde auf {{ratio}}× verlangsamt, um dem natürlichen Dub-Audio zu entsprechen.",
|
||||
"fit_compressed_title": "TTS-Audio macht {{pct}} % des Slots aus – stark komprimiert.",
|
||||
"fit_audio_title": "Audio passt in den Steckplatz.",
|
||||
"fit_ratio_title": "TTS-Audio macht {{pct}} % des Slots aus."
|
||||
},
|
||||
"voice": {
|
||||
"personality": "Persönlichkeit",
|
||||
@@ -697,10 +835,24 @@
|
||||
"status_running": "laufen",
|
||||
"status_done": "erledigt",
|
||||
"status_failed": "gescheitert",
|
||||
"status_cancelled": "abgesagt"
|
||||
"status_cancelled": "abgesagt",
|
||||
"add_to_queue_title": "Fügen Sie Videos zur Warteschlange hinzu",
|
||||
"drop_hint_text": "Legen Sie Videodateien hier ab oder klicken Sie zum Durchsuchen",
|
||||
"drop_formats": "MP4 · MOV · MKV · WEBM",
|
||||
"files_kicker": "DATEIEN ({{count}})",
|
||||
"file_size_mb": "{{size}} MB",
|
||||
"target_languages": "ZIELSPRACHEN",
|
||||
"voice_kicker": "STIMME",
|
||||
"default_option": "Standard",
|
||||
"clone_profiles": "Profile klonen",
|
||||
"presets": "Voreinstellungen",
|
||||
"preserve_bg": "Hintergrundaudio (Musik/FX) beibehalten",
|
||||
"estimate": "{{videos}} Video(s) × {{langs}} Sprache(n) = {{jobs}} Job(s)",
|
||||
"select_files_langs": "Wählen Sie Dateien und Sprachen aus",
|
||||
"add_to_queue": "Zur Warteschlange hinzufügen"
|
||||
},
|
||||
"gallery": {
|
||||
"title": "Galerie",
|
||||
"title": "OmniVoice Galerie",
|
||||
"search_placeholder": "YouTube durchsuchen…",
|
||||
"all_voices": "Alle Stimmen ({{count}})",
|
||||
"no_voices": "Noch keine Stimmen",
|
||||
@@ -715,6 +867,14 @@
|
||||
"youtube_results": "YouTube-Ergebnisse ({{count}})",
|
||||
"clone_profile": "Profil klonen",
|
||||
"crop_audio": "Audio zuschneiden",
|
||||
"cat_disney": "Disney",
|
||||
"cat_anime": "Anime",
|
||||
"cat_marvel": "Marvel/DC",
|
||||
"cat_celebs": "Prominente",
|
||||
"cat_politicians": "Politiker",
|
||||
"cat_news": "Nachrichtensprecher",
|
||||
"cat_gaming": "Spielen",
|
||||
"cat_books": "Bücher/Filme",
|
||||
"subtitle": "Hunderte vorgefertigte Stimmen – wählen Sie eine aus und legen Sie los.",
|
||||
"zone_archetypes": "Archetypen",
|
||||
"zone_imports": "Meine Importe",
|
||||
@@ -819,7 +979,6 @@
|
||||
"badge": "Kommerzielle Lizenz",
|
||||
"hero_title": "Versenden Sie KI-Stimmen in der Produktion",
|
||||
"hero_desc": "OmniVoice Studio ist als Quelle unter der Functional Source License (FSL) verfügbar. Die meisten Benutzer können ohne kommerzielle Vereinbarung evaluieren, Prototypen erstellen und sogar intern bereitstellen. Sie benötigen eine kommerzielle Lizenz nur, wenn Sie ein konkurrierendes Produkt oder eine konkurrierende Dienstleistung entwickeln oder wenn Ihr Anwendungsfall außerhalb der FSL-Grenzen liegt.",
|
||||
"hero_note": "Für den Aufbau eines Konkurrenzprodukts oder -dienstes oder für die Bereitstellung in großem Maßstab (z. B. Bereitstellung einer Pay-per-Use-API) ist eine kommerzielle Lizenz erforderlich. Die Preisstufen folgen in Kürze – nehmen Sie in der Zwischenzeit Kontakt mit uns auf.",
|
||||
"why_title": "Warum Unternehmen sich für OmniVoice entscheiden",
|
||||
"pricing_title": "Preise",
|
||||
"faq_title": "Häufige Fragen",
|
||||
@@ -839,7 +998,8 @@
|
||||
"benefit_source": "Quellverfügbarer Kern",
|
||||
"benefit_source_desc": "Volle Sicht auf den Stapel. Prüfen, forken und anpassen Sie innerhalb der Lizenzbedingungen.",
|
||||
"benefit_lang": "646 Sprachen",
|
||||
"benefit_lang_desc": "Transkribieren, übersetzen und synchronisieren Sie in 646 Sprachen mit menschlicher Qualität."
|
||||
"benefit_lang_desc": "Transkribieren, übersetzen und synchronisieren Sie in 646 Sprachen mit menschlicher Qualität.",
|
||||
"hero_note": "Die interne Nutzung – auch in großem Umfang – ist im Rahmen der FSL kostenlos; Eine kommerzielle Lizenz ist nur erforderlich, um OmniVoice anderen als Konkurrenzprodukt oder -dienst anzubieten (eine gehostete oder Pay-per-Use-API, eine weiterverkaufte oder White-Label-App). Die Preisstufen folgen bald – nehmen Sie in der Zwischenzeit Kontakt mit uns auf."
|
||||
},
|
||||
"exportModal": {
|
||||
"export": "Exportieren",
|
||||
@@ -928,7 +1088,479 @@
|
||||
"dubbing_title": "Erleben Sie Synchronisation in Aktion",
|
||||
"dubbing_sync": "Synchronisierte Wiedergabe",
|
||||
"dubbing_picker": "Versuchen Sie es mit einer anderen Sprache:",
|
||||
"dubbing_cta": "Führen Sie dies in Ihrem eigenen Video aus →"
|
||||
"dubbing_cta": "Führen Sie dies in Ihrem eigenen Video aus →",
|
||||
"dubbing_loading": "Synchronisationsdemo wird geladen…",
|
||||
"dubbing_dismiss": "Synchronisationsdemo schließen",
|
||||
"original_tag": "Original",
|
||||
"dubbed_tag": "synchronisiert",
|
||||
"script_conversational": "Konversation",
|
||||
"script_technical": "Fachvokabular",
|
||||
"script_french": "Nicht-Englisch (Französisch)",
|
||||
"aria_pause": "Pause {{label}}",
|
||||
"aria_hear": "Hören Sie {{label}}",
|
||||
"aria_replay": "Wiederholen Sie {{label}} durch den Transkriptor"
|
||||
},
|
||||
"direction": {
|
||||
"title": "Richtung für Segment #{{id}}",
|
||||
"desc": "Sagen Sie der Pipeline, wie sich diese Zeile anfühlen soll. Einfaches Englisch funktioniert – das System ordnet Ihre Wörter einer stabilen Taxonomie zu (Energie / Emotion / Tempo / Intimität / Formalität) und fädelt die Taxonomie dann durch filmische Übersetzung, TTS und Slot-Fit ein.",
|
||||
"label": "Richtung",
|
||||
"lineHint": "Zeile: „{{text}}“",
|
||||
"placeholder": "z.B. dringend und überrascht / warm, hoffnungsvoll / geflüstert, intim",
|
||||
"previewParse": "Vorschauanalyse",
|
||||
"previewFailed": "Vorschau fehlgeschlagen: {{message}}",
|
||||
"clear": "Klar",
|
||||
"cancel": "Abbrechen",
|
||||
"saveDirection": "Richtung speichern",
|
||||
"ttsInstruct": "TTS weist an:",
|
||||
"nothingParsed": "– (nichts analysiert)",
|
||||
"translateHint": "Hinweis zum Übersetzen:",
|
||||
"rateBias": "Ratenverzerrung:",
|
||||
"speedsUp": "beschleunigt",
|
||||
"slowsDown": "verlangsamt sich",
|
||||
"taxonomyTokens": "Taxonomie-Token"
|
||||
},
|
||||
"errors": {
|
||||
"title": "Dieser Tab ist ins Stocken geraten.",
|
||||
"desc": "Keine Sorge – der Rest der App funktioniert weiterhin. Sie können die Registerkarten wechseln oder es unten noch einmal versuchen.",
|
||||
"tryAgain": "Versuchen Sie es erneut",
|
||||
"openDocs": "Öffnen Sie die Dokumentation zu diesem Fehler"
|
||||
},
|
||||
"keyboard": {
|
||||
"title": "Tastaturkürzel",
|
||||
"footer": "Drücken Sie jederzeit <1>?</1>, um dies zu öffnen.",
|
||||
"or": "oder",
|
||||
"nav": "Navigation",
|
||||
"nav_cheatsheet": "Zeige diesen Spickzettel",
|
||||
"nav_closeModal": "Modal schließen / abbrechen",
|
||||
"nav_save": "Projekt speichern / Trimmen festschreiben",
|
||||
"segmentEditor": "Segmenteditor",
|
||||
"seg_split": "Segment am Cursor teilen",
|
||||
"seg_merge": "Mit dem nächsten Segment zusammenführen",
|
||||
"seg_undo": "Rückgängig machen",
|
||||
"seg_redo": "Wiederholen",
|
||||
"seg_click": "Primäre Aktion",
|
||||
"seg_shiftClick": "Bereichsauswahl",
|
||||
"trimmer": "Audio-Trimmer",
|
||||
"trim_playPause": "Vorschauwiedergabe/Pause",
|
||||
"trim_nudgeStart": "Startgriff anstoßen",
|
||||
"trim_nudgeEnd": "Griff am Ende anstoßen",
|
||||
"trim_fineNudge": "Feiner Anstoß",
|
||||
"trim_coarseNudge": "Grober Anstoß",
|
||||
"trim_zoomIn": "Vergrößern/verkleinern",
|
||||
"trim_fitAll": "Alle anpassen / Auswahl anpassen",
|
||||
"trim_confirm": "Trimmen bestätigen",
|
||||
"dub": "Dub",
|
||||
"dub_generate": "Dub generieren",
|
||||
"dub_sidebar": "Seitenleiste umschalten"
|
||||
},
|
||||
"network": {
|
||||
"sharing_on_title": "Teilen auf – klicken Sie für Details",
|
||||
"share_on_network": "Teilen Sie es in Ihrem Netzwerk",
|
||||
"switching": "Wechseln…",
|
||||
"network": "Netzwerk",
|
||||
"local": "Lokal",
|
||||
"share_confirm_title": "In Ihrem Netzwerk teilen?",
|
||||
"share_confirm_hint": "Andere Geräte in Ihrem WLAN/Ethernet können OmniVoice über die angezeigte Zugangs-PIN erreichen, sobald es eingeschaltet ist.",
|
||||
"enabling": "Aktivieren…",
|
||||
"enable": "Aktivieren",
|
||||
"shared_title": "In Ihrem Netzwerk geteilt",
|
||||
"no_interface": "Keine erreichbare Netzwerkschnittstelle – stellen Sie eine Verbindung zu WLAN/Ethernet her.",
|
||||
"copy_link": "Link kopieren",
|
||||
"open_in_browser": "Im Browser öffnen",
|
||||
"qr_alt": "QR für {{ip}}",
|
||||
"pin": "PIN:",
|
||||
"stop_sharing": "Hören Sie auf zu teilen",
|
||||
"copied": "Kopiert",
|
||||
"enable_error": "Die Freigabe konnte nicht aktiviert werden: {{message}}",
|
||||
"disable_error": "Konnte nicht deaktiviert werden: {{message}}"
|
||||
},
|
||||
"readiness": {
|
||||
"checking_system": "Prüfsystem…",
|
||||
"all_ready": "Alle Systeme bereit",
|
||||
"system_readiness": "Systembereitschaft",
|
||||
"asr_model": "ASR-Modell",
|
||||
"loaded_ready": "Geladen und fertig",
|
||||
"loading_first_run": "Wird geladen… (dies kann beim ersten Durchlauf 1-2 Minuten dauern)",
|
||||
"failed_to_load": "Laden fehlgeschlagen",
|
||||
"not_loaded_yet": "Noch nicht geladen – wird bei der ersten Transkription geladen",
|
||||
"error_check_logs": "Fehler: {{error}}. Überprüfen Sie die Protokolle und versuchen Sie einen Neustart.",
|
||||
"check_logs_restart": "Überprüfen Sie die Protokolle auf Modellladefehler. Versuchen Sie einen Neustart.",
|
||||
"llm_cinematic": "LLM (Film)",
|
||||
"llm_configure": "Konfigurieren Sie TRANSLATE_BASE_URL für die filmische Übersetzungsqualität",
|
||||
"llm_set_env": "Legen Sie die Umgebungsvariablen TRANSLATE_BASE_URL und TRANSLATE_API_KEY fest. Funktioniert mit Ollama, OpenAI, LM Studio usw.",
|
||||
"llm_optional": "Optional – TRANSLATE_BASE_URL für Kinoqualität festlegen"
|
||||
},
|
||||
"license": {
|
||||
"title": "Supertonic-3 – Lizenzannahme",
|
||||
"intro": "Supertonic-3 wird unter zwei unterschiedlichen Lizenzen ausgeliefert. Bitte überprüfen Sie beides, bevor Sie die Engine aktivieren.",
|
||||
"sdk_heading": "SDK-Code · MIT",
|
||||
"sdk_desc": "Das Python-Inferenz-SDK (supertonic) ist MIT-lizenziert. Zulässige Nutzung, auch kommerziell.",
|
||||
"read_mit": "Lesen Sie die MIT-Lizenz →",
|
||||
"model_heading": "Modellgewichte · OpenRAIL-M",
|
||||
"model_desc": "Die Supertonic-3-Modellgewichte werden unter der OpenRAIL-M-Lizenz veröffentlicht. Diese Lizenz beschränkt die Nutzung auf nicht böswillige Zwecke. Den vollständigen Satz nutzungsbasierter Einschränkungen finden Sie in der verlinkten Lizenz.",
|
||||
"read_openrail": "Lesen Sie die OpenRAIL-M-Lizenz →",
|
||||
"footer": "Wenn Sie auf „Akzeptieren“ klicken, wird Ihre Zustimmung in den lokalen Einstellungen von OmniVoice erfasst und die Engine aktiviert. Ihre Zustimmung wird nur auf diesem Gerät gespeichert – keine Meldung an Supertone Inc. oder Dritte.",
|
||||
"saving": "Sparen…",
|
||||
"accept": "Akzeptiere",
|
||||
"accepted_toast": "Supertonic-3-Lizenz akzeptiert.",
|
||||
"accept_error": "Lizenzannahme konnte nicht aufgezeichnet werden: {{message}}"
|
||||
},
|
||||
"voicePreview": {
|
||||
"title": "Sprachvorschau",
|
||||
"close": "Vorschau schließen",
|
||||
"default_text": "Hallo! Dies ist eine Vorschau darauf, wie ich mit dieser Stimme klinge.",
|
||||
"default_voice": "Standardstimme",
|
||||
"clone_profiles": "Profile klonen",
|
||||
"designed_voices": "Gestaltete Stimmen",
|
||||
"presets": "Voreinstellungen",
|
||||
"placeholder": "Geben Sie etwas ein, das Sie hören möchten …",
|
||||
"stop": "Stopp",
|
||||
"regenerate": "Regenerieren",
|
||||
"preview": "Vorschau",
|
||||
"hint": "8 Schritte · schnelle Vorschau"
|
||||
},
|
||||
"header": {
|
||||
"kicker_studio": "Studio",
|
||||
"kicker_library": "Bibliothek",
|
||||
"kicker_preferences": "Präferenzen",
|
||||
"kicker_licensing": "Lizenzierung",
|
||||
"label_launchpad": "Launchpad",
|
||||
"label_clone": "Sprachklon",
|
||||
"label_design": "Sprachdesign",
|
||||
"label_dub": "Synchronisation",
|
||||
"label_projects": "OmniDrive",
|
||||
"label_gallery": "Galerie",
|
||||
"label_transcriptions": "Transkriptionen",
|
||||
"label_settings": "Einstellungen",
|
||||
"label_enterprise": "Kommerzielle Lizenz",
|
||||
"status_ready": "Bereit",
|
||||
"status_loading": "Laden…",
|
||||
"status_idle": "Leerlauf",
|
||||
"memory_management": "Speicherverwaltung",
|
||||
"flush": "Spülen",
|
||||
"loaded_models": "Geladene Modelle",
|
||||
"no_models": "Keine Modelle geladen",
|
||||
"unload": "Entladen",
|
||||
"flush_caches": "Caches leeren",
|
||||
"unload_all_flush": "Alles entladen + spülen"
|
||||
},
|
||||
"sidebar": {
|
||||
"tab_drive": "Fahren",
|
||||
"tab_history": "Geschichte",
|
||||
"tab_exports": "Exporte",
|
||||
"save_project": "Dub-Projekt speichern",
|
||||
"save_new_project": "Als neues Dub-Projekt speichern",
|
||||
"dub_projects": "Dub-Projekte",
|
||||
"voice_clones": "Sprachklone",
|
||||
"designed_voices": "Gestaltete Stimmen",
|
||||
"no_dub_projects": "Keine gespeicherten Dub-Projekte",
|
||||
"no_dub_hint": "Laden Sie ein Video hoch und klicken Sie auf Speichern, um Ihre Arbeit zu behalten.",
|
||||
"no_clones": "Noch keine Sprachklone",
|
||||
"no_clones_hint": "Nehmen Sie Audio auf oder laden Sie es hoch und klicken Sie dann auf Als Sprachprofil speichern.",
|
||||
"no_designs": "Noch keine entworfenen Stimmen",
|
||||
"no_designs_hint": "Erzeugen Sie eine Stimme und speichern Sie sie im Verlauf.",
|
||||
"history_subtitle": "Generationsverlauf · In SQLite gespeichert",
|
||||
"no_history": "Keine Generationsgeschichte",
|
||||
"no_history_hint": "Audio synthetisieren oder ein Video überspielen – die Ergebnisse werden hier angezeigt.",
|
||||
"clear_history": "Verlauf löschen",
|
||||
"clear_confirm": "Alle {{count}}-Verlaufselemente löschen? Dies kann nicht rückgängig gemacht werden.",
|
||||
"history_cleared": "Verlauf gelöscht",
|
||||
"recent_exports": "Aktuelle Exporte",
|
||||
"no_exports": "Keine heruntergeladenen Ausgaben",
|
||||
"no_exports_hint": "Exportieren Sie eine Datei über Tauri, um sie hier zu verfolgen.",
|
||||
"show_in_folder": "Im Ordner anzeigen",
|
||||
"open": "Offen",
|
||||
"select": "Auswählen",
|
||||
"try_voice": "Versuchen Sie es",
|
||||
"consistent": "konsistent",
|
||||
"locked": "Gesperrt",
|
||||
"clone_label": "Klonen",
|
||||
"design_label": "Design",
|
||||
"dub_label": "Dub",
|
||||
"save_label": "Speichern",
|
||||
"lock_identity": "Sprachidentität sperren",
|
||||
"in_folder": "in {{folder}}"
|
||||
},
|
||||
"trimmer": {
|
||||
"title": "Referenzaudio zuschneiden",
|
||||
"decoding": "Audio dekodieren…",
|
||||
"meta_length": "Länge {{duration}} · {{sampleRate}} Hz",
|
||||
"meta_rendering": "Wellenform rendern {{percent}}%",
|
||||
"keyboard_hint": "Scrollen = Zoomen · Umschalt+Scrollen = Schwenken · Alt+Ziehen = Schwenken · ⏐ ⟵ ⟶ ⏐ Tasten passen Ziehpunkte an",
|
||||
"zoom_in": "Vergrößern (+)",
|
||||
"zoom_out": "Verkleinern (-)",
|
||||
"fit_all": "Alle anpassen (Startseite)",
|
||||
"fit_selection": "Auswahl anpassen (Ende)",
|
||||
"fit_sel_btn": "FIT SEL",
|
||||
"view_range": "Ansicht {{start}} → {{end}} ({{duration}})",
|
||||
"start_label": "Starten",
|
||||
"end_label": "Ende",
|
||||
"length_label": "Länge",
|
||||
"too_long": ">{{max}}s",
|
||||
"too_short": "zu kurz",
|
||||
"length_ok": "ok",
|
||||
"loop_preview": "Loop-Vorschau",
|
||||
"pause": "Pause",
|
||||
"preview_selection": "Vorschauauswahl",
|
||||
"play_hint": "Leertaste zum Abspielen · Eingabetaste zum Bestätigen · Esc zum Abbrechen",
|
||||
"cancel": "Abbrechen",
|
||||
"use_trimmed": "Beschnitten verwenden",
|
||||
"decode_failed": "Dekodierung fehlgeschlagen: {{message}}",
|
||||
"playback_failed": "Wiedergabe fehlgeschlagen: {{message}}",
|
||||
"audio_load_failed": "Das Laden der Audiodaten ist fehlgeschlagen",
|
||||
"unit_seconds": "s"
|
||||
},
|
||||
"casting": {
|
||||
"title": "Sprecher-Casting",
|
||||
"auto_assign_title": "Weisen Sie automatisch Stimmen aus extrahierten Sprecherklonen zu",
|
||||
"auto_cast": "Automatische Besetzung",
|
||||
"all_cast": "Alles besetzt",
|
||||
"segments_count": "{{count}} Segmente",
|
||||
"assign_voice": "Stimme zuweisen…",
|
||||
"from_video": "🎤 Aus Video ({{name}})",
|
||||
"no_profiles": "Noch keine Sprachprofile gespeichert.",
|
||||
"preview_voice": "Vorschau der Stimme"
|
||||
},
|
||||
"checkpoint": {
|
||||
"asr_title": "Transkripte bereit",
|
||||
"asr_cta": "Übersetzen",
|
||||
"asr_hint": "Beheben Sie jetzt alle ASR-Fehler – eine straffe Diktion erspart spätere TTS-Versuche.",
|
||||
"translate_title": "Übersetzungen bereit",
|
||||
"translate_cta": "Dub generieren",
|
||||
"translate_hint": "Überfliegen Sie den Zieltext. Überlange Leitungen werden schneller; Sie können es auch direkt bearbeiten.",
|
||||
"done_title": "Dub abgeschlossen",
|
||||
"done_hint": "Überprüfen Sie Timing und Synchronisierungsverhältnisse. Optimieren Sie eine beliebige Zeile und klicken Sie auf „Geändert regenerieren“, um eine teilweise Wiederherstellung durchzuführen.",
|
||||
"segment_one": "{{count}}-Segment",
|
||||
"segment_other": "{{count}} Segmente",
|
||||
"dismiss_title": "Verwerfen – wird für diese Phase erst wieder angezeigt, wenn es neu geladen wird"
|
||||
},
|
||||
"compare": {
|
||||
"title": "A/B-Stimmenvergleich",
|
||||
"close": "Enger Vergleich",
|
||||
"desc": "Vergleichen Sie zwei Stimmen nebeneinander, um Casting-Entscheidungen zu treffen. App bleibt interaktiv hinter.",
|
||||
"test_phrase": "Testphrase",
|
||||
"voice_a": "Stimme A",
|
||||
"voice_b": "Stimme B",
|
||||
"select_voice": "— Stimme auswählen —",
|
||||
"preset_suffix": "(Voreingestellt)",
|
||||
"no_audio": "Noch kein Ton",
|
||||
"close_btn": "Schließen",
|
||||
"comparing": "Vergleichen…",
|
||||
"compare_btn": "Vergleichen",
|
||||
"preparing_voice": "Stimme vorbereiten...",
|
||||
"generating_voice_a": "Erzeugen von Sprach-A...",
|
||||
"generating_voice_b": "Erzeugen von Sprachb...",
|
||||
"comparison_complete": "Vergleich abgeschlossen!",
|
||||
"play_failed": "Wiedergabe fehlgeschlagen: {{message}}"
|
||||
},
|
||||
"models": {
|
||||
"hf_token_set_toast": "HuggingFace-Token-Set – schnellere Downloads ermöglicht",
|
||||
"hf_token_save_failed": "Token konnte nicht gespeichert werden",
|
||||
"install_started": "Installation gestartet – Fortschritt in der Zeile",
|
||||
"delete_confirm": "{{repoId}} löschen? Sie können es später erneut installieren.",
|
||||
"delete_confirm_title": "Modell löschen",
|
||||
"deleted": "Gelöscht {{repoId}}",
|
||||
"reinstall_confirm": "{{repoId}} neu installieren? Dadurch wird die aktuelle Kopie gelöscht und erneut heruntergeladen.",
|
||||
"reinstall_confirm_title": "Modell neu installieren",
|
||||
"reinstalling": "Neuinstallation",
|
||||
"recommended_installed": "Empfohlene Modelle sind bereits installiert.",
|
||||
"started_downloading_one": "Mit dem Herunterladen des Modells {{count}} begonnen",
|
||||
"started_downloading_other": "Mit dem Herunterladen von {{count}}-Modellen begonnen",
|
||||
"install_failed": "Installation fehlgeschlagen: {{message}}",
|
||||
"removing_cached": "Zwischengespeicherte Revisionen werden entfernt…",
|
||||
"resolving_metadata": "Repo-Metadaten auflösen",
|
||||
"retry_attempt": "Wiederholungsversuch {{attempt}} – {{error}}",
|
||||
"connecting_hf": "Verbindung zu HuggingFace herstellen…",
|
||||
"resolving_files_one": "Datei {{count}} wird aufgelöst…",
|
||||
"resolving_files_other": "{{count}}-Dateien werden aufgelöst…",
|
||||
"resolving_files_active": "Auflösen von {{count}} Datei(en)… · {{file}}",
|
||||
"measuring": "messen…",
|
||||
"files_progress": "{{done}}/{{total}} Dateien",
|
||||
"install_error": "Installation fehlgeschlagen: {{error}}",
|
||||
"view_on_hf": "Auf HuggingFace ansehen",
|
||||
"install_btn": "Installieren",
|
||||
"reinstall_btn": "Neu installieren",
|
||||
"downloading": "Herunterladen",
|
||||
"deleting": "löschen",
|
||||
"working": "arbeiten",
|
||||
"installed": "installiert",
|
||||
"not_installed": "nicht installiert",
|
||||
"required_tag": "erforderlich",
|
||||
"delete_btn": "Löschen",
|
||||
"hf_token_btn": "HF-Token",
|
||||
"hf_set_title": "Legen Sie das HuggingFace-Token für schnellere Downloads fest",
|
||||
"get_token": "Token holen →",
|
||||
"reco_installed_for": "Empfohlenes Bundle für {{device}} installiert",
|
||||
"reco_for": "Empfohlen für {{device}}",
|
||||
"starting": "Beginnend mit …",
|
||||
"required_size": "Erforderlich: ~{{size}} GB",
|
||||
"all_size": "Alle ~{{size}} GB",
|
||||
"req_tag": "erf",
|
||||
"search_placeholder": "Modelle suchen…",
|
||||
"search_label": "Modelle suchen",
|
||||
"no_matches": "Kein Modell entspricht Ihren Filtern.",
|
||||
"sort_by": "Sortieren nach {{column}}",
|
||||
"column_model": "Modell",
|
||||
"column_role": "Rolle",
|
||||
"column_size": "Größe",
|
||||
"column_status": "Status",
|
||||
"ready_badge": "Bereit",
|
||||
"loading_badge": "Laden…",
|
||||
"idle_badge": "Leerlauf",
|
||||
"started_downloading_required_one": "Mit dem Herunterladen des erforderlichen Modells {{count}} begonnen",
|
||||
"started_downloading_required_other": "Mit dem Herunterladen der für {{count}} erforderlichen Modelle begonnen"
|
||||
},
|
||||
"enterprise_faq": {
|
||||
"q_internal_tools": "Benötige ich eine Lizenz für interne Tools?",
|
||||
"a_internal_tools": "Die interne Nutzung durch Ihre Mitarbeiter und Auftragnehmer ist ein zulässiger Zweck gemäß FSL – es ist keine Lizenz erforderlich. Eine kommerzielle Lizenz ist erforderlich, wenn Sie OmniVoice anderen als Teil eines konkurrierenden Produkts oder einer konkurrierenden Dienstleistung (Weiterverkauf, gehostetes SaaS, White-Label) zur Verfügung stellen.",
|
||||
"q_try_before": "Kann ich es versuchen, bevor ich mich verpflichte?",
|
||||
"a_try_before": "Ja. Die vollständige App kann kostenlos heruntergeladen und zur Evaluierung im Rahmen der FSL lokal ausgeführt werden. Wenn Sie bereit sind, eine kommerzielle Bereitstellung zu besprechen, senden Sie uns eine E-Mail und wir besprechen gemeinsam die Details.",
|
||||
"q_watermark": "Was ist mit dem Wasserzeichen?",
|
||||
"a_watermark": "Das unsichtbare AudioSeal-Wasserzeichen ist standardmäßig eingebettet. Kommerzielle Lizenznehmer können es unter Einstellungen → Datenschutz deaktivieren. Die kostenlose/persönliche Nutzung beinhaltet immer das Wasserzeichen.",
|
||||
"q_apache": "Wird die Quelle jemals Apache 2.0?",
|
||||
"a_apache": "Ja. Jede Veröffentlichung wird am zweiten Jahrestag ihrer Veröffentlichung automatisch auf die Apache-Lizenz, Version 2.0, konvertiert. Das bedeutet, dass die heutige Veröffentlichung Apache 2.0 in zwei Jahren ist, ohne dass wir etwas unternehmen müssen – die FSL garantiert dies unwiderruflich."
|
||||
},
|
||||
"gallery_extra": {
|
||||
"save_prompt": "Geben Sie einen Namen für dieses Sprachprofil ein:",
|
||||
"saved_as_profile": "Stimme als Profil gespeichert!",
|
||||
"save_profile_failed": "Profil konnte nicht gespeichert werden",
|
||||
"download_failed": "Download fehlgeschlagen: {{message}}",
|
||||
"trim_load_failed": "Audio zum Zuschneiden konnte nicht geladen werden: {{message}}",
|
||||
"upload_crop_failed": "Zugeschnittene Stimme konnte nicht hochgeladen werden: {{message}}"
|
||||
},
|
||||
"dub_workflow": {
|
||||
"preparing_audio": "Audio wird vorbereitet…",
|
||||
"preparing_video": "Video wird vorbereitet…",
|
||||
"extracting_audio_scenes": "Extrahieren von Audio und Szenen…",
|
||||
"transcribing_audio": "Audio transkribieren…",
|
||||
"transcription_complete": "Transkription abgeschlossen",
|
||||
"upload_cancelled": "Upload abgebrochen",
|
||||
"upload_failed": "Hochladen fehlgeschlagen: {{message}}",
|
||||
"downloading_video": "Video wird heruntergeladen…",
|
||||
"ingested": "Aufgenommen {{url}}",
|
||||
"ingest_cancelled": "Aufnahme abgebrochen",
|
||||
"ingest_failed": "URL-Aufnahme fehlgeschlagen: {{message}}",
|
||||
"retry_cancelled": "Wiederholungsversuch abgebrochen",
|
||||
"transcription_failed": "Transkription fehlgeschlagen: {{message}}",
|
||||
"import_srt_no_job": "Laden Sie zuerst ein Video hoch oder nehmen Sie es auf – es gibt keinen Job, dem Sie Untertitel hinzufügen müssen.",
|
||||
"imported_cues": "{{count}} Cue(s) von {{file}} importiert",
|
||||
"skipped_malformed": "{{count}} übersprungen (fehlerhaft)",
|
||||
"dropped_overlap": "{{count}} gelöscht (Überlappung)",
|
||||
"clamped_to_duration": "{{count}} auf Medienlänge geklemmt",
|
||||
"srt_import_failed": "Der SRT-Import ist fehlgeschlagen",
|
||||
"cleaned_one": "Bereinigtes {{count}}-Fragment",
|
||||
"cleaned_other": "Bereinigte {{count}}-Fragmente",
|
||||
"segments_clean": "Segmente bereits sauber",
|
||||
"cleanup_failed": "Bereinigung fehlgeschlagen: {{message}}",
|
||||
"cinematic_no_llm": "Für filmische Qualität ist ein LLM erforderlich – setzen Sie TRANSLATE_BASE_URL + TRANSLATE_API_KEY (Ollama funktioniert lokal). Zurückgreifen auf Fast.",
|
||||
"translate_errors": "{{errorCount}}/{{totalCount}} Segment(e) fehlgeschlagen: {{firstError}}",
|
||||
"translated_segments": "{{count}} Segment(e) → {{lang}} übersetzt",
|
||||
"translated_cinematic_suffix": "(Filmisch)",
|
||||
"translation_failed": "Übersetzung fehlgeschlagen: {{message}}",
|
||||
"regenerating": "{{count}} Segment(e) werden neu generiert…",
|
||||
"generating_dub": "Dub erzeugen…",
|
||||
"generating_progress": "Dub wird erzeugt… {{current}}/{{total}}",
|
||||
"generation_aborted": "Generation abgebrochen.",
|
||||
"dubbing_aborted": "Überspielen abgebrochen",
|
||||
"generation_stream_ended": "Der Generierungsstrom wurde vor Abschluss beendet",
|
||||
"dub_complete": "Dub abgeschlossen",
|
||||
"stop_failed": "Stoppen fehlgeschlagen",
|
||||
"save_first": "Bitte klicken Sie zuerst auf „Hochladen und transkribieren“, damit das Video vor dem Speichern auf dem Server verarbeitet wird.",
|
||||
"project_saved": "Projekt gespeichert",
|
||||
"project_created": "Projekt erstellt",
|
||||
"save_failed": "Speichern fehlgeschlagen: {{message}}",
|
||||
"opened_project": "Geöffnet: {{name}}",
|
||||
"delete_project_confirm": "Dieses Projekt löschen? Dies kann nicht rückgängig gemacht werden.",
|
||||
"project_deleted": "Projekt gelöscht",
|
||||
"delete_history_confirm": "Dieses Verlaufselement löschen?",
|
||||
"history_deleted": "Verlaufselement gelöscht",
|
||||
"restored_state": "Wiederhergestellter Zustand der vorherigen Generation",
|
||||
"upgrading_preview": "Upgrade von {{count}} Segment(en) in Vorschauqualität auf volle Qualität…"
|
||||
},
|
||||
"tts_errors": {
|
||||
"enter_text": "Bitte geben Sie Text ein",
|
||||
"upload_or_select": "Laden Sie ein Audio hoch oder wählen Sie ein Sprachprofil aus",
|
||||
"trim_hint": "Audio ist {{duration}}s – für bestes Klonen auf ≤{{max}}s kürzen",
|
||||
"timeout": "Zeitüberschreitung bei der Generierung – das Modell wird möglicherweise noch heruntergeladen. Überprüfen Sie Einstellungen → Protokolle und versuchen Sie es dann erneut.",
|
||||
"error_prefix": "Fehler: {{message}}",
|
||||
"ignored_unsupported": "Nicht unterstützte Anweisung ignoriert: {{items}}",
|
||||
"ignored_duplicate": "Ignoriert (Kategorie bereits festgelegt): {{items}}"
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Teilen und Fernzugriff",
|
||||
"help": "Stellen Sie diese laufende OmniVoice-Instanz Ihren anderen Computern zur Verfügung, ohne sie neu zu starten. „Nur Loopback“ ist die Standardeinstellung – es wird nichts geteilt, bis Sie es hier aktivieren.",
|
||||
"local_network": "Lokales Netzwerk",
|
||||
"local_help": "Teilen Sie mit einer einmaligen Zugangs-PIN über Ihr WLAN/Ethernet. Andere Geräte scannen den QR-Code oder öffnen den Link.",
|
||||
"ports_title": "Häfen",
|
||||
"ports_help": "Diese werden über Umgebungsvariablen festgelegt, die beim Start gelesen werden. Ändern Sie den Backend- oder UI-Port, indem Sie die Variable festlegen und OmniVoice neu starten.",
|
||||
"backend_port": "Backend-Port",
|
||||
"ui_port": "UI-Port",
|
||||
"lan_share_port": "LAN-Freigabeport",
|
||||
"port_error": "Geben Sie einen Port zwischen 1024 und 65535 ein",
|
||||
"port_saved": "LAN-Freigabe-Port gespeichert – gilt, wenn Sie die Freigabe das nächste Mal aktivieren",
|
||||
"port_save_failed": "Port konnte nicht gespeichert werden: {{message}}",
|
||||
"saving": "Sparen…",
|
||||
"ports_note": "Backend- und UI-Ports werden beim Neustart angewendet. Der LAN-Freigabeport wird angewendet, wenn Sie das nächste Mal die Freigabe aktivieren.",
|
||||
"tailscale_title": "Tailscale (privater Fernzugriff)",
|
||||
"tailscale_checking": "Suche nach Tailscale…",
|
||||
"tailscale_absent": "Tailscale nicht erkannt. Installieren Sie es, um von überall in Ihrem privaten Tailnet sicher auf OmniVoice zuzugreifen.",
|
||||
"tailscale_install": "Tailscale installieren",
|
||||
"tailscale_running": "Tailscale läuft. Bedienen Sie OmniVoice über Ihr privates Tailnet.",
|
||||
"tailscale_not_logged_in": "Tailscale ist installiert, aber nicht angemeldet. Starten Sie zunächst Tailscale und melden Sie sich an.",
|
||||
"tailscale_enabled": "Tailscale-Aufschlag aktiviert",
|
||||
"tailscale_enable_failed": "Tailscale konnte nicht aktiviert werden",
|
||||
"tailscale_enable_error": "Tailscale konnte nicht aktiviert werden: {{message}}",
|
||||
"tailscale_disabled": "Tailscale-Aufschlag deaktiviert",
|
||||
"tailscale_disable_failed": "Tailscale konnte nicht deaktiviert werden",
|
||||
"tailscale_disable_error": "Tailscale konnte nicht deaktiviert werden: {{message}}",
|
||||
"tailscale_enabling": "Aktivieren…",
|
||||
"tailscale_enable_btn": "Tailscale-Serve aktivieren",
|
||||
"tailscale_disabling": "Deaktivieren…",
|
||||
"tailscale_disable_btn": "Stoppen Sie den Tailscale-Aufschlag",
|
||||
"tailscale_copy": "Link kopieren",
|
||||
"tailscale_open": "Im Browser öffnen",
|
||||
"copied": "Kopiert",
|
||||
"tailscale_qr_alt": "QR-Code für die Tailscale-URL"
|
||||
},
|
||||
"reportBug": {
|
||||
"label": "Melden Sie einen Fehler",
|
||||
"title": "Öffnet eine vorab ausgefüllte GitHub-Problemseite in Ihrem Browser. Es wird nichts gesendet, bis Sie auf „Senden“ klicken."
|
||||
},
|
||||
"app": {
|
||||
"loading": "Laden…",
|
||||
"trimmed_loaded": "Zugeschnittenes Audio geladen",
|
||||
"toast_exported": "Exportiert: {{name}}",
|
||||
"toast_export_failed": "Export fehlgeschlagen: {{message}}",
|
||||
"toast_open_folder_failed": "Ordner konnte nicht geöffnet werden: {{message}}",
|
||||
"toast_saving": "Speichern {{name}}...",
|
||||
"toast_saved": "Gespeichert: {{path}}",
|
||||
"toast_save_error": "Speicherfehler: {{message}}",
|
||||
"toast_processing": "Verarbeitung {{name}}...",
|
||||
"toast_downloaded": "Heruntergeladen {{name}}",
|
||||
"toast_download_error": "Download-Fehler: {{message}}",
|
||||
"toast_upload_first": "Bitte klicken Sie zuerst auf „Hochladen und transkribieren“, damit das Video vor dem Speichern auf dem Server verarbeitet wird.",
|
||||
"toast_save_failed": "Speichern fehlgeschlagen: {{message}}",
|
||||
"toast_opened": "Geöffnet: {{name}}",
|
||||
"toast_project_deleted": "Projekt gelöscht",
|
||||
"toast_restored_state": "Wiederhergestellter Zustand der vorherigen Generation",
|
||||
"toast_history_deleted": "Verlaufselement gelöscht",
|
||||
"toast_flushed": "Gespült – RAM {{ram}}G · VRAM {{vram}}G{{unloaded}}",
|
||||
"toast_model_unloaded": "· Modell entladen",
|
||||
"toast_flush_failed": "Spülung fehlgeschlagen: {{message}}",
|
||||
"toast_project_saved": "Projekt gespeichert",
|
||||
"toast_project_created": "Projekt erstellt"
|
||||
},
|
||||
"update": {
|
||||
"available": "Update {{version}} verfügbar",
|
||||
"install": "Installieren und neu starten",
|
||||
"install_hint": "Update herunterladen und in die neue Version neu starten",
|
||||
"downloading": "Wird aktualisiert… {{pct}} %",
|
||||
"restart": "Zum Aktualisieren neu starten",
|
||||
"busy": "Beende zuerst deine Synchronisation – dann installiere das Update.",
|
||||
"whats_new": "Was ist neu?",
|
||||
"failed": "Update fehlgeschlagen",
|
||||
"retry": "Versuchen Sie es noch einmal",
|
||||
"dismiss": "Entlassen"
|
||||
},
|
||||
"archetypes": {
|
||||
"featured": "Hervorgehoben",
|
||||
@@ -946,5 +1578,25 @@
|
||||
"facet_accent": "Akzent",
|
||||
"facet_lang": "Sprache",
|
||||
"facet_whisper": "Flüstern"
|
||||
},
|
||||
"support": {
|
||||
"tab_support": "Unterstützung",
|
||||
"tab_license": "Kommerzielle Lizenz",
|
||||
"toggle_label": "Support oder kommerzielle Lizenz",
|
||||
"other_ways": "Andere Möglichkeiten zu helfen",
|
||||
"star_github": "Stern auf GitHub",
|
||||
"join_discord": "Treten Sie Discord bei"
|
||||
},
|
||||
"updates": {
|
||||
"tab": "Aktualisierungen",
|
||||
"up_to_date": "Auf dem neuesten Stand · v{{version}}",
|
||||
"check_now": "Jetzt prüfen",
|
||||
"releases": "Veröffentlichungen",
|
||||
"current": "aktuell",
|
||||
"prerelease": "Vorschau",
|
||||
"loading": "Veröffentlichungen werden geladen…",
|
||||
"none": "Keine Veröffentlichungen gefunden",
|
||||
"load_error": "Veröffentlichungen konnten nicht geladen werden (offline?)",
|
||||
"retry_load": "Versuchen Sie es noch einmal"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,23 @@
|
||||
"install_hint": "Download the update and restart into the new version",
|
||||
"downloading": "Updating… {{pct}}%",
|
||||
"restart": "Restart to update",
|
||||
"busy": "Finish your dub first — then install the update."
|
||||
"busy": "Finish your dub first — then install the update.",
|
||||
"whats_new": "What's new",
|
||||
"failed": "Update failed",
|
||||
"retry": "Retry",
|
||||
"dismiss": "Dismiss"
|
||||
},
|
||||
"updates": {
|
||||
"tab": "Updates",
|
||||
"up_to_date": "Up to date · v{{version}}",
|
||||
"check_now": "Check now",
|
||||
"releases": "Releases",
|
||||
"current": "current",
|
||||
"prerelease": "preview",
|
||||
"loading": "Loading releases…",
|
||||
"none": "No releases found",
|
||||
"load_error": "Couldn't load releases (offline?)",
|
||||
"retry_load": "Retry"
|
||||
},
|
||||
"stories": {
|
||||
"title": "Stories Editor",
|
||||
@@ -77,25 +93,6 @@
|
||||
"exportDone": "Audiobook downloaded",
|
||||
"exportFailed": "Export failed — check the backend is running."
|
||||
},
|
||||
"common": {
|
||||
"open": "Open",
|
||||
"cancel": "Cancel",
|
||||
"save": "Save",
|
||||
"delete": "Delete",
|
||||
"loading": "Loading…",
|
||||
"error": "Something went wrong",
|
||||
"languages_count": "646 languages",
|
||||
"clear": "Clear",
|
||||
"refresh": "Refresh",
|
||||
"close": "Close",
|
||||
"back": "Back",
|
||||
"play": "Play",
|
||||
"pause": "Pause",
|
||||
"reload": "Force Reload UI",
|
||||
"backend": "Backend",
|
||||
"frontend": "Frontend",
|
||||
"tauri": "Tauri"
|
||||
},
|
||||
"nav": {
|
||||
"launchpad": "Launchpad",
|
||||
"clone": "Clone",
|
||||
@@ -105,7 +102,10 @@
|
||||
"transcripts": "Transcripts",
|
||||
"omnidrive": "OmniDrive",
|
||||
"settings": "Settings",
|
||||
"stories": "Stories"
|
||||
"stories": "Stories",
|
||||
"move_rail_right": "Move rail to the right",
|
||||
"move_rail_left": "Move rail to the left",
|
||||
"flip_rail": "Flip rail side"
|
||||
},
|
||||
"launchpad": {
|
||||
"greeting": "hello there",
|
||||
@@ -117,6 +117,10 @@
|
||||
"design_desc": "Build a new voice from a sentence. Gender, age, accent, mood — your call.",
|
||||
"dub_title": "Video Dubbing",
|
||||
"dub_desc": "Transcribe, translate, re-voice. Keep each speaker, line up the timing, ship it.",
|
||||
"stories_title": "Stories",
|
||||
"stories_desc": "Multi-voice audiobooks — cast your characters, drop in a script, export by chapter.",
|
||||
"gallery_title": "Voice Gallery",
|
||||
"gallery_desc": "Browse ready-made designed voices by accent, age and style — no setup.",
|
||||
"ab_compare": "A/B Compare",
|
||||
"ab_compare_title": "Try two voices side by side",
|
||||
"cloned_voices": "Cloned Voices",
|
||||
@@ -127,7 +131,12 @@
|
||||
"locked": "LOCKED",
|
||||
"open": "Open",
|
||||
"try_it": "Try it",
|
||||
"audio_only": "Audio Only"
|
||||
"audio_only": "Audio Only",
|
||||
"transcripts_title": "Transcripts",
|
||||
"transcripts_desc": "Turn audio or video into editable, searchable text — across 646 languages.",
|
||||
"recent_files": "Recent files",
|
||||
"view_all_files": "View all files",
|
||||
"file": "File"
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "Prompt",
|
||||
@@ -225,7 +234,40 @@
|
||||
"ffmpeg_missing": "Not found",
|
||||
"ffmpeg_current": "Current path",
|
||||
"ffmpeg_desc": "Set a custom ffmpeg path if auto-detection fails.",
|
||||
"ffmpeg_saved": "FFmpeg path set — restart backend to apply."
|
||||
"ffmpeg_saved": "FFmpeg path set — restart backend to apply.",
|
||||
"diagnostics_copied": "Diagnostics copied — paste into your issue report.",
|
||||
"updater_desktop": "Updater only runs in the desktop app.",
|
||||
"latest_version": "You're on the latest version.",
|
||||
"logs_load_failed": "Failed to load logs: {{message}}",
|
||||
"clear_frontend_confirm": "Clear the in-memory frontend log buffer?",
|
||||
"clear_frontend_title": "Clear logs",
|
||||
"frontend_logs_cleared": "Frontend logs cleared",
|
||||
"clear_tauri_confirm": "Truncate the Tauri-side log files? The OS will continue to write new entries.",
|
||||
"clear_tauri_title": "Clear Tauri logs",
|
||||
"nothing_to_clear": "Nothing to clear — no Tauri log file on disk yet.",
|
||||
"cleared_tauri_one": "Cleared {{count}} Tauri log file",
|
||||
"cleared_tauri_other": "Cleared {{count}} Tauri log files",
|
||||
"clear_tauri_failed": "Failed to clear Tauri logs: {{message}}",
|
||||
"clear_backend_confirm": "Clear the backend runtime + crash logs? This cannot be undone.",
|
||||
"clear_backend_title": "Clear logs",
|
||||
"backend_logs_cleared": "Backend logs cleared",
|
||||
"clear_backend_failed": "Failed to clear logs",
|
||||
"copy_failed": "Copy failed: {{message}}",
|
||||
"update_check_failed": "Update check failed: {{message}}",
|
||||
"save_failed": "Save failed: {{message}}",
|
||||
"clear_failed": "Clear failed: {{message}}",
|
||||
"engine_switched": "{{family}} → {{engine}}",
|
||||
"channel_set_failed": "Failed to set channel: {{message}}",
|
||||
"updater_downloading": "Downloading {{version}}…",
|
||||
"updater_installed": "Installed — relaunching.",
|
||||
"updater_available_title": "Update available",
|
||||
"updater_available_body": "Version {{version}} is available.\n\n{{notes}}\n\nDownload and install now?",
|
||||
"updater_notes_fallback": "See release notes on GitHub.",
|
||||
"shortcut_load_failed": "Could not load shortcut: {{message}}",
|
||||
"shortcut_set": "Dictation shortcut set to {{shortcut}}",
|
||||
"shortcut_register_failed": "Couldn't register: {{message}}",
|
||||
"shortcut_reset": "Reset to default",
|
||||
"shortcut_reset_failed": "Reset failed: {{message}}"
|
||||
},
|
||||
"about": {
|
||||
"app": "App",
|
||||
@@ -305,19 +347,6 @@
|
||||
"group_deepl": "DeepL Custom Endpoint",
|
||||
"group_microsoft": "Microsoft Translator Custom Endpoint"
|
||||
},
|
||||
"engines": {
|
||||
"review_on": "Review",
|
||||
"review_off": "Rapid-fire",
|
||||
"banners_on": "Stage banners on",
|
||||
"banners_off": "Stage banners off",
|
||||
"backend": "Backend",
|
||||
"status": "Status",
|
||||
"ready": "ready",
|
||||
"unavailable": "unavailable",
|
||||
"use": "Use",
|
||||
"loading": "Loading engines…",
|
||||
"refresh": "Refresh"
|
||||
},
|
||||
"capture": {
|
||||
"desc": "Global hotkeys only work in the desktop app. The web UI uses an in-page <1>Ctrl+Shift+Space</1> shortcut while the window has focus.",
|
||||
"desc_detail": "The hotkey works system-wide while OmniVoice is running — it focuses the window and starts dictation. Avoid combos already claimed by the OS (on macOS, <1>⌘+Space</1> is Spotlight and <2>⌘+⇧+Space</2> cycles input sources). If registration fails, pick a different combo.",
|
||||
@@ -328,13 +357,17 @@
|
||||
"record_shortcut": "Record shortcut",
|
||||
"recording": "Recording…",
|
||||
"save": "Save",
|
||||
"reset_default": "Reset to default"
|
||||
},
|
||||
"logs": {
|
||||
"no_tauri_log": "No Tauri log on disk yet — launch via the desktop build to produce one",
|
||||
"empty_frontend": "No frontend console entries captured yet. Interact with the app — every console.* will appear here.",
|
||||
"empty_tauri": "No Tauri log available. Runs in the desktop shell only.",
|
||||
"empty_backend": "Runtime log is empty. Activity will appear here as the backend logs it."
|
||||
"reset_default": "Reset to default",
|
||||
"listening_label": "Listening…",
|
||||
"transcribing_label": "Transcribing…",
|
||||
"pasted": "Pasted",
|
||||
"no_speech": "No speech detected",
|
||||
"mic_denied": "Mic access denied",
|
||||
"mic_denied_toast": "Microphone access denied. {{hint}}",
|
||||
"mic_hint_mac": "macOS: open System Settings → Privacy & Security → Microphone and enable OmniVoice.",
|
||||
"mic_hint_windows": "Windows: open Settings → Privacy & security → Microphone and allow OmniVoice.",
|
||||
"mic_hint_linux": "Linux: check that your user is in the audio group and the WebView has mic access.",
|
||||
"transcription_failed": "Transcription failed: {{message}}"
|
||||
},
|
||||
"voice_profile": {
|
||||
"test_text": "Hello — this is a test of this voice.",
|
||||
@@ -463,6 +496,12 @@
|
||||
"clean_up_title": "Merge tiny fragments and adjacent short segments",
|
||||
"popular": "Popular",
|
||||
"all_languages": "All languages",
|
||||
"add_language": "Add language",
|
||||
"search_languages": "Search languages…",
|
||||
"languages_selected_one": "{{count}} language selected",
|
||||
"languages_selected_other": "{{count}} languages selected",
|
||||
"more_to_narrow": "+{{count}} more — type to narrow",
|
||||
"no_matches": "No matches",
|
||||
"engine_label": "Engine",
|
||||
"install_engine": "Install this engine",
|
||||
"installing_engine": "…installing",
|
||||
@@ -502,7 +541,11 @@
|
||||
"install_already": "{{engine}} was already installed",
|
||||
"install_ok": "{{engine}} installed",
|
||||
"install_failed": "Install failed: {{message}}",
|
||||
"prep_elapsed": "{{time}} elapsed"
|
||||
"prep_elapsed": "{{time}} elapsed",
|
||||
"diagnostic_copied": "Diagnostic copied",
|
||||
"copy_failed": "Copy failed",
|
||||
"open_docs": "Open docs",
|
||||
"copy_diagnostic": "Copy diagnostic"
|
||||
},
|
||||
"glossary": {
|
||||
"title": "Glossary",
|
||||
@@ -577,7 +620,17 @@
|
||||
"more_actions_title": "More actions",
|
||||
"speaker_pick": "Pick…",
|
||||
"speaker_title_detected": "Speaker — pick from detected, or type a custom name",
|
||||
"speaker_title_custom": "Speaker — type a name (no diarization clones detected)"
|
||||
"speaker_title_custom": "Speaker — type a name (no diarization clones detected)",
|
||||
"time_edit_title": "Click to edit start time (m:ss.s). Enter to commit, Esc to cancel.",
|
||||
"fit_fits": "Fits",
|
||||
"fit_fits_title": "Natural-rate audio fit inside the slot.",
|
||||
"fit_overflows": "Overflows +{{seconds}}s",
|
||||
"fit_overflows_title": "Translated text was longer than the original slot by {{seconds}}s. The audio was hard-trimmed; shorten the text or switch Timing to \"Stretch Video\".",
|
||||
"fit_stretched": "Video {{ratio}}×",
|
||||
"fit_stretched_title": "Stretch Video mode: this segment's video was slowed to {{ratio}}× to fit the natural dub audio.",
|
||||
"fit_compressed_title": "TTS audio is {{pct}}% of the slot — heavily compressed.",
|
||||
"fit_audio_title": "Audio fit inside the slot.",
|
||||
"fit_ratio_title": "TTS audio is {{pct}}% of the slot."
|
||||
},
|
||||
"voice": {
|
||||
"personality": "Personality",
|
||||
@@ -672,10 +725,24 @@
|
||||
"status_running": "running",
|
||||
"status_done": "done",
|
||||
"status_failed": "failed",
|
||||
"status_cancelled": "cancelled"
|
||||
"status_cancelled": "cancelled",
|
||||
"add_to_queue_title": "Add Videos to Queue",
|
||||
"drop_hint_text": "Drop video files here or click to browse",
|
||||
"drop_formats": "MP4 · MOV · MKV · WEBM",
|
||||
"files_kicker": "FILES ({{count}})",
|
||||
"file_size_mb": "{{size}} MB",
|
||||
"target_languages": "TARGET LANGUAGES",
|
||||
"voice_kicker": "VOICE",
|
||||
"default_option": "Default",
|
||||
"clone_profiles": "Clone Profiles",
|
||||
"presets": "Presets",
|
||||
"preserve_bg": "Preserve background audio (music/FX)",
|
||||
"estimate": "{{videos}} video(s) × {{langs}} lang(s) = {{jobs}} job(s)",
|
||||
"select_files_langs": "Select files and languages",
|
||||
"add_to_queue": "Add to Queue"
|
||||
},
|
||||
"gallery": {
|
||||
"title": "Gallery",
|
||||
"title": "OmniVoice Gallery",
|
||||
"search_placeholder": "Search YouTube…",
|
||||
"all_voices": "All Voices ({{count}})",
|
||||
"no_voices": "No voices yet",
|
||||
@@ -811,7 +878,7 @@
|
||||
"badge": "Commercial License",
|
||||
"hero_title": "Ship AI voices in production",
|
||||
"hero_desc": "OmniVoice Studio is source-available under the Functional Source License (FSL). Most users can evaluate, prototype, and even deploy internally without a commercial agreement. You need a commercial license only if you are building a competing product or service, or if your use case falls outside the FSL’s boundaries.",
|
||||
"hero_note": "Building a competing product or service or deploying at scale (e.g., serving pay-per-use API) requires a commercial license. Pricing tiers coming soon — get in touch in the meantime.",
|
||||
"hero_note": "Internal use — even at scale — is free under the FSL; a commercial license is only required to offer OmniVoice to others as a competing product or service (a hosted or pay-per-use API, a resold or white-labelled app). Pricing tiers are coming soon — get in touch in the meantime.",
|
||||
"why_title": "Why businesses choose OmniVoice",
|
||||
"pricing_title": "Pricing",
|
||||
"faq_title": "Common questions",
|
||||
@@ -920,7 +987,17 @@
|
||||
"dubbing_title": "See dubbing in action",
|
||||
"dubbing_sync": "Synced playback",
|
||||
"dubbing_picker": "Try another language:",
|
||||
"dubbing_cta": "Run this on your own video →"
|
||||
"dubbing_cta": "Run this on your own video →",
|
||||
"dubbing_loading": "Loading dubbing demo…",
|
||||
"dubbing_dismiss": "Dismiss dubbing demo",
|
||||
"original_tag": "original",
|
||||
"dubbed_tag": "dubbed",
|
||||
"script_conversational": "Conversational",
|
||||
"script_technical": "Technical vocabulary",
|
||||
"script_french": "Non-English (French)",
|
||||
"aria_pause": "Pause {{label}}",
|
||||
"aria_hear": "Hear {{label}}",
|
||||
"aria_replay": "Replay {{label}} through transcriber"
|
||||
},
|
||||
"bootstrap": {
|
||||
"title": "OmniVoice Studio",
|
||||
@@ -944,6 +1021,573 @@
|
||||
"waiting_output": "Waiting for output…",
|
||||
"auto_detect": "Auto-detect",
|
||||
"suggest_lang": "Switch to {{lang}}?",
|
||||
"select_lang": "Language:"
|
||||
"select_lang": "Language:",
|
||||
"region_global": "Global (direct)",
|
||||
"region_china": "China (mirror)",
|
||||
"region_russia": "Russia (mirror)",
|
||||
"region_restricted": "Restricted (mirror)",
|
||||
"unknown_error": "Unknown error",
|
||||
"retrying": "Retrying…"
|
||||
},
|
||||
"direction": {
|
||||
"title": "Direction for segment #{{id}}",
|
||||
"desc": "Tell the pipeline how this line should feel. Plain English works — the system maps your words onto a stable taxonomy (energy / emotion / pace / intimacy / formality), then threads the taxonomy through Cinematic translation, TTS, and slot-fit.",
|
||||
"label": "Direction",
|
||||
"lineHint": "Line: \"{{text}}\"",
|
||||
"placeholder": "e.g. urgent and surprised / warm, hopeful / whispered, intimate",
|
||||
"previewParse": "Preview parse",
|
||||
"previewFailed": "Preview failed: {{message}}",
|
||||
"clear": "Clear",
|
||||
"cancel": "Cancel",
|
||||
"saveDirection": "Save direction",
|
||||
"ttsInstruct": "TTS instruct:",
|
||||
"nothingParsed": "— (nothing parsed)",
|
||||
"translateHint": "Translate hint:",
|
||||
"rateBias": "Rate bias:",
|
||||
"speedsUp": "speeds up",
|
||||
"slowsDown": "slows down",
|
||||
"taxonomyTokens": "taxonomy tokens"
|
||||
},
|
||||
"engines": {
|
||||
"review_on": "Review",
|
||||
"review_off": "Rapid-fire",
|
||||
"banners_on": "Stage banners on",
|
||||
"banners_off": "Stage banners off",
|
||||
"backend": "Backend",
|
||||
"status": "Status",
|
||||
"ready": "ready",
|
||||
"unavailable": "Unavailable",
|
||||
"use": "Use",
|
||||
"loading": "Loading engines…",
|
||||
"refresh": "Refresh",
|
||||
"matrixTitle": "Engine Compatibility Matrix",
|
||||
"loadFailed": "Failed to load engines: {{message}}",
|
||||
"couldNotLoad": "Could not load engines: {{message}}",
|
||||
"retry": "Retry",
|
||||
"activeEngine": "Active {{family}}: {{engine}}",
|
||||
"engineCompatLabel": "{{family}} engine compatibility",
|
||||
"active": "active",
|
||||
"whyUnavailable": "Why unavailable?",
|
||||
"lastError": "Last error: {{error}}",
|
||||
"installedAndReady": "Installed and ready",
|
||||
"notInstalled": "Not installed",
|
||||
"available": "Available",
|
||||
"subprocessTitle": "Runs in its own subprocess + venv",
|
||||
"inProcessTitle": "Runs in the OmniVoice Python process",
|
||||
"testEngine": "Test engine",
|
||||
"testing": "Testing…",
|
||||
"recheck": "Re-check",
|
||||
"rechecking": "Re-checking…",
|
||||
"latencyMs": "{{ms}} ms",
|
||||
"failed": "failed",
|
||||
"acceptLicense": "Accept license",
|
||||
"noBackends": "No backends registered.",
|
||||
"switch_failed": "Failed to switch engine"
|
||||
},
|
||||
"errors": {
|
||||
"title": "This tab hit a snag.",
|
||||
"desc": "Don't worry — the rest of the app still works. You can switch tabs, or try again below.",
|
||||
"tryAgain": "Try again",
|
||||
"openDocs": "Open docs for this error"
|
||||
},
|
||||
"common": {
|
||||
"open": "Open",
|
||||
"cancel": "Cancel",
|
||||
"save": "Save",
|
||||
"delete": "Delete",
|
||||
"loading": "Loading…",
|
||||
"error": "Something went wrong",
|
||||
"languages_count": "646 languages",
|
||||
"clear": "Clear",
|
||||
"refresh": "Refresh",
|
||||
"close": "Close",
|
||||
"back": "Back",
|
||||
"play": "Play",
|
||||
"pause": "Pause",
|
||||
"reload": "Force Reload UI",
|
||||
"backend": "Backend",
|
||||
"frontend": "Frontend",
|
||||
"tauri": "Tauri",
|
||||
"cancelOp": "Cancel operation",
|
||||
"dismiss": "Dismiss",
|
||||
"dismissStatus": "Dismiss status",
|
||||
"search": "Search…",
|
||||
"no_matches": "No matches",
|
||||
"recent_and_popular": "Recent & Popular",
|
||||
"popular_label": "Popular",
|
||||
"showing_of": "Showing {{shown}} of {{total}}. Type to search…",
|
||||
"yes": "Yes",
|
||||
"no": "No"
|
||||
},
|
||||
"keyboard": {
|
||||
"title": "Keyboard shortcuts",
|
||||
"footer": "Press <1>?</1> any time to open this.",
|
||||
"or": "or",
|
||||
"nav": "Navigation",
|
||||
"nav_cheatsheet": "Show this cheatsheet",
|
||||
"nav_closeModal": "Close modal / cancel",
|
||||
"nav_save": "Save project / commit trim",
|
||||
"segmentEditor": "Segment editor",
|
||||
"seg_split": "Split segment at cursor",
|
||||
"seg_merge": "Merge with next segment",
|
||||
"seg_undo": "Undo",
|
||||
"seg_redo": "Redo",
|
||||
"seg_click": "Primary action",
|
||||
"seg_shiftClick": "Range select",
|
||||
"trimmer": "Audio trimmer",
|
||||
"trim_playPause": "Preview play / pause",
|
||||
"trim_nudgeStart": "Nudge start handle",
|
||||
"trim_nudgeEnd": "Nudge end handle",
|
||||
"trim_fineNudge": "Fine nudge",
|
||||
"trim_coarseNudge": "Coarse nudge",
|
||||
"trim_zoomIn": "Zoom in / out",
|
||||
"trim_fitAll": "Fit all / Fit selection",
|
||||
"trim_confirm": "Confirm trim",
|
||||
"dub": "Dub",
|
||||
"dub_generate": "Generate dub",
|
||||
"dub_sidebar": "Toggle sidebar"
|
||||
},
|
||||
"network": {
|
||||
"sharing_on_title": "Sharing on — click for details",
|
||||
"share_on_network": "Share on your network",
|
||||
"switching": "Switching…",
|
||||
"network": "Network",
|
||||
"local": "Local",
|
||||
"share_confirm_title": "Share on your network?",
|
||||
"share_confirm_hint": "Other devices on your Wi-Fi/Ethernet will be able to reach OmniVoice using the access PIN shown once it's on.",
|
||||
"enabling": "Enabling…",
|
||||
"enable": "Enable",
|
||||
"shared_title": "Shared on your network",
|
||||
"no_interface": "No reachable network interface — connect to Wi-Fi/Ethernet.",
|
||||
"copy_link": "Copy link",
|
||||
"open_in_browser": "Open in browser",
|
||||
"qr_alt": "QR for {{ip}}",
|
||||
"pin": "PIN:",
|
||||
"stop_sharing": "Stop sharing",
|
||||
"copied": "Copied",
|
||||
"enable_error": "Could not enable sharing: {{message}}",
|
||||
"disable_error": "Could not disable: {{message}}"
|
||||
},
|
||||
"readiness": {
|
||||
"checking_system": "Checking system…",
|
||||
"all_ready": "All systems ready",
|
||||
"system_readiness": "System Readiness",
|
||||
"asr_model": "ASR Model",
|
||||
"loaded_ready": "Loaded and ready",
|
||||
"loading_first_run": "Loading… (this may take 1-2 minutes on first run)",
|
||||
"failed_to_load": "Failed to load",
|
||||
"not_loaded_yet": "Not loaded yet — will load on first transcription",
|
||||
"error_check_logs": "Error: {{error}}. Check logs and try restarting.",
|
||||
"check_logs_restart": "Check logs for model loading errors. Try restarting.",
|
||||
"llm_cinematic": "LLM (Cinematic)",
|
||||
"llm_configure": "Configure TRANSLATE_BASE_URL for Cinematic translation quality",
|
||||
"llm_set_env": "Set TRANSLATE_BASE_URL and TRANSLATE_API_KEY environment variables. Works with Ollama, OpenAI, LM Studio, etc.",
|
||||
"llm_optional": "Optional — set TRANSLATE_BASE_URL for Cinematic quality"
|
||||
},
|
||||
"license": {
|
||||
"title": "Supertonic-3 — License Acceptance",
|
||||
"intro": "Supertonic-3 ships under two distinct licenses. Please review both before enabling the engine.",
|
||||
"sdk_heading": "SDK Code · MIT",
|
||||
"sdk_desc": "The Python inference SDK (supertonic) is MIT-licensed. Permissive use, including commercial.",
|
||||
"read_mit": "Read the MIT license →",
|
||||
"model_heading": "Model Weights · OpenRAIL-M",
|
||||
"model_desc": "The Supertonic-3 model weights are released under the OpenRAIL-M license. This license restricts use to non-malicious purposes — see the linked license for the full set of use-based restrictions.",
|
||||
"read_openrail": "Read the OpenRAIL-M license →",
|
||||
"footer": "Clicking Accept records your acceptance in OmniVoice's local settings and enables the engine. Your acceptance is stored on this machine only — nothing is reported to Supertone Inc. or any third party.",
|
||||
"saving": "Saving…",
|
||||
"accept": "Accept",
|
||||
"accepted_toast": "Supertonic-3 license accepted.",
|
||||
"accept_error": "Failed to record license acceptance: {{message}}"
|
||||
},
|
||||
"voicePreview": {
|
||||
"title": "Voice Preview",
|
||||
"close": "Close preview",
|
||||
"default_text": "Hello! This is a preview of how I sound in this voice.",
|
||||
"default_voice": "Default voice",
|
||||
"clone_profiles": "Clone Profiles",
|
||||
"designed_voices": "Designed Voices",
|
||||
"presets": "Presets",
|
||||
"placeholder": "Type something to hear…",
|
||||
"stop": "Stop",
|
||||
"regenerate": "Regenerate",
|
||||
"preview": "Preview",
|
||||
"hint": "8 steps · fast preview"
|
||||
},
|
||||
"header": {
|
||||
"kicker_studio": "Studio",
|
||||
"kicker_library": "Library",
|
||||
"kicker_preferences": "Preferences",
|
||||
"kicker_licensing": "Licensing",
|
||||
"label_launchpad": "Launchpad",
|
||||
"label_clone": "Voice Clone",
|
||||
"label_design": "Voice Design",
|
||||
"label_dub": "Dubbing",
|
||||
"label_projects": "OmniDrive",
|
||||
"label_gallery": "Gallery",
|
||||
"label_transcriptions": "Transcriptions",
|
||||
"label_settings": "Settings",
|
||||
"label_enterprise": "Commercial License",
|
||||
"status_ready": "Ready",
|
||||
"status_loading": "Loading…",
|
||||
"status_idle": "Idle",
|
||||
"memory_management": "Memory management",
|
||||
"flush": "Flush",
|
||||
"loaded_models": "Loaded Models",
|
||||
"no_models": "No models loaded",
|
||||
"unload": "Unload",
|
||||
"flush_caches": "Flush caches",
|
||||
"unload_all_flush": "Unload all + flush"
|
||||
},
|
||||
"sidebar": {
|
||||
"tab_drive": "Drive",
|
||||
"tab_history": "History",
|
||||
"tab_exports": "Exports",
|
||||
"save_project": "Save Dub Project",
|
||||
"save_new_project": "Save as New Dub Project",
|
||||
"dub_projects": "Dub Projects",
|
||||
"voice_clones": "Voice Clones",
|
||||
"designed_voices": "Designed Voices",
|
||||
"no_dub_projects": "No saved dub projects",
|
||||
"no_dub_hint": "Upload a video and click Save to keep your work.",
|
||||
"no_clones": "No voice clones yet",
|
||||
"no_clones_hint": "Record or upload audio, then click Save as Voice Profile.",
|
||||
"no_designs": "No designed voices yet",
|
||||
"no_designs_hint": "Generate a voice and save it from History.",
|
||||
"history_subtitle": "Generation history · Stored in SQLite",
|
||||
"no_history": "No generation history",
|
||||
"no_history_hint": "Synthesize audio or dub a video — results will appear here.",
|
||||
"clear_history": "Clear History",
|
||||
"clear_confirm": "Clear all {{count}} history items? This cannot be undone.",
|
||||
"history_cleared": "History cleared",
|
||||
"recent_exports": "Recent Exports",
|
||||
"no_exports": "No downloaded outputs",
|
||||
"no_exports_hint": "Export a file via Tauri to see it tracked here.",
|
||||
"show_in_folder": "Show in folder",
|
||||
"open": "Open",
|
||||
"select": "Select",
|
||||
"try_voice": "Try",
|
||||
"consistent": "consistent",
|
||||
"locked": "Locked",
|
||||
"clone_label": "Clone",
|
||||
"design_label": "Design",
|
||||
"dub_label": "Dub",
|
||||
"save_label": "Save",
|
||||
"lock_identity": "Lock voice identity",
|
||||
"in_folder": "in {{folder}}"
|
||||
},
|
||||
"logs": {
|
||||
"no_tauri_log": "No Tauri log on disk yet — launch via the desktop build to produce one",
|
||||
"empty_frontend": "No frontend console entries captured yet. Interact with the app — every console.* will appear here.",
|
||||
"empty_tauri": "No Tauri log available. Runs in the desktop shell only.",
|
||||
"empty_backend": "Runtime log is empty. Activity will appear here as the backend logs it.",
|
||||
"title": "Logs",
|
||||
"source_backend": "Backend",
|
||||
"source_frontend": "Frontend",
|
||||
"source_tauri": "Tauri",
|
||||
"expand": "Expand logs",
|
||||
"collapse": "Collapse logs",
|
||||
"expand_aria": "Expand logs panel",
|
||||
"collapse_aria": "Collapse logs panel",
|
||||
"drag_resize": "Drag to resize",
|
||||
"refresh": "Refresh",
|
||||
"refresh_aria": "Refresh logs",
|
||||
"copy_visible": "Copy visible log",
|
||||
"copy_visible_aria": "Copy visible log",
|
||||
"clear": "Clear",
|
||||
"clear_aria": "Clear log",
|
||||
"report_issue": "Report issue (copy diagnostic)",
|
||||
"report_issue_aria": "Report issue",
|
||||
"close": "Close",
|
||||
"close_aria": "Close logs panel",
|
||||
"join_discord": "Join our Discord",
|
||||
"join_discord_aria": "Join our Discord community",
|
||||
"support_project": "Support this project",
|
||||
"support_project_aria": "Support this project",
|
||||
"empty_frontend_short": "No frontend console output yet.",
|
||||
"empty_lines": "No lines.",
|
||||
"all_clear": "✅ All clear — no issues detected",
|
||||
"log_cleared": "{{source}} log cleared",
|
||||
"clear_failed": "Clear failed: {{message}}",
|
||||
"log_copied": "Copied {{source}} log",
|
||||
"copy_failed": "Copy failed: {{message}}",
|
||||
"report_copied": "Diagnostic report copied — paste it into a GitHub issue.",
|
||||
"report_failed": "Report failed: {{message}}"
|
||||
},
|
||||
"trimmer": {
|
||||
"title": "Trim reference audio",
|
||||
"decoding": "Decoding audio…",
|
||||
"meta_length": "Length {{duration}} · {{sampleRate}} Hz",
|
||||
"meta_rendering": "rendering waveform {{percent}}%",
|
||||
"keyboard_hint": "scroll = zoom · shift+scroll = pan · alt+drag = pan · ⏐ ⟵ ⟶ ⏐ keys adjust handles",
|
||||
"zoom_in": "Zoom in (+)",
|
||||
"zoom_out": "Zoom out (-)",
|
||||
"fit_all": "Fit all (Home)",
|
||||
"fit_selection": "Fit selection (End)",
|
||||
"fit_sel_btn": "FIT SEL",
|
||||
"view_range": "View {{start}} → {{end}} ({{duration}})",
|
||||
"start_label": "Start",
|
||||
"end_label": "End",
|
||||
"length_label": "Length",
|
||||
"too_long": ">{{max}}s",
|
||||
"too_short": "too short",
|
||||
"length_ok": "ok",
|
||||
"loop_preview": "Loop preview",
|
||||
"pause": "Pause",
|
||||
"preview_selection": "Preview selection",
|
||||
"play_hint": "Space to play · Enter to confirm · Esc to cancel",
|
||||
"cancel": "Cancel",
|
||||
"use_trimmed": "Use trimmed",
|
||||
"decode_failed": "Decode failed: {{message}}",
|
||||
"playback_failed": "Playback failed: {{message}}",
|
||||
"audio_load_failed": "Audio load failed",
|
||||
"unit_seconds": "s"
|
||||
},
|
||||
"casting": {
|
||||
"title": "Speaker Casting",
|
||||
"auto_assign_title": "Auto-assign voices from extracted speaker clones",
|
||||
"auto_cast": "Auto-cast",
|
||||
"all_cast": "All cast",
|
||||
"segments_count": "{{count}} segments",
|
||||
"assign_voice": "Assign voice…",
|
||||
"from_video": "🎤 From video ({{name}})",
|
||||
"no_profiles": "No voice profiles saved yet.",
|
||||
"preview_voice": "Preview voice"
|
||||
},
|
||||
"checkpoint": {
|
||||
"asr_title": "Transcripts ready",
|
||||
"asr_cta": "Translate",
|
||||
"asr_hint": "Fix any ASR errors now — tight diction saves TTS attempts later.",
|
||||
"translate_title": "Translations ready",
|
||||
"translate_cta": "Generate dub",
|
||||
"translate_hint": "Skim the target text. Over-length lines get speed-boosted; you can also edit directly.",
|
||||
"done_title": "Dub complete",
|
||||
"done_hint": "Review timing and sync ratios. Tweak any line and hit \"Regen changed\" for a fast partial redo.",
|
||||
"segment_one": "{{count}} segment",
|
||||
"segment_other": "{{count}} segments",
|
||||
"dismiss_title": "Dismiss — won't reappear for this stage until reload"
|
||||
},
|
||||
"compare": {
|
||||
"title": "A/B Voice Comparison",
|
||||
"close": "Close comparison",
|
||||
"desc": "Compare two voices side by side to make casting decisions. App stays interactive behind.",
|
||||
"test_phrase": "Test phrase",
|
||||
"voice_a": "Voice A",
|
||||
"voice_b": "Voice B",
|
||||
"select_voice": "— Select voice —",
|
||||
"preset_suffix": "(Preset)",
|
||||
"no_audio": "No audio yet",
|
||||
"close_btn": "Close",
|
||||
"comparing": "Comparing…",
|
||||
"compare_btn": "Compare",
|
||||
"preparing_voice": "Preparing voice...",
|
||||
"generating_voice_a": "Generating Voice A...",
|
||||
"generating_voice_b": "Generating Voice B...",
|
||||
"comparison_complete": "Comparison complete!",
|
||||
"play_failed": "Play failed: {{message}}"
|
||||
},
|
||||
"models": {
|
||||
"hf_token_set_toast": "HuggingFace token set — faster downloads enabled",
|
||||
"hf_token_save_failed": "Failed to save token",
|
||||
"install_started": "Install started — progress in the row",
|
||||
"delete_confirm": "Delete {{repoId}}? You can reinstall it later.",
|
||||
"delete_confirm_title": "Delete model",
|
||||
"deleted": "Deleted {{repoId}}",
|
||||
"reinstall_confirm": "Reinstall {{repoId}}? This will delete the current copy and download again.",
|
||||
"reinstall_confirm_title": "Reinstall model",
|
||||
"reinstalling": "Reinstalling",
|
||||
"recommended_installed": "Recommended models are already installed.",
|
||||
"started_downloading_one": "Started downloading {{count}} model",
|
||||
"started_downloading_other": "Started downloading {{count}} models",
|
||||
"install_failed": "Install failed: {{message}}",
|
||||
"removing_cached": "Removing cached revisions…",
|
||||
"resolving_metadata": "Resolving repo metadata",
|
||||
"retry_attempt": "Retry attempt {{attempt}} — {{error}}",
|
||||
"connecting_hf": "Connecting to HuggingFace…",
|
||||
"resolving_files_one": "Resolving {{count}} file…",
|
||||
"resolving_files_other": "Resolving {{count}} files…",
|
||||
"resolving_files_active": "Resolving {{count}} file(s)… · {{file}}",
|
||||
"measuring": "measuring…",
|
||||
"files_progress": "{{done}}/{{total}} files",
|
||||
"install_error": "Install failed: {{error}}",
|
||||
"view_on_hf": "View on HuggingFace",
|
||||
"install_btn": "Install",
|
||||
"reinstall_btn": "Reinstall",
|
||||
"downloading": "downloading",
|
||||
"deleting": "deleting",
|
||||
"working": "working",
|
||||
"installed": "installed",
|
||||
"not_installed": "not installed",
|
||||
"required_tag": "required",
|
||||
"delete_btn": "Delete",
|
||||
"hf_token_btn": "HF Token",
|
||||
"hf_set_title": "Set HuggingFace token for faster downloads",
|
||||
"get_token": "Get token →",
|
||||
"reco_installed_for": "Recommended bundle installed for {{device}}",
|
||||
"reco_for": "Recommended for {{device}}",
|
||||
"starting": "Starting…",
|
||||
"required_size": "Required ~{{size}} GB",
|
||||
"all_size": "All ~{{size}} GB",
|
||||
"req_tag": "req",
|
||||
"search_placeholder": "Search models…",
|
||||
"search_label": "Search models",
|
||||
"no_matches": "No models match your filters.",
|
||||
"sort_by": "Sort by {{column}}",
|
||||
"column_model": "Model",
|
||||
"column_role": "Role",
|
||||
"column_size": "Size",
|
||||
"column_status": "Status",
|
||||
"ready_badge": "Ready",
|
||||
"loading_badge": "Loading…",
|
||||
"idle_badge": "Idle",
|
||||
"started_downloading_required_one": "Started downloading {{count}} required model",
|
||||
"started_downloading_required_other": "Started downloading {{count}} required models"
|
||||
},
|
||||
"enterprise_faq": {
|
||||
"q_internal_tools": "Do I need a license for internal tools?",
|
||||
"a_internal_tools": "Internal use by your employees and contractors is a Permitted Purpose under the FSL — no license required. A commercial license is needed when you make OmniVoice available to others as part of a competing product or service (resale, hosted SaaS, white-label).",
|
||||
"q_try_before": "Can I try before committing?",
|
||||
"a_try_before": "Yes. The full app is free to download and run locally for evaluation under the FSL. When you're ready to discuss a commercial deployment, email us and we'll work through the details together.",
|
||||
"q_watermark": "What about the watermark?",
|
||||
"a_watermark": "The invisible AudioSeal watermark is embedded by default. Commercial licensees can disable it in Settings → Privacy. Free/personal use always includes the watermark.",
|
||||
"q_apache": "Does the source ever become Apache 2.0?",
|
||||
"a_apache": "Yes. Each release converts automatically to the Apache License, Version 2.0 on the second anniversary of its publication. That means today's release is Apache 2.0 in two years, no action required from us — the FSL guarantees it irrevocably."
|
||||
},
|
||||
"gallery_extra": {
|
||||
"save_prompt": "Enter a name for this voice profile:",
|
||||
"saved_as_profile": "Voice saved as profile!",
|
||||
"save_profile_failed": "Failed to save profile",
|
||||
"download_failed": "Download failed: {{message}}",
|
||||
"trim_load_failed": "Failed to load audio for trimming: {{message}}",
|
||||
"upload_crop_failed": "Failed to upload cropped voice: {{message}}"
|
||||
},
|
||||
"dub_workflow": {
|
||||
"preparing_audio": "Preparing audio…",
|
||||
"preparing_video": "Preparing video…",
|
||||
"extracting_audio_scenes": "Extracting audio & scenes…",
|
||||
"transcribing_audio": "Transcribing audio…",
|
||||
"transcription_complete": "Transcription complete",
|
||||
"upload_cancelled": "Upload cancelled",
|
||||
"upload_failed": "Upload failed: {{message}}",
|
||||
"downloading_video": "Downloading video…",
|
||||
"ingested": "Ingested {{url}}",
|
||||
"ingest_cancelled": "Ingest cancelled",
|
||||
"ingest_failed": "URL ingest failed: {{message}}",
|
||||
"retry_cancelled": "Retry cancelled",
|
||||
"transcription_failed": "Transcription failed: {{message}}",
|
||||
"import_srt_no_job": "Upload or ingest a video first — there is no job to attach subtitles to.",
|
||||
"imported_cues": "Imported {{count}} cue(s) from {{file}}",
|
||||
"skipped_malformed": "{{count}} skipped (malformed)",
|
||||
"dropped_overlap": "{{count}} dropped (overlap)",
|
||||
"clamped_to_duration": "{{count}} clamped to media length",
|
||||
"srt_import_failed": "SRT import failed",
|
||||
"cleaned_one": "Cleaned {{count}} fragment",
|
||||
"cleaned_other": "Cleaned {{count}} fragments",
|
||||
"segments_clean": "Segments already clean",
|
||||
"cleanup_failed": "Clean up failed: {{message}}",
|
||||
"cinematic_no_llm": "Cinematic quality needs an LLM — set TRANSLATE_BASE_URL + TRANSLATE_API_KEY (Ollama works locally). Falling back to Fast.",
|
||||
"translate_errors": "{{errorCount}}/{{totalCount}} segment(s) failed: {{firstError}}",
|
||||
"translated_segments": "Translated {{count}} segment(s) → {{lang}}",
|
||||
"translated_cinematic_suffix": " (Cinematic)",
|
||||
"translation_failed": "Translation failed: {{message}}",
|
||||
"regenerating": "Regenerating {{count}} segment(s)…",
|
||||
"generating_dub": "Generating dub…",
|
||||
"generating_progress": "Generating dub… {{current}}/{{total}}",
|
||||
"generation_aborted": "Generation aborted.",
|
||||
"dubbing_aborted": "Dubbing aborted",
|
||||
"generation_stream_ended": "Generation stream ended before completion",
|
||||
"dub_complete": "Dub complete",
|
||||
"stop_failed": "Failed to stop",
|
||||
"save_first": "Please click 'Upload & Transcribe' first so the video is processed on the server before saving.",
|
||||
"project_saved": "Project saved",
|
||||
"project_created": "Project created",
|
||||
"save_failed": "Save failed: {{message}}",
|
||||
"opened_project": "Opened: {{name}}",
|
||||
"delete_project_confirm": "Delete this project? This cannot be undone.",
|
||||
"project_deleted": "Project deleted",
|
||||
"delete_history_confirm": "Delete this history item?",
|
||||
"history_deleted": "History item deleted",
|
||||
"restored_state": "Restored previous generation state",
|
||||
"upgrading_preview": "Upgrading {{count}} preview-quality segment(s) to full quality…"
|
||||
},
|
||||
"tts_errors": {
|
||||
"enter_text": "Please enter text",
|
||||
"upload_or_select": "Upload an audio or select a voice profile",
|
||||
"trim_hint": "Audio is {{duration}}s — trim to ≤{{max}}s for best cloning",
|
||||
"timeout": "Generation timed out — the model may still be downloading. Check Settings → Logs, then try again.",
|
||||
"error_prefix": "Error: {{message}}",
|
||||
"ignored_unsupported": "Ignored unsupported instruct: {{items}}",
|
||||
"ignored_duplicate": "Ignored (category already set): {{items}}"
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Sharing & Remote Access",
|
||||
"help": "Expose this running OmniVoice instance to your other machines without restarting it. Loopback-only is the default — nothing is shared until you turn it on here.",
|
||||
"local_network": "Local network",
|
||||
"local_help": "Share on your Wi-Fi / Ethernet with a one-time access PIN. Other devices scan the QR code or open the link.",
|
||||
"ports_title": "Ports",
|
||||
"ports_help": "These are set via environment variables read at startup. Change the backend or UI port by setting the variable and restarting OmniVoice.",
|
||||
"backend_port": "Backend port",
|
||||
"ui_port": "UI port",
|
||||
"lan_share_port": "LAN-share port",
|
||||
"port_error": "Enter a port between 1024 and 65535",
|
||||
"port_saved": "LAN-share port saved — applies next time you enable sharing",
|
||||
"port_save_failed": "Could not save port: {{message}}",
|
||||
"saving": "Saving…",
|
||||
"ports_note": "Backend and UI ports apply on restart. The LAN-share port applies next time you enable sharing.",
|
||||
"tailscale_title": "Tailscale (private remote access)",
|
||||
"tailscale_checking": "Checking for Tailscale…",
|
||||
"tailscale_absent": "Tailscale not detected. Install it to reach OmniVoice securely from anywhere on your private tailnet.",
|
||||
"tailscale_install": "Install Tailscale",
|
||||
"tailscale_running": "Tailscale is running. Serve OmniVoice over your private tailnet.",
|
||||
"tailscale_not_logged_in": "Tailscale is installed but not logged in. Start and sign in to Tailscale first.",
|
||||
"tailscale_enabled": "Tailscale serve enabled",
|
||||
"tailscale_enable_failed": "Could not enable Tailscale",
|
||||
"tailscale_enable_error": "Could not enable Tailscale: {{message}}",
|
||||
"tailscale_disabled": "Tailscale serve disabled",
|
||||
"tailscale_disable_failed": "Could not disable Tailscale",
|
||||
"tailscale_disable_error": "Could not disable Tailscale: {{message}}",
|
||||
"tailscale_enabling": "Enabling…",
|
||||
"tailscale_enable_btn": "Enable Tailscale serve",
|
||||
"tailscale_disabling": "Disabling…",
|
||||
"tailscale_disable_btn": "Stop Tailscale serve",
|
||||
"tailscale_copy": "Copy link",
|
||||
"tailscale_open": "Open in browser",
|
||||
"copied": "Copied",
|
||||
"tailscale_qr_alt": "QR code for the Tailscale URL"
|
||||
},
|
||||
"reportBug": {
|
||||
"label": "Report a bug",
|
||||
"title": "Opens a prefilled GitHub Issues page in your browser. Nothing is sent until you click Submit."
|
||||
},
|
||||
"app": {
|
||||
"loading": "Loading…",
|
||||
"trimmed_loaded": "Trimmed audio loaded",
|
||||
"toast_exported": "Exported: {{name}}",
|
||||
"toast_export_failed": "Export failed: {{message}}",
|
||||
"toast_open_folder_failed": "Could not open folder: {{message}}",
|
||||
"toast_saving": "Saving {{name}}...",
|
||||
"toast_saved": "Saved: {{path}}",
|
||||
"toast_save_error": "Save error: {{message}}",
|
||||
"toast_processing": "Processing {{name}}...",
|
||||
"toast_downloaded": "Downloaded {{name}}",
|
||||
"toast_download_error": "Download error: {{message}}",
|
||||
"toast_upload_first": "Please click 'Upload & Transcribe' first so the video is processed on the server before saving.",
|
||||
"toast_save_failed": "Save failed: {{message}}",
|
||||
"toast_opened": "Opened: {{name}}",
|
||||
"toast_project_deleted": "Project deleted",
|
||||
"toast_restored_state": "Restored previous generation state",
|
||||
"toast_history_deleted": "History item deleted",
|
||||
"toast_flushed": "Flushed — RAM {{ram}}G · VRAM {{vram}}G{{unloaded}}",
|
||||
"toast_model_unloaded": " · model unloaded",
|
||||
"toast_flush_failed": "Flush failed: {{message}}",
|
||||
"toast_project_saved": "Project saved",
|
||||
"toast_project_created": "Project created"
|
||||
},
|
||||
"support": {
|
||||
"tab_support": "Support",
|
||||
"tab_license": "Commercial License",
|
||||
"toggle_label": "Support or commercial license",
|
||||
"other_ways": "Other ways to help",
|
||||
"star_github": "Star on GitHub",
|
||||
"join_discord": "Join Discord"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
{
|
||||
"update": {
|
||||
"available": "Actualización {{version}} disponible",
|
||||
"install": "Instalar y reiniciar",
|
||||
"install_hint": "Descarga la actualización y reinicia en la nueva versión",
|
||||
"downloading": "Actualizando… {{pct}}%",
|
||||
"restart": "Reiniciar para actualizar",
|
||||
"busy": "Termina tu doblaje primero — luego instala la actualización."
|
||||
},
|
||||
"nav": {
|
||||
"clone": "Clonar",
|
||||
"design": "Diseñar",
|
||||
@@ -16,7 +8,10 @@
|
||||
"launchpad": "Plataforma de lanzamiento",
|
||||
"gallery": "Galería",
|
||||
"transcripts": "Transcripciones",
|
||||
"omnidrive": "OmniDrive"
|
||||
"omnidrive": "OmniDrive",
|
||||
"move_rail_right": "Mover el carril hacia la derecha",
|
||||
"move_rail_left": "Mover el carril hacia la izquierda",
|
||||
"flip_rail": "Lado del riel abatible"
|
||||
},
|
||||
"settings": {
|
||||
"general": "General",
|
||||
@@ -45,7 +40,40 @@
|
||||
"ffmpeg_missing": "No encontrado",
|
||||
"ffmpeg_current": "Camino actual",
|
||||
"ffmpeg_desc": "Establezca una ruta ffmpeg personalizada si falla la detección automática.",
|
||||
"ffmpeg_saved": "Conjunto de ruta FFmpeg: reinicie el backend para aplicar."
|
||||
"ffmpeg_saved": "Conjunto de ruta FFmpeg: reinicie el backend para aplicar.",
|
||||
"diagnostics_copied": "Diagnóstico copiado: péguelo en su informe de problemas.",
|
||||
"updater_desktop": "El actualizador solo se ejecuta en la aplicación de escritorio.",
|
||||
"latest_version": "Estás en la última versión.",
|
||||
"logs_load_failed": "No se pudieron cargar los registros: {{message}}",
|
||||
"clear_frontend_confirm": "¿Borrar el búfer de registro de interfaz en memoria?",
|
||||
"clear_frontend_title": "Borrar registros",
|
||||
"frontend_logs_cleared": "Registros de interfaz borrados",
|
||||
"clear_tauri_confirm": "¿Truncar los archivos de registro del lado Tauri? El sistema operativo seguirá escribiendo nuevas entradas.",
|
||||
"clear_tauri_title": "Borrar registros de Tauri",
|
||||
"nothing_to_clear": "No hay nada que borrar: todavía no hay ningún archivo de registro de Tauri en el disco.",
|
||||
"cleared_tauri_one": "Se borró el archivo de registro {{count}} Tauri",
|
||||
"cleared_tauri_other": "Se borraron {{count}} archivos de registro de Tauri",
|
||||
"clear_tauri_failed": "No se pudieron borrar los registros de Tauri: {{message}}",
|
||||
"clear_backend_confirm": "¿Borrar el tiempo de ejecución del backend y los registros de fallos? Esto no se puede deshacer.",
|
||||
"clear_backend_title": "Borrar registros",
|
||||
"backend_logs_cleared": "Registros de backend borrados",
|
||||
"clear_backend_failed": "No se pudieron borrar los registros",
|
||||
"copy_failed": "Copia fallida: {{message}}",
|
||||
"update_check_failed": "Error en la verificación de actualización: {{message}}",
|
||||
"save_failed": "Error al guardar: {{message}}",
|
||||
"clear_failed": "Borrado fallido: {{message}}",
|
||||
"engine_switched": "{{family}} → {{engine}}",
|
||||
"channel_set_failed": "No se pudo configurar el canal: {{message}}",
|
||||
"updater_downloading": "Descargando {{version}}…",
|
||||
"updater_installed": "Instalado - relanzando.",
|
||||
"updater_available_title": "Actualización disponible",
|
||||
"updater_available_body": "La versión {{version}} está disponible.\n\n{{notes}}\n\n¿Descargar e instalar ahora?",
|
||||
"updater_notes_fallback": "Consulte las notas de la versión en GitHub.",
|
||||
"shortcut_load_failed": "No se pudo cargar el acceso directo: {{message}}",
|
||||
"shortcut_set": "Atajo de dictado configurado en {{shortcut}}",
|
||||
"shortcut_register_failed": "No se pudo registrar: {{message}}",
|
||||
"shortcut_reset": "Restablecer los valores predeterminados",
|
||||
"shortcut_reset_failed": "Error al restablecer: {{message}}"
|
||||
},
|
||||
"bootstrap": {
|
||||
"title": "OmniVoice Studio",
|
||||
@@ -70,7 +98,13 @@
|
||||
"suggest_lang": "¿Cambiar al Español?",
|
||||
"select_lang": "Idioma:",
|
||||
"lines_one": "{{count}} línea",
|
||||
"lines_other": "{{count}} líneas"
|
||||
"lines_other": "{{count}} líneas",
|
||||
"region_global": "Global (directo)",
|
||||
"region_china": "China (espejo)",
|
||||
"region_russia": "Rusia (espejo)",
|
||||
"region_restricted": "Restringido (espejo)",
|
||||
"unknown_error": "Error desconocido",
|
||||
"retrying": "Reintentando…"
|
||||
},
|
||||
"stories": {
|
||||
"title": "Editor de historias",
|
||||
@@ -159,7 +193,17 @@
|
||||
"reload": "Forzar recarga de interfaz de usuario",
|
||||
"backend": "backend",
|
||||
"frontend": "Interfaz",
|
||||
"tauri": "Tauro"
|
||||
"tauri": "Tauro",
|
||||
"cancelOp": "Cancelar operación",
|
||||
"dismiss": "Descartar",
|
||||
"dismissStatus": "Descartar estado",
|
||||
"search": "Buscar…",
|
||||
"no_matches": "No hay coincidencias",
|
||||
"recent_and_popular": "Reciente y popular",
|
||||
"popular_label": "populares",
|
||||
"showing_of": "Mostrando {{shown}} de {{total}}. Escribe para buscar…",
|
||||
"yes": "si",
|
||||
"no": "No"
|
||||
},
|
||||
"launchpad": {
|
||||
"greeting": "hola",
|
||||
@@ -181,7 +225,16 @@
|
||||
"locked": "BLOQUEADO",
|
||||
"open": "Abierto",
|
||||
"try_it": "Pruébalo",
|
||||
"audio_only": "Sólo audio"
|
||||
"audio_only": "Sólo audio",
|
||||
"stories_title": "Historias",
|
||||
"stories_desc": "Audiolibros con varias voces: elige tus personajes, introduce un guión y exporta por capítulo.",
|
||||
"gallery_title": "Galería de voz",
|
||||
"gallery_desc": "Explore voces diseñadas ya preparadas por acento, edad y estilo, sin configuración.",
|
||||
"transcripts_title": "Transcripciones",
|
||||
"transcripts_desc": "Convierta audio o vídeo en texto editable y con capacidad de búsqueda, en 646 idiomas.",
|
||||
"recent_files": "Archivos recientes",
|
||||
"view_all_files": "Ver todos los archivos",
|
||||
"file": "Archivo"
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "rápido",
|
||||
@@ -272,11 +325,6 @@
|
||||
"outputs": "Salidas",
|
||||
"crash_log": "Registro de fallos",
|
||||
"update_endpoint": "Actualizar punto final",
|
||||
"update_channel": "Canal de actualización",
|
||||
"channel_stable": "Estable",
|
||||
"channel_preview": "Vista previa",
|
||||
"channel_set": "Canal de actualización configurado en {{channel}}",
|
||||
"channel_preview_hint": "La vista previa sigue la última compilación de main: funciones más nuevas, menos probadas. Vuelve a estable si hay una versión estable más reciente.",
|
||||
"yes": "si",
|
||||
"no": "no",
|
||||
"web_preview": "vista previa web",
|
||||
@@ -285,7 +333,12 @@
|
||||
"copy_diagnostics": "Copiar diagnóstico",
|
||||
"github": "OmniVoice en GitHub",
|
||||
"model_card": "tarjeta modelo",
|
||||
"commercial_license": "Licencia Comercial"
|
||||
"commercial_license": "Licencia Comercial",
|
||||
"update_channel": "Canal de actualización",
|
||||
"channel_stable": "Estable",
|
||||
"channel_preview": "Vista previa",
|
||||
"channel_set": "Canal de actualización configurado en {{channel}}",
|
||||
"channel_preview_hint": "La vista previa sigue la última compilación de main: funciones más nuevas, menos probadas. Vuelve a estable si hay una versión estable más reciente."
|
||||
},
|
||||
"privacy": {
|
||||
"desc": "Todo se ejecuta en <1>esta máquina</1>. Su audio, video y transcripciones nunca salen de su computadora a menos que utilice explícitamente un traductor en línea (Google, DeepL, etc.) o acceda a HuggingFace.",
|
||||
@@ -341,7 +394,30 @@
|
||||
"unavailable": "no disponible",
|
||||
"use": "uso",
|
||||
"loading": "Cargando motores…",
|
||||
"refresh": "Actualizar"
|
||||
"refresh": "Actualizar",
|
||||
"matrixTitle": "Matriz de compatibilidad de motores",
|
||||
"loadFailed": "No se pudieron cargar los motores: {{message}}",
|
||||
"couldNotLoad": "No se pudieron cargar los motores: {{message}}",
|
||||
"retry": "Reintentar",
|
||||
"activeEngine": "Activo {{family}}: {{engine}}",
|
||||
"engineCompatLabel": "{{family}} compatibilidad del motor",
|
||||
"active": "activo",
|
||||
"whyUnavailable": "¿Por qué no está disponible?",
|
||||
"lastError": "Último error: {{error}}",
|
||||
"installedAndReady": "Instalado y listo",
|
||||
"notInstalled": "No instalado",
|
||||
"available": "Disponible",
|
||||
"subprocessTitle": "Se ejecuta en su propio subproceso + venv",
|
||||
"inProcessTitle": "Se ejecuta en el proceso OmniVoice Python",
|
||||
"testEngine": "motor de prueba",
|
||||
"testing": "Probando…",
|
||||
"recheck": "Vuelva a comprobar",
|
||||
"rechecking": "Volviendo a comprobar…",
|
||||
"latencyMs": "{{ms}} ms",
|
||||
"failed": "falló",
|
||||
"acceptLicense": "Aceptar licencia",
|
||||
"noBackends": "No hay backends registrados.",
|
||||
"switch_failed": "No se pudo cambiar de motor"
|
||||
},
|
||||
"capture": {
|
||||
"desc": "Las teclas de acceso rápido globales solo funcionan en la aplicación de escritorio. La interfaz de usuario web utiliza un acceso directo <1>Ctrl+Shift+Espacio</1> en la página mientras la ventana tiene el foco.",
|
||||
@@ -353,13 +429,55 @@
|
||||
"record_shortcut": "Grabar acceso directo",
|
||||
"recording": "Grabando…",
|
||||
"save": "Guardar",
|
||||
"reset_default": "Restablecer los valores predeterminados"
|
||||
"reset_default": "Restablecer los valores predeterminados",
|
||||
"listening_label": "Escuchando…",
|
||||
"transcribing_label": "Transcribiendo…",
|
||||
"pasted": "Pegado",
|
||||
"no_speech": "No se detectó voz",
|
||||
"mic_denied": "Acceso al micrófono denegado",
|
||||
"mic_denied_toast": "Acceso al micrófono denegado. {{hint}}",
|
||||
"mic_hint_mac": "macOS: abra Configuración del sistema → Privacidad y seguridad → Micrófono y habilite OmniVoice.",
|
||||
"mic_hint_windows": "Windows: abra Configuración → Privacidad y seguridad → Micrófono y permita OmniVoice.",
|
||||
"mic_hint_linux": "Linux: verifique que su usuario esté en el grupo de audio y que WebView tenga acceso al micrófono.",
|
||||
"transcription_failed": "Error de transcripción: {{message}}"
|
||||
},
|
||||
"logs": {
|
||||
"no_tauri_log": "Aún no hay ningún registro de Tauri en el disco: inicie a través de la compilación de escritorio para producir uno",
|
||||
"empty_frontend": "Aún no se han capturado entradas de la consola frontal. Interactúa con la aplicación: todas las consolas* aparecerán aquí.",
|
||||
"empty_tauri": "No hay registros de Tauri disponibles. Se ejecuta únicamente en el shell del escritorio.",
|
||||
"empty_backend": "El registro de tiempo de ejecución está vacío. La actividad aparecerá aquí cuando el backend la registre."
|
||||
"empty_backend": "El registro de tiempo de ejecución está vacío. La actividad aparecerá aquí cuando el backend la registre.",
|
||||
"title": "Registros",
|
||||
"source_backend": "backend",
|
||||
"source_frontend": "Interfaz",
|
||||
"source_tauri": "Tauro",
|
||||
"expand": "Expandir registros",
|
||||
"collapse": "Contraer registros",
|
||||
"expand_aria": "Expandir el panel de registros",
|
||||
"collapse_aria": "Contraer panel de registros",
|
||||
"drag_resize": "Arrastra para cambiar el tamaño",
|
||||
"refresh": "Actualizar",
|
||||
"refresh_aria": "Actualizar registros",
|
||||
"copy_visible": "Copiar registro visible",
|
||||
"copy_visible_aria": "Copiar registro visible",
|
||||
"clear": "Borrar",
|
||||
"clear_aria": "Borrar registro",
|
||||
"report_issue": "Informar problema (diagnóstico de copia)",
|
||||
"report_issue_aria": "Informar problema",
|
||||
"close": "Cerrar",
|
||||
"close_aria": "Cerrar panel de registros",
|
||||
"join_discord": "Únete a nuestra discordia",
|
||||
"join_discord_aria": "Únete a nuestra comunidad de Discord",
|
||||
"support_project": "Apoya este proyecto",
|
||||
"support_project_aria": "Apoya este proyecto",
|
||||
"empty_frontend_short": "Aún no hay salida de la consola frontal.",
|
||||
"empty_lines": "Sin colas.",
|
||||
"all_clear": "✅ Todo claro: no se detectaron problemas",
|
||||
"log_cleared": "{{source}} registro borrado",
|
||||
"clear_failed": "Borrado fallido: {{message}}",
|
||||
"log_copied": "Registro {{source}} copiado",
|
||||
"copy_failed": "Copia fallida: {{message}}",
|
||||
"report_copied": "Informe de diagnóstico copiado: péguelo en un problema de GitHub.",
|
||||
"report_failed": "Informe fallido: {{message}}"
|
||||
},
|
||||
"voice_profile": {
|
||||
"test_text": "Hola, esta es una prueba de esta voz.",
|
||||
@@ -527,7 +645,17 @@
|
||||
"install_already": "{{engine}} ya estaba instalado",
|
||||
"install_ok": "{{engine}} instalado",
|
||||
"install_failed": "Error de instalación: {{message}}",
|
||||
"prep_elapsed": "{{time}} transcurrido"
|
||||
"prep_elapsed": "{{time}} transcurrido",
|
||||
"add_language": "Agregar idioma",
|
||||
"search_languages": "Buscar idiomas…",
|
||||
"languages_selected_one": "{{count}} idioma seleccionado",
|
||||
"languages_selected_other": "{{count}} idiomas seleccionados",
|
||||
"more_to_narrow": "+{{count}} más — escribe para limitar",
|
||||
"no_matches": "No hay coincidencias",
|
||||
"diagnostic_copied": "Diagnóstico copiado",
|
||||
"copy_failed": "Copia fallida",
|
||||
"open_docs": "Documentos abiertos",
|
||||
"copy_diagnostic": "Copiar diagnóstico"
|
||||
},
|
||||
"glossary": {
|
||||
"title": "Glosario",
|
||||
@@ -602,7 +730,17 @@
|
||||
"more_actions_title": "Más acciones",
|
||||
"speaker_pick": "Elige…",
|
||||
"speaker_title_detected": "Altavoz: elija uno de los detectados o escriba un nombre personalizado",
|
||||
"speaker_title_custom": "Orador: escriba un nombre (no se detectaron clones de diarización)"
|
||||
"speaker_title_custom": "Orador: escriba un nombre (no se detectaron clones de diarización)",
|
||||
"time_edit_title": "Haga clic para editar la hora de inicio (m:ss.s). Ingrese para confirmar, Esc para cancelar.",
|
||||
"fit_fits": "Se adapta",
|
||||
"fit_fits_title": "El audio de velocidad natural cabe dentro de la ranura.",
|
||||
"fit_overflows": "Se desborda +{{seconds}}s",
|
||||
"fit_overflows_title": "El texto traducido era más largo que el espacio original en {{seconds}}s. El audio estaba muy recortado; acorte el texto o cambie el tiempo a \"Estirar vídeo\".",
|
||||
"fit_stretched": "Vídeo {{ratio}}×",
|
||||
"fit_stretched_title": "Modo Stretch Video: el video de este segmento se ralentizó a {{ratio}}× para adaptarse al audio doblado natural.",
|
||||
"fit_compressed_title": "El audio TTS es el {{pct}}% de la ranura: muy comprimido.",
|
||||
"fit_audio_title": "El audio encaja dentro de la ranura.",
|
||||
"fit_ratio_title": "El audio TTS es el {{pct}}% de la ranura."
|
||||
},
|
||||
"voice": {
|
||||
"personality": "Personalidad",
|
||||
@@ -697,10 +835,24 @@
|
||||
"status_running": "corriendo",
|
||||
"status_done": "hecho",
|
||||
"status_failed": "falló",
|
||||
"status_cancelled": "cancelado"
|
||||
"status_cancelled": "cancelado",
|
||||
"add_to_queue_title": "Agregar videos a la cola",
|
||||
"drop_hint_text": "Suelte archivos de vídeo aquí o haga clic para explorar",
|
||||
"drop_formats": "MP4 · MOV · MKV · WEBM",
|
||||
"files_kicker": "ARCHIVOS ({{count}})",
|
||||
"file_size_mb": "{{size}} MB",
|
||||
"target_languages": "IDIOMAS OBJETIVO",
|
||||
"voice_kicker": "VOZ",
|
||||
"default_option": "Predeterminado",
|
||||
"clone_profiles": "Clonar perfiles",
|
||||
"presets": "Preajustes",
|
||||
"preserve_bg": "Conservar audio de fondo (música/FX)",
|
||||
"estimate": "{{videos}} vídeo(s) × {{langs}} idioma(s) = {{jobs}} trabajo(s)",
|
||||
"select_files_langs": "Seleccionar archivos e idiomas",
|
||||
"add_to_queue": "Agregar a la cola"
|
||||
},
|
||||
"gallery": {
|
||||
"title": "Galería",
|
||||
"title": "OmniVoice Galería",
|
||||
"search_placeholder": "Buscar en YouTube…",
|
||||
"all_voices": "Todas las voces ({{count}})",
|
||||
"no_voices": "Aún no hay voces",
|
||||
@@ -715,6 +867,14 @@
|
||||
"youtube_results": "Resultados de YouTube ({{count}})",
|
||||
"clone_profile": "Clonar perfil",
|
||||
"crop_audio": "Recortar audio",
|
||||
"cat_disney": "disney",
|
||||
"cat_anime": "animado",
|
||||
"cat_marvel": "maravilla/dc",
|
||||
"cat_celebs": "celebridades",
|
||||
"cat_politicians": "Políticos",
|
||||
"cat_news": "Presentadores de noticias",
|
||||
"cat_gaming": "Juegos",
|
||||
"cat_books": "Libros/Películas",
|
||||
"subtitle": "Cientos de voces diseñadas ya preparadas: elige una y listo.",
|
||||
"zone_archetypes": "Arquetipos",
|
||||
"zone_imports": "Mis Importaciones",
|
||||
@@ -819,7 +979,6 @@
|
||||
"badge": "Licencia Comercial",
|
||||
"hero_title": "Enviar voces de IA en producción",
|
||||
"hero_desc": "OmniVoice Studio está disponible bajo la Licencia de fuente funcional (FSL). La mayoría de los usuarios pueden evaluar, crear prototipos e incluso implementar internamente sin un acuerdo comercial. Necesita una licencia comercial sólo si está creando un producto o servicio de la competencia, o si su caso de uso queda fuera de los límites de la FSL.",
|
||||
"hero_note": "Para crear un producto o servicio competitivo o implementarlo a escala (por ejemplo, ofrecer API de pago por uso) se requiere una licencia comercial. Próximamente niveles de precios; póngase en contacto mientras tanto.",
|
||||
"why_title": "Por qué las empresas eligen OmniVoice",
|
||||
"pricing_title": "Precios",
|
||||
"faq_title": "Preguntas comunes",
|
||||
@@ -839,7 +998,8 @@
|
||||
"benefit_source": "Núcleo disponible en la fuente",
|
||||
"benefit_source_desc": "Visibilidad total de la pila. Audite, bifurque y adapte dentro de los términos de la licencia.",
|
||||
"benefit_lang": "646 idiomas",
|
||||
"benefit_lang_desc": "Transcribe, traduce y dobla en 646 idiomas con calidad de nivel humano."
|
||||
"benefit_lang_desc": "Transcribe, traduce y dobla en 646 idiomas con calidad de nivel humano.",
|
||||
"hero_note": "El uso interno, incluso a escala, es gratuito según la FSL; solo se requiere una licencia comercial para ofrecer OmniVoice a otros como un producto o servicio de la competencia (una API alojada o de pago por uso, una aplicación revendida o de marca blanca). Los niveles de precios estarán disponibles pronto; póngase en contacto mientras tanto."
|
||||
},
|
||||
"exportModal": {
|
||||
"export": "Exportar",
|
||||
@@ -928,7 +1088,479 @@
|
||||
"dubbing_title": "Ver doblaje en acción",
|
||||
"dubbing_sync": "Reproducción sincronizada",
|
||||
"dubbing_picker": "Prueba con otro idioma:",
|
||||
"dubbing_cta": "Ejecute esto en su propio video →"
|
||||
"dubbing_cta": "Ejecute esto en su propio video →",
|
||||
"dubbing_loading": "Cargando demostración de doblaje…",
|
||||
"dubbing_dismiss": "Descartar demostración de doblaje",
|
||||
"original_tag": "originales",
|
||||
"dubbed_tag": "apodado",
|
||||
"script_conversational": "conversacional",
|
||||
"script_technical": "Vocabulario técnico",
|
||||
"script_french": "No inglés (francés)",
|
||||
"aria_pause": "Pausa {{label}}",
|
||||
"aria_hear": "Escuchar {{label}}",
|
||||
"aria_replay": "Reproducir {{label}} a través del transcriptor"
|
||||
},
|
||||
"direction": {
|
||||
"title": "Dirección del segmento #{{id}}",
|
||||
"desc": "Dígale al oleoducto cómo debería sentirse esta línea. El inglés simple funciona: el sistema asigna sus palabras a una taxonomía estable (energía/emoción/ritmo/intimidad/formalidad) y luego integra la taxonomía a través de traducción cinematográfica, TTS y ajuste de ranura.",
|
||||
"label": "Dirección",
|
||||
"lineHint": "Línea: \"{{text}}\"",
|
||||
"placeholder": "por ej. urgente y sorprendido / cálido, esperanzado / susurrado, íntimo",
|
||||
"previewParse": "Análisis de vista previa",
|
||||
"previewFailed": "Error en la vista previa: {{message}}",
|
||||
"clear": "Borrar",
|
||||
"cancel": "Cancelar",
|
||||
"saveDirection": "Guardar dirección",
|
||||
"ttsInstruct": "TTS instruye:",
|
||||
"nothingParsed": "- (nada analizado)",
|
||||
"translateHint": "Traducir pista:",
|
||||
"rateBias": "Sesgo de tarifas:",
|
||||
"speedsUp": "acelera",
|
||||
"slowsDown": "se ralentiza",
|
||||
"taxonomyTokens": "fichas de taxonomía"
|
||||
},
|
||||
"errors": {
|
||||
"title": "Esta pestaña tuvo un problema.",
|
||||
"desc": "No te preocupes, el resto de la aplicación sigue funcionando. Puedes cambiar de pestaña o volver a intentarlo a continuación.",
|
||||
"tryAgain": "Inténtalo de nuevo",
|
||||
"openDocs": "Abrir documentos para este error"
|
||||
},
|
||||
"keyboard": {
|
||||
"title": "Atajos de teclado",
|
||||
"footer": "Presione <1>?</1> en cualquier momento para abrir esto.",
|
||||
"or": "o",
|
||||
"nav": "Navegación",
|
||||
"nav_cheatsheet": "Mostrar esta hoja de trucos",
|
||||
"nav_closeModal": "Cerrar modal/cancelar",
|
||||
"nav_save": "Guardar proyecto/comprobar recorte",
|
||||
"segmentEditor": "editor de segmentos",
|
||||
"seg_split": "Dividir segmento en el cursor",
|
||||
"seg_merge": "Fusionarse con el siguiente segmento",
|
||||
"seg_undo": "Deshacer",
|
||||
"seg_redo": "Rehacer",
|
||||
"seg_click": "acción primaria",
|
||||
"seg_shiftClick": "Seleccionar rango",
|
||||
"trimmer": "Recortador de audio",
|
||||
"trim_playPause": "Vista previa de reproducción/pausa",
|
||||
"trim_nudgeStart": "Empujar el mango de inicio",
|
||||
"trim_nudgeEnd": "Mango de extremo de empuje",
|
||||
"trim_fineNudge": "Buen empujón",
|
||||
"trim_coarseNudge": "empujón grueso",
|
||||
"trim_zoomIn": "Acercar/alejar",
|
||||
"trim_fitAll": "Ajustar todo/Ajustar selección",
|
||||
"trim_confirm": "Confirmar recorte",
|
||||
"dub": "Doblar",
|
||||
"dub_generate": "Generar doblaje",
|
||||
"dub_sidebar": "Alternar barra lateral"
|
||||
},
|
||||
"network": {
|
||||
"sharing_on_title": "Compartir en: haga clic para obtener más detalles",
|
||||
"share_on_network": "Comparte en tu red",
|
||||
"switching": "Cambiando…",
|
||||
"network": "Red",
|
||||
"local": "locales",
|
||||
"share_confirm_title": "¿Compartir en tu red?",
|
||||
"share_confirm_hint": "Otros dispositivos en su Wi-Fi/Ethernet podrán acceder a OmniVoice utilizando el PIN de acceso que se muestra una vez que esté encendido.",
|
||||
"enabling": "Habilitando…",
|
||||
"enable": "Habilitar",
|
||||
"shared_title": "Compartido en tu red",
|
||||
"no_interface": "No hay una interfaz de red accesible: conéctese a Wi-Fi/Ethernet.",
|
||||
"copy_link": "Copiar enlace",
|
||||
"open_in_browser": "Abrir en el navegador",
|
||||
"qr_alt": "QR para {{ip}}",
|
||||
"pin": "PIN:",
|
||||
"stop_sharing": "dejar de compartir",
|
||||
"copied": "Copiado",
|
||||
"enable_error": "No se pudo habilitar el uso compartido: {{message}}",
|
||||
"disable_error": "No se pudo desactivar: {{message}}"
|
||||
},
|
||||
"readiness": {
|
||||
"checking_system": "Sistema de comprobación...",
|
||||
"all_ready": "Todos los sistemas listos",
|
||||
"system_readiness": "Preparación del sistema",
|
||||
"asr_model": "Modelo ASR",
|
||||
"loaded_ready": "Cargado y listo",
|
||||
"loading_first_run": "Cargando... (esto puede tardar entre 1 y 2 minutos en la primera ejecución)",
|
||||
"failed_to_load": "No se pudo cargar",
|
||||
"not_loaded_yet": "Aún no cargado: se cargará en la primera transcripción",
|
||||
"error_check_logs": "Error: {{error}}. Verifique los registros e intente reiniciar.",
|
||||
"check_logs_restart": "Verifique los registros para detectar errores de carga del modelo. Intenta reiniciar.",
|
||||
"llm_cinematic": "LLM (Cinemático)",
|
||||
"llm_configure": "Configure TRANSLATE_BASE_URL para calidad de traducción cinematográfica",
|
||||
"llm_set_env": "Establezca las variables de entorno TRANSLATE_BASE_URL y TRANSLATE_API_KEY. Funciona con Ollama, OpenAI, LM Studio, etc.",
|
||||
"llm_optional": "Opcional: configure TRANSLATE_BASE_URL para calidad cinematográfica"
|
||||
},
|
||||
"license": {
|
||||
"title": "Supertonic-3 — Aceptación de licencia",
|
||||
"intro": "Supertonic-3 se envía bajo dos licencias distintas. Revise ambos antes de habilitar el motor.",
|
||||
"sdk_heading": "Código SDK · MIT",
|
||||
"sdk_desc": "El SDK de inferencia de Python (supertonic) tiene licencia MIT. Uso permisivo, incluido el comercial.",
|
||||
"read_mit": "Leer la licencia MIT →",
|
||||
"model_heading": "Pesos modelo · OpenRAIL-M",
|
||||
"model_desc": "Los pesos del modelo Supertonic-3 se publican bajo la licencia OpenRAIL-M. Esta licencia restringe el uso a fines no maliciosos; consulte la licencia vinculada para conocer el conjunto completo de restricciones basadas en el uso.",
|
||||
"read_openrail": "Lea la licencia OpenRAIL-M →",
|
||||
"footer": "Al hacer clic en Aceptar se registra su aceptación en la configuración local de OmniVoice y se habilita el motor. Su aceptación se almacena únicamente en esta máquina; no se informa nada a Supertone Inc. ni a ningún tercero.",
|
||||
"saving": "Guardando…",
|
||||
"accept": "Aceptar",
|
||||
"accepted_toast": "Se acepta licencia Supertonic-3.",
|
||||
"accept_error": "No se pudo registrar la aceptación de la licencia: {{message}}"
|
||||
},
|
||||
"voicePreview": {
|
||||
"title": "Vista previa de voz",
|
||||
"close": "Cerrar vista previa",
|
||||
"default_text": "¡Hola! Esta es una vista previa de cómo sueno con esta voz.",
|
||||
"default_voice": "Voz predeterminada",
|
||||
"clone_profiles": "Clonar perfiles",
|
||||
"designed_voices": "Voces diseñadas",
|
||||
"presets": "Preajustes",
|
||||
"placeholder": "Escribe algo para escuchar...",
|
||||
"stop": "Detener",
|
||||
"regenerate": "regenerar",
|
||||
"preview": "Vista previa",
|
||||
"hint": "8 pasos · vista previa rápida"
|
||||
},
|
||||
"header": {
|
||||
"kicker_studio": "Estudio",
|
||||
"kicker_library": "Biblioteca",
|
||||
"kicker_preferences": "Preferencias",
|
||||
"kicker_licensing": "Licencias",
|
||||
"label_launchpad": "Plataforma de lanzamiento",
|
||||
"label_clone": "Clon de voz",
|
||||
"label_design": "Diseño de voz",
|
||||
"label_dub": "Doblaje",
|
||||
"label_projects": "OmniDrive",
|
||||
"label_gallery": "Galería",
|
||||
"label_transcriptions": "Transcripciones",
|
||||
"label_settings": "Configuración",
|
||||
"label_enterprise": "Licencia Comercial",
|
||||
"status_ready": "Listo",
|
||||
"status_loading": "Cargando…",
|
||||
"status_idle": "inactivo",
|
||||
"memory_management": "Gestión de memoria",
|
||||
"flush": "al ras",
|
||||
"loaded_models": "Modelos cargados",
|
||||
"no_models": "No hay modelos cargados",
|
||||
"unload": "descargar",
|
||||
"flush_caches": "Vaciar cachés",
|
||||
"unload_all_flush": "Descargar todo + enjuagar"
|
||||
},
|
||||
"sidebar": {
|
||||
"tab_drive": "conducir",
|
||||
"tab_history": "Historia",
|
||||
"tab_exports": "Exportaciones",
|
||||
"save_project": "Guardar proyecto de doblaje",
|
||||
"save_new_project": "Guardar como nuevo proyecto de doblaje",
|
||||
"dub_projects": "Proyectos de doblaje",
|
||||
"voice_clones": "Clones de voz",
|
||||
"designed_voices": "Voces diseñadas",
|
||||
"no_dub_projects": "No hay proyectos de doblaje guardados",
|
||||
"no_dub_hint": "Sube un vídeo y haz clic en Guardar para conservar tu trabajo.",
|
||||
"no_clones": "Aún no hay clones de voz",
|
||||
"no_clones_hint": "Grabe o cargue audio, luego haga clic en Guardar como perfil de voz.",
|
||||
"no_designs": "Aún no hay voces diseñadas",
|
||||
"no_designs_hint": "Genera una voz y guárdala del Historial.",
|
||||
"history_subtitle": "Historial de generación · Almacenado en SQLite",
|
||||
"no_history": "Sin historial generacional",
|
||||
"no_history_hint": "Sintetiza audio o copia un vídeo: los resultados aparecerán aquí.",
|
||||
"clear_history": "Borrar historial",
|
||||
"clear_confirm": "¿Borrar todos los {{count}} elementos del historial? Esto no se puede deshacer.",
|
||||
"history_cleared": "Historial borrado",
|
||||
"recent_exports": "Exportaciones recientes",
|
||||
"no_exports": "No hay salidas descargadas",
|
||||
"no_exports_hint": "Exporte un archivo a través de Tauri para verlo rastreado aquí.",
|
||||
"show_in_folder": "Mostrar en carpeta",
|
||||
"open": "Abierto",
|
||||
"select": "Seleccionar",
|
||||
"try_voice": "Pruebe",
|
||||
"consistent": "consistente",
|
||||
"locked": "bloqueado",
|
||||
"clone_label": "clonar",
|
||||
"design_label": "Diseño",
|
||||
"dub_label": "Doblar",
|
||||
"save_label": "Guardar",
|
||||
"lock_identity": "Bloquear identidad de voz",
|
||||
"in_folder": "en {{folder}}"
|
||||
},
|
||||
"trimmer": {
|
||||
"title": "Recortar audio de referencia",
|
||||
"decoding": "Decodificando audio…",
|
||||
"meta_length": "Longitud {{duration}} · {{sampleRate}} Hz",
|
||||
"meta_rendering": "representación de forma de onda {{percent}}%",
|
||||
"keyboard_hint": "desplazamiento = zoom · mayúsculas+desplazamiento = panorámica · alt+arrastrar = panorámica · teclas ⏐ ⟵ ⟶ ⏐ ajustan los controles",
|
||||
"zoom_in": "Acercar (+)",
|
||||
"zoom_out": "Alejar (-)",
|
||||
"fit_all": "Se adapta a todos (Inicio)",
|
||||
"fit_selection": "Selección de ajuste (Fin)",
|
||||
"fit_sel_btn": "AJUSTE SEL",
|
||||
"view_range": "Ver {{start}} → {{end}} ({{duration}})",
|
||||
"start_label": "Empezar",
|
||||
"end_label": "Fin",
|
||||
"length_label": "Longitud",
|
||||
"too_long": ">{{max}}s",
|
||||
"too_short": "demasiado corto",
|
||||
"length_ok": "bien",
|
||||
"loop_preview": "Vista previa del bucle",
|
||||
"pause": "Pausa",
|
||||
"preview_selection": "Selección de vista previa",
|
||||
"play_hint": "Espacio para jugar · Enter para confirmar · Esc para cancelar",
|
||||
"cancel": "Cancelar",
|
||||
"use_trimmed": "Usar recortado",
|
||||
"decode_failed": "Error de decodificación: {{message}}",
|
||||
"playback_failed": "Error de reproducción: {{message}}",
|
||||
"audio_load_failed": "Falló la carga de audio",
|
||||
"unit_seconds": "s"
|
||||
},
|
||||
"casting": {
|
||||
"title": "Casting de oradores",
|
||||
"auto_assign_title": "Asignar automáticamente voces a partir de clones de altavoces extraídos",
|
||||
"auto_cast": "emisión automática",
|
||||
"all_cast": "Todo el elenco",
|
||||
"segments_count": "{{count}} segmentos",
|
||||
"assign_voice": "Asignar voz…",
|
||||
"from_video": "🎤 Del vídeo ({{name}})",
|
||||
"no_profiles": "Aún no se han guardado perfiles de voz.",
|
||||
"preview_voice": "Vista previa de voz"
|
||||
},
|
||||
"checkpoint": {
|
||||
"asr_title": "Transcripciones listas",
|
||||
"asr_cta": "Traducir",
|
||||
"asr_hint": "Corrija cualquier error de ASR ahora: la dicción precisa ahorra intentos de TTS más adelante.",
|
||||
"translate_title": "Traducciones listas",
|
||||
"translate_cta": "Generar doblaje",
|
||||
"translate_hint": "Hojee el texto de destino. Las líneas demasiado largas aumentan la velocidad; También puedes editar directamente.",
|
||||
"done_title": "doblaje completo",
|
||||
"done_hint": "Revise las proporciones de sincronización y sincronización. Modifica cualquier línea y presiona \"Regeneración cambiada\" para una rehacer parcial rápida.",
|
||||
"segment_one": "{{count}} segmento",
|
||||
"segment_other": "{{count}} segmentos",
|
||||
"dismiss_title": "Descartar: no volverá a aparecer en esta etapa hasta que se vuelva a cargar"
|
||||
},
|
||||
"compare": {
|
||||
"title": "Comparación de voz A/B",
|
||||
"close": "Cerrar comparación",
|
||||
"desc": "Compara dos voces una al lado de la otra para tomar decisiones sobre el reparto. La aplicación permanece interactiva detrás.",
|
||||
"test_phrase": "frase de prueba",
|
||||
"voice_a": "Voz A",
|
||||
"voice_b": "Voz B",
|
||||
"select_voice": "— Seleccionar voz —",
|
||||
"preset_suffix": "(Preestablecido)",
|
||||
"no_audio": "Aún no hay audio",
|
||||
"close_btn": "Cerrar",
|
||||
"comparing": "Comparando…",
|
||||
"compare_btn": "Comparar",
|
||||
"preparing_voice": "Preparando voz...",
|
||||
"generating_voice_a": "Generando voz A...",
|
||||
"generating_voice_b": "Generando voz B...",
|
||||
"comparison_complete": "¡Comparación completa!",
|
||||
"play_failed": "Error al reproducir: {{message}}"
|
||||
},
|
||||
"models": {
|
||||
"hf_token_set_toast": "Conjunto de tokens HuggingFace: descargas más rápidas habilitadas",
|
||||
"hf_token_save_failed": "No se pudo guardar el token",
|
||||
"install_started": "Instalación iniciada: progreso en la fila",
|
||||
"delete_confirm": "¿Eliminar {{repoId}}? Puedes reinstalarlo más tarde.",
|
||||
"delete_confirm_title": "Eliminar modelo",
|
||||
"deleted": "Eliminado {{repoId}}",
|
||||
"reinstall_confirm": "¿Reinstalar {{repoId}}? Esto eliminará la copia actual y la descargará nuevamente.",
|
||||
"reinstall_confirm_title": "Reinstalar el modelo",
|
||||
"reinstalling": "Reinstalar",
|
||||
"recommended_installed": "Los modelos recomendados ya están instalados.",
|
||||
"started_downloading_one": "Comenzó a descargar el modelo {{count}}",
|
||||
"started_downloading_other": "Comenzó a descargar modelos {{count}}",
|
||||
"install_failed": "Error de instalación: {{message}}",
|
||||
"removing_cached": "Eliminando revisiones almacenadas en caché…",
|
||||
"resolving_metadata": "Resolución de metadatos del repositorio",
|
||||
"retry_attempt": "Reintentar {{attempt}} — {{error}}",
|
||||
"connecting_hf": "Conectándose a HuggingFace…",
|
||||
"resolving_files_one": "Resolviendo el archivo {{count}}…",
|
||||
"resolving_files_other": "Resolviendo archivos {{count}}…",
|
||||
"resolving_files_active": "Resolviendo {{count}} archivo(s)… · {{file}}",
|
||||
"measuring": "midiendo…",
|
||||
"files_progress": "Archivos {{done}}/{{total}}",
|
||||
"install_error": "Error de instalación: {{error}}",
|
||||
"view_on_hf": "Ver en HuggingFace",
|
||||
"install_btn": "Instalar",
|
||||
"reinstall_btn": "Reinstalar",
|
||||
"downloading": "descargando",
|
||||
"deleting": "eliminando",
|
||||
"working": "trabajando",
|
||||
"installed": "instalado",
|
||||
"not_installed": "no instalado",
|
||||
"required_tag": "requerido",
|
||||
"delete_btn": "Eliminar",
|
||||
"hf_token_btn": "Ficha HF",
|
||||
"hf_set_title": "Configure el token HuggingFace para descargas más rápidas",
|
||||
"get_token": "Obtener token →",
|
||||
"reco_installed_for": "Paquete recomendado instalado para {{device}}",
|
||||
"reco_for": "Recomendado para {{device}}",
|
||||
"starting": "Empezando…",
|
||||
"required_size": "Requerido ~{{size}} GB",
|
||||
"all_size": "Todo ~{{size}} GB",
|
||||
"req_tag": "req",
|
||||
"search_placeholder": "Buscar modelos…",
|
||||
"search_label": "Buscar modelos",
|
||||
"no_matches": "Ningún modelo coincide con tus filtros.",
|
||||
"sort_by": "Ordenar por {{column}}",
|
||||
"column_model": "modelo",
|
||||
"column_role": "Rol",
|
||||
"column_size": "Tamaño",
|
||||
"column_status": "Estado",
|
||||
"ready_badge": "Listo",
|
||||
"loading_badge": "Cargando…",
|
||||
"idle_badge": "inactivo",
|
||||
"started_downloading_required_one": "Comenzó a descargar {{count}} modelo requerido",
|
||||
"started_downloading_required_other": "Comenzó a descargar {{count}} modelos requeridos"
|
||||
},
|
||||
"enterprise_faq": {
|
||||
"q_internal_tools": "¿Necesito una licencia para herramientas internas?",
|
||||
"a_internal_tools": "El uso interno por parte de sus empleados y contratistas es un Propósito permitido según la FSL; no se requiere licencia. Se necesita una licencia comercial cuando pone OmniVoice a disposición de otros como parte de un producto o servicio de la competencia (reventa, SaaS alojado, marca blanca).",
|
||||
"q_try_before": "¿Puedo intentarlo antes de comprometerme?",
|
||||
"a_try_before": "Sí. La aplicación completa se puede descargar y ejecutar localmente de forma gratuita para su evaluación según la FSL. Cuando esté listo para discutir una implementación comercial, envíenos un correo electrónico y trabajaremos juntos en los detalles.",
|
||||
"q_watermark": "¿Qué pasa con la marca de agua?",
|
||||
"a_watermark": "La marca de agua invisible AudioSeal está incrustada de forma predeterminada. Los licenciatarios comerciales pueden desactivarlo en Configuración → Privacidad. El uso gratuito/personal siempre incluye la marca de agua.",
|
||||
"q_apache": "¿La fuente alguna vez se convierte en Apache 2.0?",
|
||||
"a_apache": "Sí. Cada versión se convierte automáticamente a la Licencia Apache, Versión 2.0 en el segundo aniversario de su publicación. Eso significa que el lanzamiento de hoy es Apache 2.0 dentro de dos años, no es necesario que hagamos nada: la FSL lo garantiza irrevocablemente."
|
||||
},
|
||||
"gallery_extra": {
|
||||
"save_prompt": "Ingrese un nombre para este perfil de voz:",
|
||||
"saved_as_profile": "¡Voz guardada como perfil!",
|
||||
"save_profile_failed": "No se pudo guardar el perfil",
|
||||
"download_failed": "Error de descarga: {{message}}",
|
||||
"trim_load_failed": "No se pudo cargar el audio para recortar: {{message}}",
|
||||
"upload_crop_failed": "No se pudo cargar la voz recortada: {{message}}"
|
||||
},
|
||||
"dub_workflow": {
|
||||
"preparing_audio": "Preparando audio…",
|
||||
"preparing_video": "Preparando vídeo...",
|
||||
"extracting_audio_scenes": "Extrayendo audio y escenas...",
|
||||
"transcribing_audio": "Transcribiendo audio…",
|
||||
"transcription_complete": "Transcripción completa",
|
||||
"upload_cancelled": "Subida cancelada",
|
||||
"upload_failed": "Error al cargar: {{message}}",
|
||||
"downloading_video": "Descargando vídeo…",
|
||||
"ingested": "Ingerido {{url}}",
|
||||
"ingest_cancelled": "Ingesta cancelada",
|
||||
"ingest_failed": "Falló la ingesta de URL: {{message}}",
|
||||
"retry_cancelled": "Reintento cancelado",
|
||||
"transcription_failed": "Error de transcripción: {{message}}",
|
||||
"import_srt_no_job": "Primero cargue o ingiera un video: no hay ningún trabajo al que adjuntar subtítulos.",
|
||||
"imported_cues": "{{count}} cue(s) importadas de {{file}}",
|
||||
"skipped_malformed": "{{count}} omitido (mal formado)",
|
||||
"dropped_overlap": "{{count}} caído (superposición)",
|
||||
"clamped_to_duration": "{{count}} sujetado a la longitud del medio",
|
||||
"srt_import_failed": "Error al importar SRT",
|
||||
"cleaned_one": "Fragmento {{count}} limpio",
|
||||
"cleaned_other": "Fragmentos {{count}} limpios",
|
||||
"segments_clean": "Segmentos ya limpios",
|
||||
"cleanup_failed": "Error de limpieza: {{message}}",
|
||||
"cinematic_no_llm": "La calidad cinematográfica necesita un LLM: configure TRANSLATE_BASE_URL + TRANSLATE_API_KEY (Ollama funciona localmente). Volviendo a Fast.",
|
||||
"translate_errors": "{{errorCount}}/{{totalCount}} segmento(s) fallidos: {{firstError}}",
|
||||
"translated_segments": "{{count}} segmento(s) traducido(s) → {{lang}}",
|
||||
"translated_cinematic_suffix": "(Cinemático)",
|
||||
"translation_failed": "Error de traducción: {{message}}",
|
||||
"regenerating": "Regenerando {{count}} segmento(s)…",
|
||||
"generating_dub": "Generando doblaje…",
|
||||
"generating_progress": "Generando doblaje… {{current}}/{{total}}",
|
||||
"generation_aborted": "Generación abortada.",
|
||||
"dubbing_aborted": "Doblaje cancelado",
|
||||
"generation_stream_ended": "El flujo de generación finalizó antes de completarse",
|
||||
"dub_complete": "doblaje completo",
|
||||
"stop_failed": "No se pudo detener",
|
||||
"save_first": "Primero haga clic en \"Cargar y transcribir\" para que el video se procese en el servidor antes de guardarlo.",
|
||||
"project_saved": "Proyecto guardado",
|
||||
"project_created": "Proyecto creado",
|
||||
"save_failed": "Error al guardar: {{message}}",
|
||||
"opened_project": "Abierto: {{name}}",
|
||||
"delete_project_confirm": "¿Eliminar este proyecto? Esto no se puede deshacer.",
|
||||
"project_deleted": "Proyecto eliminado",
|
||||
"delete_history_confirm": "¿Eliminar este elemento del historial?",
|
||||
"history_deleted": "Elemento del historial eliminado",
|
||||
"restored_state": "Estado de generación anterior restaurado",
|
||||
"upgrading_preview": "Actualizando {{count}} segmentos de calidad de vista previa a calidad completa..."
|
||||
},
|
||||
"tts_errors": {
|
||||
"enter_text": "Por favor ingresa texto",
|
||||
"upload_or_select": "Sube un audio o selecciona un perfil de voz",
|
||||
"trim_hint": "El audio es {{duration}}s; recórtelo a ≤{{max}}s para una mejor clonación.",
|
||||
"timeout": "Se agotó el tiempo de generación: es posible que el modelo aún se esté descargando. Verifique Configuración → Registros y vuelva a intentarlo.",
|
||||
"error_prefix": "Error: {{message}}",
|
||||
"ignored_unsupported": "Instrucción no admitida ignorada: {{items}}",
|
||||
"ignored_duplicate": "Ignorado (categoría ya establecida): {{items}}"
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Compartir y acceso remoto",
|
||||
"help": "Exponga esta instancia de OmniVoice en ejecución a sus otras máquinas sin reiniciarla. Solo bucle invertido es el valor predeterminado: no se comparte nada hasta que lo activas aquí.",
|
||||
"local_network": "red local",
|
||||
"local_help": "Comparte en tu Wi-Fi/Ethernet con un PIN de acceso único. Otros dispositivos escanean el código QR o abren el enlace.",
|
||||
"ports_title": "Puertos",
|
||||
"ports_help": "Estos se configuran mediante variables de entorno leídas al inicio. Cambie el backend o el puerto de la interfaz de usuario configurando la variable y reiniciando OmniVoice.",
|
||||
"backend_port": "Puerto de fondo",
|
||||
"ui_port": "Puerto de interfaz de usuario",
|
||||
"lan_share_port": "Puerto LAN compartido",
|
||||
"port_error": "Ingrese un puerto entre 1024 y 65535",
|
||||
"port_saved": "Puerto LAN compartido guardado: se aplica la próxima vez que habilites el uso compartido",
|
||||
"port_save_failed": "No se pudo guardar el puerto: {{message}}",
|
||||
"saving": "Guardando…",
|
||||
"ports_note": "Los puertos de backend y UI se aplican al reiniciar. El puerto LAN compartido se aplica la próxima vez que habilite el uso compartido.",
|
||||
"tailscale_title": "Tailscale (acceso remoto privado)",
|
||||
"tailscale_checking": "Comprobando la escala trasera...",
|
||||
"tailscale_absent": "Escala de cola no detectada. Instálelo para comunicarse con OmniVoice de forma segura desde cualquier lugar de su red privada.",
|
||||
"tailscale_install": "Instalar escala trasera",
|
||||
"tailscale_running": "La escala de cola se está ejecutando. Ofrezca OmniVoice a través de su tailnet privada.",
|
||||
"tailscale_not_logged_in": "Tailscale está instalado pero no ha iniciado sesión. Inicie e inicie sesión en Tailscale primero.",
|
||||
"tailscale_enabled": "Servicio de escala final habilitado",
|
||||
"tailscale_enable_failed": "No se pudo habilitar Tailscale",
|
||||
"tailscale_enable_error": "No se pudo habilitar Tailscale: {{message}}",
|
||||
"tailscale_disabled": "Servicio de escala de cola inhabilitado",
|
||||
"tailscale_disable_failed": "No se pudo desactivar Tailscale",
|
||||
"tailscale_disable_error": "No se pudo desactivar Tailscale: {{message}}",
|
||||
"tailscale_enabling": "Habilitando…",
|
||||
"tailscale_enable_btn": "Habilitar el servicio Tailscale",
|
||||
"tailscale_disabling": "Deshabilitando…",
|
||||
"tailscale_disable_btn": "Detener el servicio Tailscale",
|
||||
"tailscale_copy": "Copiar enlace",
|
||||
"tailscale_open": "Abrir en el navegador",
|
||||
"copied": "Copiado",
|
||||
"tailscale_qr_alt": "Código QR para la URL de Tailscale"
|
||||
},
|
||||
"reportBug": {
|
||||
"label": "Informar un error",
|
||||
"title": "Abre una página de Problemas de GitHub precargada en su navegador. No se envía nada hasta que haga clic en Enviar."
|
||||
},
|
||||
"app": {
|
||||
"loading": "Cargando…",
|
||||
"trimmed_loaded": "Audio recortado cargado",
|
||||
"toast_exported": "Exportado: {{name}}",
|
||||
"toast_export_failed": "Error al exportar: {{message}}",
|
||||
"toast_open_folder_failed": "No se pudo abrir la carpeta: {{message}}",
|
||||
"toast_saving": "Guardando {{name}}...",
|
||||
"toast_saved": "Guardado: {{path}}",
|
||||
"toast_save_error": "Error al guardar: {{message}}",
|
||||
"toast_processing": "Procesando {{name}}...",
|
||||
"toast_downloaded": "Descargado {{name}}",
|
||||
"toast_download_error": "Error de descarga: {{message}}",
|
||||
"toast_upload_first": "Primero haga clic en \"Cargar y transcribir\" para que el video se procese en el servidor antes de guardarlo.",
|
||||
"toast_save_failed": "Error al guardar: {{message}}",
|
||||
"toast_opened": "Abierto: {{name}}",
|
||||
"toast_project_deleted": "Proyecto eliminado",
|
||||
"toast_restored_state": "Estado de generación anterior restaurado",
|
||||
"toast_history_deleted": "Elemento del historial eliminado",
|
||||
"toast_flushed": "Vaciado — RAM {{ram}}G · VRAM {{vram}}G{{unloaded}}",
|
||||
"toast_model_unloaded": "· modelo descargado",
|
||||
"toast_flush_failed": "Error de descarga: {{message}}",
|
||||
"toast_project_saved": "Proyecto guardado",
|
||||
"toast_project_created": "Proyecto creado"
|
||||
},
|
||||
"update": {
|
||||
"available": "Actualización {{version}} disponible",
|
||||
"install": "Instalar y reiniciar",
|
||||
"install_hint": "Descarga la actualización y reinicia en la nueva versión",
|
||||
"downloading": "Actualizando… {{pct}}%",
|
||||
"restart": "Reiniciar para actualizar",
|
||||
"busy": "Termina tu doblaje primero — luego instala la actualización.",
|
||||
"whats_new": "¿Qué hay de nuevo?",
|
||||
"failed": "La actualización falló",
|
||||
"retry": "Reintentar",
|
||||
"dismiss": "Descartar"
|
||||
},
|
||||
"archetypes": {
|
||||
"featured": "Destacado",
|
||||
@@ -946,5 +1578,25 @@
|
||||
"facet_accent": "acento",
|
||||
"facet_lang": "Idioma",
|
||||
"facet_whisper": "susurro"
|
||||
},
|
||||
"support": {
|
||||
"tab_support": "Soporte",
|
||||
"tab_license": "Licencia Comercial",
|
||||
"toggle_label": "Soporte o licencia comercial",
|
||||
"other_ways": "Otras formas de ayudar",
|
||||
"star_github": "Estrella en GitHub",
|
||||
"join_discord": "Únete a la discordia"
|
||||
},
|
||||
"updates": {
|
||||
"tab": "Actualizaciones",
|
||||
"up_to_date": "Actualizado · v{{version}}",
|
||||
"check_now": "Compruébalo ahora",
|
||||
"releases": "Lanzamientos",
|
||||
"current": "actual",
|
||||
"prerelease": "vista previa",
|
||||
"loading": "Cargando lanzamientos…",
|
||||
"none": "No se encontraron lanzamientos",
|
||||
"load_error": "No se pudieron cargar las versiones (¿sin conexión?)",
|
||||
"retry_load": "Reintentar"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,4 @@
|
||||
{
|
||||
"update": {
|
||||
"available": "Mise à jour {{version}} disponible",
|
||||
"install": "Installer et redémarrer",
|
||||
"install_hint": "Télécharger la mise à jour et redémarrer dans la nouvelle version",
|
||||
"downloading": "Mise à jour… {{pct}} %",
|
||||
"restart": "Redémarrer pour mettre à jour",
|
||||
"busy": "Terminez d'abord votre doublage, puis installez la mise à jour."
|
||||
},
|
||||
"nav": {
|
||||
"clone": "Cloner",
|
||||
"design": "Concevoir",
|
||||
@@ -16,7 +8,10 @@
|
||||
"launchpad": "Barre de lancement",
|
||||
"gallery": "Galerie",
|
||||
"transcripts": "Transcriptions",
|
||||
"omnidrive": "OmniDrive"
|
||||
"omnidrive": "OmniDrive",
|
||||
"move_rail_right": "Déplacer le rail vers la droite",
|
||||
"move_rail_left": "Déplacer le rail vers la gauche",
|
||||
"flip_rail": "Côté rail rabattable"
|
||||
},
|
||||
"settings": {
|
||||
"general": "Général",
|
||||
@@ -45,7 +40,40 @@
|
||||
"ffmpeg_missing": "Pas trouvé",
|
||||
"ffmpeg_current": "Chemin actuel",
|
||||
"ffmpeg_desc": "Définissez un chemin ffmpeg personnalisé si la détection automatique échoue.",
|
||||
"ffmpeg_saved": "Ensemble de chemins FFmpeg – redémarrez le backend pour appliquer."
|
||||
"ffmpeg_saved": "Ensemble de chemins FFmpeg – redémarrez le backend pour appliquer.",
|
||||
"diagnostics_copied": "Diagnostics copiés : collez-les dans votre rapport de problème.",
|
||||
"updater_desktop": "Le programme de mise à jour ne s'exécute que dans l'application de bureau.",
|
||||
"latest_version": "Vous êtes sur la dernière version.",
|
||||
"logs_load_failed": "Échec du chargement des journaux : {{message}}",
|
||||
"clear_frontend_confirm": "Effacer le tampon du journal frontal en mémoire ?",
|
||||
"clear_frontend_title": "Effacer les journaux",
|
||||
"frontend_logs_cleared": "Journaux frontend effacés",
|
||||
"clear_tauri_confirm": "Tronquer les fichiers journaux côté Tauri ? Le système d'exploitation continuera à écrire de nouvelles entrées.",
|
||||
"clear_tauri_title": "Effacer les journaux Tauri",
|
||||
"nothing_to_clear": "Rien à effacer – pas encore de fichier journal Tauri sur le disque.",
|
||||
"cleared_tauri_one": "Fichier journal {{count}} Tauri effacé",
|
||||
"cleared_tauri_other": "Fichiers journaux {{count}} Tauri effacés",
|
||||
"clear_tauri_failed": "Échec de la suppression des journaux Tauri : {{message}}",
|
||||
"clear_backend_confirm": "Effacer le runtime backend + les journaux de crash ? Cela ne peut pas être annulé.",
|
||||
"clear_backend_title": "Effacer les journaux",
|
||||
"backend_logs_cleared": "Journaux back-end effacés",
|
||||
"clear_backend_failed": "Échec de la suppression des journaux",
|
||||
"copy_failed": "Échec de la copie : {{message}}",
|
||||
"update_check_failed": "Échec de la vérification de la mise à jour : {{message}}",
|
||||
"save_failed": "Échec de l'enregistrement : {{message}}",
|
||||
"clear_failed": "Échec de la suppression : {{message}}",
|
||||
"engine_switched": "{{family}} → {{engine}}",
|
||||
"channel_set_failed": "Échec de la définition du canal : {{message}}",
|
||||
"updater_downloading": "Téléchargement de {{version}}…",
|
||||
"updater_installed": "Installé — relance.",
|
||||
"updater_available_title": "Mise à jour disponible",
|
||||
"updater_available_body": "La version {{version}} est disponible.\n\n{{notes}}\n\nTélécharger et installer maintenant ?",
|
||||
"updater_notes_fallback": "Voir les notes de version sur GitHub.",
|
||||
"shortcut_load_failed": "Impossible de charger le raccourci : {{message}}",
|
||||
"shortcut_set": "Raccourci de dictée défini sur {{shortcut}}",
|
||||
"shortcut_register_failed": "Impossible de s'inscrire : {{message}}",
|
||||
"shortcut_reset": "Réinitialiser aux valeurs par défaut",
|
||||
"shortcut_reset_failed": "Échec de la réinitialisation : {{message}}"
|
||||
},
|
||||
"bootstrap": {
|
||||
"title": "OmniVoice Studio",
|
||||
@@ -70,7 +98,13 @@
|
||||
"suggest_lang": "Passer au Français ?",
|
||||
"select_lang": "Langue :",
|
||||
"lines_one": "Ligne {{count}}",
|
||||
"lines_other": "{{count}} lignes"
|
||||
"lines_other": "{{count}} lignes",
|
||||
"region_global": "Mondial (direct)",
|
||||
"region_china": "Chine (miroir)",
|
||||
"region_russia": "Russie (miroir)",
|
||||
"region_restricted": "Restreint (miroir)",
|
||||
"unknown_error": "Erreur inconnue",
|
||||
"retrying": "Nouvelle tentative…"
|
||||
},
|
||||
"stories": {
|
||||
"title": "Éditeur d'histoires",
|
||||
@@ -159,7 +193,17 @@
|
||||
"reload": "Forcer le rechargement de l'interface utilisateur",
|
||||
"backend": "Back-end",
|
||||
"frontend": "Front-end",
|
||||
"tauri": "Taureau"
|
||||
"tauri": "Taureau",
|
||||
"cancelOp": "Annuler l'opération",
|
||||
"dismiss": "Rejeter",
|
||||
"dismissStatus": "Statut de rejet",
|
||||
"search": "Rechercher…",
|
||||
"no_matches": "Aucune correspondance",
|
||||
"recent_and_popular": "Récents et populaires",
|
||||
"popular_label": "Populaire",
|
||||
"showing_of": "Montrant {{shown}} sur {{total}}. Tapez pour rechercher…",
|
||||
"yes": "Oui",
|
||||
"no": "Non"
|
||||
},
|
||||
"launchpad": {
|
||||
"greeting": "bonjour",
|
||||
@@ -181,7 +225,16 @@
|
||||
"locked": "VERROUILLÉ",
|
||||
"open": "Ouvert",
|
||||
"try_it": "Essayez-le",
|
||||
"audio_only": "Audio uniquement"
|
||||
"audio_only": "Audio uniquement",
|
||||
"stories_title": "Histoires",
|
||||
"stories_desc": "Livres audio multi-voix : lancez vos personnages, insérez un script, exportez par chapitre.",
|
||||
"gallery_title": "Galerie vocale",
|
||||
"gallery_desc": "Parcourez les voix prêtes à l’emploi par accent, âge et style – aucune configuration.",
|
||||
"transcripts_title": "Transcriptions",
|
||||
"transcripts_desc": "Transformez l'audio ou la vidéo en texte modifiable et consultable, dans 646 langues.",
|
||||
"recent_files": "Fichiers récents",
|
||||
"view_all_files": "Afficher tous les fichiers",
|
||||
"file": "Fichier"
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "Invite",
|
||||
@@ -272,11 +325,6 @@
|
||||
"outputs": "Sorties",
|
||||
"crash_log": "Journal des incidents",
|
||||
"update_endpoint": "Mettre à jour le point de terminaison",
|
||||
"update_channel": "Canal de mise à jour",
|
||||
"channel_stable": "Stable",
|
||||
"channel_preview": "Aperçu",
|
||||
"channel_set": "Canal de mise à jour réglé sur {{channel}}",
|
||||
"channel_preview_hint": "L'aperçu suit la dernière version de main : fonctionnalités plus récentes, moins testées. Revient à la version stable si une version stable est plus avancée.",
|
||||
"yes": "oui",
|
||||
"no": "non",
|
||||
"web_preview": "aperçu Web",
|
||||
@@ -285,7 +333,12 @@
|
||||
"copy_diagnostics": "Copier les diagnostics",
|
||||
"github": "OmniVoice sur GitHub",
|
||||
"model_card": "Carte modèle",
|
||||
"commercial_license": "Licence commerciale"
|
||||
"commercial_license": "Licence commerciale",
|
||||
"update_channel": "Canal de mise à jour",
|
||||
"channel_stable": "Stable",
|
||||
"channel_preview": "Aperçu",
|
||||
"channel_set": "Canal de mise à jour réglé sur {{channel}}",
|
||||
"channel_preview_hint": "L'aperçu suit la dernière version de main : fonctionnalités plus récentes, moins testées. Revient à la version stable si une version stable est plus avancée."
|
||||
},
|
||||
"privacy": {
|
||||
"desc": "Tout fonctionne sur <1>cette machine</1>. Votre audio, vidéo et transcriptions ne quittent jamais votre ordinateur, sauf si vous utilisez explicitement un traducteur en ligne (Google, DeepL, etc.) ou si vous poussez vers HuggingFace.",
|
||||
@@ -341,7 +394,30 @@
|
||||
"unavailable": "indisponible",
|
||||
"use": "Utiliser",
|
||||
"loading": "Chargement des moteurs…",
|
||||
"refresh": "Actualiser"
|
||||
"refresh": "Actualiser",
|
||||
"matrixTitle": "Matrice de compatibilité des moteurs",
|
||||
"loadFailed": "Échec du chargement des moteurs : {{message}}",
|
||||
"couldNotLoad": "Impossible de charger les moteurs : {{message}}",
|
||||
"retry": "Réessayer",
|
||||
"activeEngine": "Actif {{family}} : {{engine}}",
|
||||
"engineCompatLabel": "Compatibilité moteur {{family}}",
|
||||
"active": "actif",
|
||||
"whyUnavailable": "Pourquoi indisponible ?",
|
||||
"lastError": "Dernière erreur : {{error}}",
|
||||
"installedAndReady": "Installé et prêt",
|
||||
"notInstalled": "Non installé",
|
||||
"available": "Disponible",
|
||||
"subprocessTitle": "S'exécute dans son propre sous-processus + venv",
|
||||
"inProcessTitle": "S'exécute dans le processus OmniVoice Python",
|
||||
"testEngine": "Moteur de test",
|
||||
"testing": "Test…",
|
||||
"recheck": "Revérifier",
|
||||
"rechecking": "Revérification…",
|
||||
"latencyMs": "{{ms}} ms",
|
||||
"failed": "échoué",
|
||||
"acceptLicense": "Accepter la licence",
|
||||
"noBackends": "Aucun backend enregistré.",
|
||||
"switch_failed": "Impossible de changer de moteur"
|
||||
},
|
||||
"capture": {
|
||||
"desc": "Les raccourcis clavier globaux ne fonctionnent que dans l'application de bureau. L'interface utilisateur Web utilise un raccourci <1>Ctrl+Shift+Espace</1> sur la page lorsque la fenêtre a le focus.",
|
||||
@@ -353,13 +429,55 @@
|
||||
"record_shortcut": "Raccourci d'enregistrement",
|
||||
"recording": "Enregistrement…",
|
||||
"save": "Enregistrer",
|
||||
"reset_default": "Réinitialiser aux valeurs par défaut"
|
||||
"reset_default": "Réinitialiser aux valeurs par défaut",
|
||||
"listening_label": "A l'écoute…",
|
||||
"transcribing_label": "Transcription…",
|
||||
"pasted": "Collé",
|
||||
"no_speech": "Aucune parole détectée",
|
||||
"mic_denied": "Accès au micro refusé",
|
||||
"mic_denied_toast": "Accès au microphone refusé. {{hint}}",
|
||||
"mic_hint_mac": "macOS : ouvrez Paramètres système → Confidentialité et sécurité → Microphone et activez OmniVoice.",
|
||||
"mic_hint_windows": "Windows : ouvrez Paramètres → Confidentialité et sécurité → Microphone et autorisez OmniVoice.",
|
||||
"mic_hint_linux": "Linux : vérifiez que votre utilisateur est dans le groupe audio et que WebView a accès au micro.",
|
||||
"transcription_failed": "Échec de la transcription : {{message}}"
|
||||
},
|
||||
"logs": {
|
||||
"no_tauri_log": "Pas encore de connexion Tauri sur le disque - lancez-le via la version de bureau pour en produire un",
|
||||
"empty_frontend": "Aucune entrée de console frontale capturée pour l'instant. Interagissez avec l'application : chaque console.* apparaîtra ici.",
|
||||
"empty_tauri": "Aucun journal Tauri disponible. Fonctionne uniquement dans le shell du bureau.",
|
||||
"empty_backend": "Le journal d'exécution est vide. L'activité apparaîtra ici au fur et à mesure que le backend l'enregistrera."
|
||||
"empty_backend": "Le journal d'exécution est vide. L'activité apparaîtra ici au fur et à mesure que le backend l'enregistrera.",
|
||||
"title": "Journaux",
|
||||
"source_backend": "Back-end",
|
||||
"source_frontend": "Front-end",
|
||||
"source_tauri": "Taureau",
|
||||
"expand": "Développer les journaux",
|
||||
"collapse": "Réduire les journaux",
|
||||
"expand_aria": "Développer le panneau des journaux",
|
||||
"collapse_aria": "Réduire le panneau des journaux",
|
||||
"drag_resize": "Faites glisser pour redimensionner",
|
||||
"refresh": "Actualiser",
|
||||
"refresh_aria": "Actualiser les journaux",
|
||||
"copy_visible": "Copier le journal visible",
|
||||
"copy_visible_aria": "Copier le journal visible",
|
||||
"clear": "Effacer",
|
||||
"clear_aria": "Effacer le journal",
|
||||
"report_issue": "Signaler un problème (copie de diagnostic)",
|
||||
"report_issue_aria": "Signaler un problème",
|
||||
"close": "Fermer",
|
||||
"close_aria": "Fermer le panneau des journaux",
|
||||
"join_discord": "Rejoignez notre Discord",
|
||||
"join_discord_aria": "Rejoignez notre communauté Discord",
|
||||
"support_project": "Soutenez ce projet",
|
||||
"support_project_aria": "Soutenez ce projet",
|
||||
"empty_frontend_short": "Pas encore de sortie de console frontale.",
|
||||
"empty_lines": "Aucune ligne.",
|
||||
"all_clear": "✅ Tout est clair – aucun problème détecté",
|
||||
"log_cleared": "Journal {{source}} effacé",
|
||||
"clear_failed": "Échec de la suppression : {{message}}",
|
||||
"log_copied": "Journal {{source}} copié",
|
||||
"copy_failed": "Échec de la copie : {{message}}",
|
||||
"report_copied": "Rapport de diagnostic copié : collez-le dans un problème GitHub.",
|
||||
"report_failed": "Échec du rapport : {{message}}"
|
||||
},
|
||||
"voice_profile": {
|
||||
"test_text": "Bonjour, c'est un test de cette voix.",
|
||||
@@ -527,7 +645,17 @@
|
||||
"install_already": "{{engine}} était déjà installé",
|
||||
"install_ok": "{{engine}} installé",
|
||||
"install_failed": "Échec de l'installation : {{message}}",
|
||||
"prep_elapsed": "{{time}} écoulé"
|
||||
"prep_elapsed": "{{time}} écoulé",
|
||||
"add_language": "Ajouter une langue",
|
||||
"search_languages": "Rechercher des langues…",
|
||||
"languages_selected_one": "{{count}} langue sélectionnée",
|
||||
"languages_selected_other": "{{count}} langues sélectionnées",
|
||||
"more_to_narrow": "+{{count}} plus — tapez pour affiner",
|
||||
"no_matches": "Aucune correspondance",
|
||||
"diagnostic_copied": "Diagnostic copié",
|
||||
"copy_failed": "Échec de la copie",
|
||||
"open_docs": "Ouvrir des documents",
|
||||
"copy_diagnostic": "Copier le diagnostic"
|
||||
},
|
||||
"glossary": {
|
||||
"title": "Glossaire",
|
||||
@@ -602,7 +730,17 @@
|
||||
"more_actions_title": "Plus de propositions",
|
||||
"speaker_pick": "Choisissez…",
|
||||
"speaker_title_detected": "Haut-parleur : choisissez parmi ceux détectés ou saisissez un nom personnalisé",
|
||||
"speaker_title_custom": "Haut-parleur : saisissez un nom (aucun clone de diarisation détecté)"
|
||||
"speaker_title_custom": "Haut-parleur : saisissez un nom (aucun clone de diarisation détecté)",
|
||||
"time_edit_title": "Cliquez pour modifier l'heure de début (m:ss.s). Entrez pour valider, Esc pour annuler.",
|
||||
"fit_fits": "Convient",
|
||||
"fit_fits_title": "L'audio à débit naturel s'adapte à l'intérieur de la fente.",
|
||||
"fit_overflows": "Débordements +{{seconds}}s",
|
||||
"fit_overflows_title": "Le texte traduit était plus long que l'emplacement d'origine de {{seconds}}s. L’audio était dur ; raccourcissez le texte ou réglez le timing sur « Étirer la vidéo ».",
|
||||
"fit_stretched": "Vidéo {{ratio}}×",
|
||||
"fit_stretched_title": "Mode vidéo extensible : la vidéo de ce segment a été ralentie à {{ratio}}× pour s'adapter à l'audio naturel du doublage.",
|
||||
"fit_compressed_title": "L'audio TTS représente {{pct}}% de l'emplacement — fortement compressé.",
|
||||
"fit_audio_title": "L'audio s'adapte à l'intérieur de la fente.",
|
||||
"fit_ratio_title": "L'audio TTS représente {{pct}}% de l'emplacement."
|
||||
},
|
||||
"voice": {
|
||||
"personality": "Personnalité",
|
||||
@@ -697,10 +835,24 @@
|
||||
"status_running": "courir",
|
||||
"status_done": "fait",
|
||||
"status_failed": "échoué",
|
||||
"status_cancelled": "annulé"
|
||||
"status_cancelled": "annulé",
|
||||
"add_to_queue_title": "Ajouter des vidéos à la file d'attente",
|
||||
"drop_hint_text": "Déposez les fichiers vidéo ici ou cliquez pour parcourir",
|
||||
"drop_formats": "MP4 · MOV · MKV · WEBM",
|
||||
"files_kicker": "FICHIERS ({{count}})",
|
||||
"file_size_mb": "{{size}} Mo",
|
||||
"target_languages": "LANGUES CIBLES",
|
||||
"voice_kicker": "VOIX",
|
||||
"default_option": "Par défaut",
|
||||
"clone_profiles": "Cloner des profils",
|
||||
"presets": "Préréglages",
|
||||
"preserve_bg": "Préserver l'audio de fond (musique/FX)",
|
||||
"estimate": "{{videos}} vidéo(s) × {{langs}} langue(s) = {{jobs}} travail(s)",
|
||||
"select_files_langs": "Sélectionnez les fichiers et les langues",
|
||||
"add_to_queue": "Ajouter à la file d'attente"
|
||||
},
|
||||
"gallery": {
|
||||
"title": "Galerie",
|
||||
"title": "OmniVoice Galerie",
|
||||
"search_placeholder": "Rechercher sur YouTube…",
|
||||
"all_voices": "Toutes les voix ({{count}})",
|
||||
"no_voices": "Pas encore de voix",
|
||||
@@ -715,6 +867,14 @@
|
||||
"youtube_results": "Résultats YouTube ({{count}})",
|
||||
"clone_profile": "Cloner le profil",
|
||||
"crop_audio": "Recadrer l'audio",
|
||||
"cat_disney": "Disney",
|
||||
"cat_anime": "Anime",
|
||||
"cat_marvel": "Marvel/DC",
|
||||
"cat_celebs": "Célébrités",
|
||||
"cat_politicians": "Politiciens",
|
||||
"cat_news": "Présentateurs de nouvelles",
|
||||
"cat_gaming": "Jeux",
|
||||
"cat_books": "Livres/Films",
|
||||
"subtitle": "Des centaines de voix prêtes à l'emploi : choisissez-en une et c'est parti.",
|
||||
"zone_archetypes": "Archétypes",
|
||||
"zone_imports": "Mes importations",
|
||||
@@ -819,7 +979,6 @@
|
||||
"badge": "Licence commerciale",
|
||||
"hero_title": "Expédier les voix de l'IA en production",
|
||||
"hero_desc": "OmniVoice Studio est disponible sous la licence Functional Source (FSL). La plupart des utilisateurs peuvent évaluer, prototyper et même déployer en interne sans accord commercial. Vous n’avez besoin d’une licence commerciale que si vous créez un produit ou un service concurrent, ou si votre cas d’utilisation dépasse les limites du FSL.",
|
||||
"hero_note": "La création d'un produit ou d'un service concurrent ou son déploiement à grande échelle (par exemple, la fourniture d'une API payante à l'utilisation) nécessite une licence commerciale. Niveaux de tarification à venir – contactez-nous en attendant.",
|
||||
"why_title": "Pourquoi les entreprises choisissent OmniVoice",
|
||||
"pricing_title": "Tarifs",
|
||||
"faq_title": "Questions courantes",
|
||||
@@ -839,7 +998,8 @@
|
||||
"benefit_source": "Noyau disponible à la source",
|
||||
"benefit_source_desc": "Visibilité totale sur la pile. Auditez, forkez et adaptez selon les termes de la licence.",
|
||||
"benefit_lang": "646 langues",
|
||||
"benefit_lang_desc": "Transcrivez, traduisez et doublez dans 646 langues avec une qualité humaine."
|
||||
"benefit_lang_desc": "Transcrivez, traduisez et doublez dans 646 langues avec une qualité humaine.",
|
||||
"hero_note": "L’utilisation interne – même à grande échelle – est gratuite dans le cadre du FSL ; une licence commerciale n'est requise que pour proposer OmniVoice à des tiers en tant que produit ou service concurrent (une API hébergée ou payante à l'utilisation, une application revendue ou en marque blanche). Les niveaux de tarification arriveront bientôt – contactez-nous en attendant."
|
||||
},
|
||||
"exportModal": {
|
||||
"export": "Exporter",
|
||||
@@ -928,7 +1088,479 @@
|
||||
"dubbing_title": "Voir le doublage en action",
|
||||
"dubbing_sync": "Lecture synchronisée",
|
||||
"dubbing_picker": "Essayez une autre langue :",
|
||||
"dubbing_cta": "Exécutez ceci sur votre propre vidéo →"
|
||||
"dubbing_cta": "Exécutez ceci sur votre propre vidéo →",
|
||||
"dubbing_loading": "Chargement de la démo de doublage…",
|
||||
"dubbing_dismiss": "Ignorer la démo de doublage",
|
||||
"original_tag": "originale",
|
||||
"dubbed_tag": "doublé",
|
||||
"script_conversational": "Conversationnel",
|
||||
"script_technical": "Vocabulaire technique",
|
||||
"script_french": "Non anglais (français)",
|
||||
"aria_pause": "Pause {{label}}",
|
||||
"aria_hear": "Écoutez {{label}}",
|
||||
"aria_replay": "Rejouer {{label}} via le transcripteur"
|
||||
},
|
||||
"direction": {
|
||||
"title": "Direction pour le segment #{{id}}",
|
||||
"desc": "Dites au pipeline ce que devrait ressentir cette ligne. L'anglais simple fonctionne : le système mappe vos mots sur une taxonomie stable (énergie/émotion/rythme/intimité/formalité), puis enchaîne la taxonomie via la traduction cinématographique, le TTS et l'ajustement par fente.",
|
||||
"label": "Direction",
|
||||
"lineHint": "Ligne : \"{{text}}\"",
|
||||
"placeholder": "par ex. urgent et surpris / chaleureux, plein d'espoir / murmuré, intime",
|
||||
"previewParse": "Aperçu de l'analyse",
|
||||
"previewFailed": "Échec de l'aperçu : {{message}}",
|
||||
"clear": "Effacer",
|
||||
"cancel": "Annuler",
|
||||
"saveDirection": "Enregistrer la direction",
|
||||
"ttsInstruct": "Instruire TTS :",
|
||||
"nothingParsed": "— (rien d'analysé)",
|
||||
"translateHint": "Indice de traduction :",
|
||||
"rateBias": "Biais de taux :",
|
||||
"speedsUp": "accélère",
|
||||
"slowsDown": "ralentit",
|
||||
"taxonomyTokens": "jetons de taxonomie"
|
||||
},
|
||||
"errors": {
|
||||
"title": "Cet onglet a rencontré un problème.",
|
||||
"desc": "Ne vous inquiétez pas, le reste de l'application fonctionne toujours. Vous pouvez changer d'onglet ou réessayer ci-dessous.",
|
||||
"tryAgain": "Réessayez",
|
||||
"openDocs": "Ouvrir la documentation pour cette erreur"
|
||||
},
|
||||
"keyboard": {
|
||||
"title": "Raccourcis clavier",
|
||||
"footer": "Appuyez sur <1>?</1> à tout moment pour l'ouvrir.",
|
||||
"or": "ou",
|
||||
"nav": "Navigation",
|
||||
"nav_cheatsheet": "Afficher cette aide-mémoire",
|
||||
"nav_closeModal": "Fermer modal / annuler",
|
||||
"nav_save": "Enregistrer le projet/commettre le trim",
|
||||
"segmentEditor": "Editeur de segments",
|
||||
"seg_split": "Diviser le segment au niveau du curseur",
|
||||
"seg_merge": "Fusionner avec le segment suivant",
|
||||
"seg_undo": "Annuler",
|
||||
"seg_redo": "Refaire",
|
||||
"seg_click": "Action primaire",
|
||||
"seg_shiftClick": "Sélection de plage",
|
||||
"trimmer": "Tondeuse audio",
|
||||
"trim_playPause": "Aperçu de la lecture/pause",
|
||||
"trim_nudgeStart": "Poignée de démarrage par poussée",
|
||||
"trim_nudgeEnd": "Poignée d'extrémité poussée",
|
||||
"trim_fineNudge": "Bon coup de pouce",
|
||||
"trim_coarseNudge": "Coup de pouce grossier",
|
||||
"trim_zoomIn": "Zoom avant/arrière",
|
||||
"trim_fitAll": "Tout adapter/Sélection d'ajustement",
|
||||
"trim_confirm": "Confirmer la coupe",
|
||||
"dub": "Doublage",
|
||||
"dub_generate": "Générer un doublage",
|
||||
"dub_sidebar": "Basculer la barre latérale"
|
||||
},
|
||||
"network": {
|
||||
"sharing_on_title": "Partage sur — cliquez pour plus de détails",
|
||||
"share_on_network": "Partagez sur votre réseau",
|
||||
"switching": "Commutation…",
|
||||
"network": "Réseau",
|
||||
"local": "Locale",
|
||||
"share_confirm_title": "Partager sur votre réseau ?",
|
||||
"share_confirm_hint": "Les autres appareils connectés à votre réseau Wi-Fi/Ethernet pourront accéder à OmniVoice en utilisant le code PIN d'accès affiché une fois allumé.",
|
||||
"enabling": "Activation…",
|
||||
"enable": "Activer",
|
||||
"shared_title": "Partagé sur votre réseau",
|
||||
"no_interface": "Aucune interface réseau accessible – connectez-vous au Wi-Fi/Ethernet.",
|
||||
"copy_link": "Copier le lien",
|
||||
"open_in_browser": "Ouvrir dans le navigateur",
|
||||
"qr_alt": "QR pour {{ip}}",
|
||||
"pin": "NIP :",
|
||||
"stop_sharing": "Arrêter de partager",
|
||||
"copied": "Copié",
|
||||
"enable_error": "Impossible d'activer le partage : {{message}}",
|
||||
"disable_error": "Impossible de désactiver : {{message}}"
|
||||
},
|
||||
"readiness": {
|
||||
"checking_system": "Système de vérification…",
|
||||
"all_ready": "Tous les systèmes sont prêts",
|
||||
"system_readiness": "État de préparation du système",
|
||||
"asr_model": "Modèle ASR",
|
||||
"loaded_ready": "Chargé et prêt",
|
||||
"loading_first_run": "Chargement… (cela peut prendre 1 à 2 minutes lors de la première exécution)",
|
||||
"failed_to_load": "Échec du chargement",
|
||||
"not_loaded_yet": "Pas encore chargé – se chargera lors de la première transcription",
|
||||
"error_check_logs": "Erreur : {{error}}. Vérifiez les journaux et essayez de redémarrer.",
|
||||
"check_logs_restart": "Vérifiez les journaux pour détecter les erreurs de chargement du modèle. Essayez de redémarrer.",
|
||||
"llm_cinematic": "LLM (Cinématique)",
|
||||
"llm_configure": "Configurer TRANSLATE_BASE_URL pour la qualité de traduction cinématographique",
|
||||
"llm_set_env": "Définissez les variables d'environnement TRANSLATE_BASE_URL et TRANSLATE_API_KEY. Fonctionne avec Ollama, OpenAI, LM Studio, etc.",
|
||||
"llm_optional": "Facultatif : définissez TRANSLATE_BASE_URL pour la qualité cinématographique"
|
||||
},
|
||||
"license": {
|
||||
"title": "Supertonic-3 — Acceptation de la licence",
|
||||
"intro": "Supertonic-3 est expédié sous deux licences distinctes. Veuillez vérifier les deux avant d'activer le moteur.",
|
||||
"sdk_heading": "Code SDK · MIT",
|
||||
"sdk_desc": "Le SDK d'inférence Python (supertonic) est sous licence MIT. Utilisation permissive, y compris commerciale.",
|
||||
"read_mit": "Lire la licence MIT →",
|
||||
"model_heading": "Poids des modèles · OpenRAIL-M",
|
||||
"model_desc": "Les poids du modèle Supertonic-3 sont publiés sous la licence OpenRAIL-M. Cette licence restreint l'utilisation à des fins non malveillantes — consultez la licence liée pour l'ensemble complet des restrictions basées sur l'utilisation.",
|
||||
"read_openrail": "Lire la licence OpenRAIL-M →",
|
||||
"footer": "Cliquer sur Accepter enregistre votre acceptation dans les paramètres locaux d'OmniVoice et active le moteur. Votre acceptation est stockée sur cette machine uniquement — rien n'est signalé à Supertone Inc. ou à un tiers.",
|
||||
"saving": "Sauvegarde…",
|
||||
"accept": "Accepter",
|
||||
"accepted_toast": "Licence Supertonic-3 acceptée.",
|
||||
"accept_error": "Échec de l'enregistrement de l'acceptation de la licence : {{message}}"
|
||||
},
|
||||
"voicePreview": {
|
||||
"title": "Aperçu vocal",
|
||||
"close": "Fermer l'aperçu",
|
||||
"default_text": "Bonjour ! Ceci est un aperçu de la façon dont je sonne avec cette voix.",
|
||||
"default_voice": "Voix par défaut",
|
||||
"clone_profiles": "Cloner des profils",
|
||||
"designed_voices": "Voix conçues",
|
||||
"presets": "Préréglages",
|
||||
"placeholder": "Tapez quelque chose à entendre…",
|
||||
"stop": "Arrêter",
|
||||
"regenerate": "Régénérer",
|
||||
"preview": "Aperçu",
|
||||
"hint": "8 étapes · aperçu rapide"
|
||||
},
|
||||
"header": {
|
||||
"kicker_studio": "Atelier",
|
||||
"kicker_library": "Bibliothèque",
|
||||
"kicker_preferences": "Préférences",
|
||||
"kicker_licensing": "Licence",
|
||||
"label_launchpad": "Barre de lancement",
|
||||
"label_clone": "Clonage vocal",
|
||||
"label_design": "Conception vocale",
|
||||
"label_dub": "Doublage",
|
||||
"label_projects": "OmniDrive",
|
||||
"label_gallery": "Galerie",
|
||||
"label_transcriptions": "Transcriptions",
|
||||
"label_settings": "Paramètres",
|
||||
"label_enterprise": "Licence commerciale",
|
||||
"status_ready": "Prêt",
|
||||
"status_loading": "Chargement…",
|
||||
"status_idle": "Inactif",
|
||||
"memory_management": "Gestion de la mémoire",
|
||||
"flush": "Rincer",
|
||||
"loaded_models": "Modèles chargés",
|
||||
"no_models": "Aucun modèle chargé",
|
||||
"unload": "Décharger",
|
||||
"flush_caches": "Vider les caches",
|
||||
"unload_all_flush": "Tout décharger + rincer"
|
||||
},
|
||||
"sidebar": {
|
||||
"tab_drive": "Conduire",
|
||||
"tab_history": "Histoire",
|
||||
"tab_exports": "Exportations",
|
||||
"save_project": "Enregistrer le projet de doublage",
|
||||
"save_new_project": "Enregistrer en tant que nouveau projet de doublage",
|
||||
"dub_projects": "Projets de doublage",
|
||||
"voice_clones": "Clones vocaux",
|
||||
"designed_voices": "Voix conçues",
|
||||
"no_dub_projects": "Aucun projet de doublage enregistré",
|
||||
"no_dub_hint": "Téléchargez une vidéo et cliquez sur Enregistrer pour conserver votre travail.",
|
||||
"no_clones": "Pas encore de clones de voix",
|
||||
"no_clones_hint": "Enregistrez ou téléchargez de l'audio, puis cliquez sur Enregistrer en tant que profil vocal.",
|
||||
"no_designs": "Aucune voix conçue pour l'instant",
|
||||
"no_designs_hint": "Générez une voix et enregistrez-la depuis l'historique.",
|
||||
"history_subtitle": "Historique des générations · Stocké dans SQLite",
|
||||
"no_history": "Pas d'historique de génération",
|
||||
"no_history_hint": "Synthétisez de l'audio ou doublez une vidéo : les résultats apparaîtront ici.",
|
||||
"clear_history": "Effacer l'historique",
|
||||
"clear_confirm": "Effacer tous les éléments de l'historique {{count}} ? Cela ne peut pas être annulé.",
|
||||
"history_cleared": "Historique effacé",
|
||||
"recent_exports": "Exportations récentes",
|
||||
"no_exports": "Aucune sortie téléchargée",
|
||||
"no_exports_hint": "Exportez un fichier via Tauri pour le voir suivi ici.",
|
||||
"show_in_folder": "Afficher dans le dossier",
|
||||
"open": "Ouvert",
|
||||
"select": "Sélectionnez",
|
||||
"try_voice": "Essayez",
|
||||
"consistent": "cohérent",
|
||||
"locked": "Verrouillé",
|
||||
"clone_label": "Cloner",
|
||||
"design_label": "Conception",
|
||||
"dub_label": "Doublage",
|
||||
"save_label": "Enregistrer",
|
||||
"lock_identity": "Verrouiller l'identité vocale",
|
||||
"in_folder": "en {{folder}}"
|
||||
},
|
||||
"trimmer": {
|
||||
"title": "Couper l'audio de référence",
|
||||
"decoding": "Décodage audio…",
|
||||
"meta_length": "Longueur {{duration}} · {{sampleRate}} Hz",
|
||||
"meta_rendering": "rendu de la forme d'onde {{percent}}%",
|
||||
"keyboard_hint": "scroll = zoom · maj+scroll = panoramique · alt+glisser = panoramique · ⏐ ⟵ ⟶ ⏐ les touches ajustent les poignées",
|
||||
"zoom_in": "Zoomer (+)",
|
||||
"zoom_out": "Zoom arrière (-)",
|
||||
"fit_all": "Convient à tous (Accueil)",
|
||||
"fit_selection": "Ajuster la sélection (Fin)",
|
||||
"fit_sel_btn": "AJUSTEMENT SÉL.",
|
||||
"view_range": "Afficher {{start}} → {{end}} ({{duration}})",
|
||||
"start_label": "Commencer",
|
||||
"end_label": "Fin",
|
||||
"length_label": "Longueur",
|
||||
"too_long": ">{{max}}s",
|
||||
"too_short": "trop court",
|
||||
"length_ok": "ok",
|
||||
"loop_preview": "Aperçu de la boucle",
|
||||
"pause": "Pause",
|
||||
"preview_selection": "Aperçu de la sélection",
|
||||
"play_hint": "Espace pour jouer · Enter pour confirmer · Esc pour annuler",
|
||||
"cancel": "Annuler",
|
||||
"use_trimmed": "Utiliser coupé",
|
||||
"decode_failed": "Échec du décodage : {{message}}",
|
||||
"playback_failed": "Échec de la lecture : {{message}}",
|
||||
"audio_load_failed": "Échec du chargement audio",
|
||||
"unit_seconds": "s"
|
||||
},
|
||||
"casting": {
|
||||
"title": "Casting de haut-parleurs",
|
||||
"auto_assign_title": "Attribuer automatiquement les voix des clones de haut-parleurs extraits",
|
||||
"auto_cast": "Diffusion automatique",
|
||||
"all_cast": "Tous les acteurs",
|
||||
"segments_count": "{{count}} segments",
|
||||
"assign_voice": "Attribuer une voix…",
|
||||
"from_video": "🎤 À partir de la vidéo ({{name}})",
|
||||
"no_profiles": "Aucun profil vocal n'a encore été enregistré.",
|
||||
"preview_voice": "Aperçu de la voix"
|
||||
},
|
||||
"checkpoint": {
|
||||
"asr_title": "Transcriptions prêtes",
|
||||
"asr_cta": "Traduire",
|
||||
"asr_hint": "Corrigez toutes les erreurs ASR maintenant : une diction serrée enregistre les tentatives TTS plus tard.",
|
||||
"translate_title": "Traductions prêtes",
|
||||
"translate_cta": "Générer un doublage",
|
||||
"translate_hint": "Parcourez le texte cible. Les lignes trop longues sont accélérées ; vous pouvez également modifier directement.",
|
||||
"done_title": "Doublage terminé",
|
||||
"done_hint": "Examinez les ratios de synchronisation et de synchronisation. Ajustez n'importe quelle ligne et appuyez sur \"Regen changé\" pour une restauration partielle rapide.",
|
||||
"segment_one": "{{count}} segment",
|
||||
"segment_other": "{{count}} segments",
|
||||
"dismiss_title": "Ignorer - ne réapparaîtra pas pour cette étape jusqu'au rechargement"
|
||||
},
|
||||
"compare": {
|
||||
"title": "Comparaison vocale A/B",
|
||||
"close": "Comparaison étroite",
|
||||
"desc": "Comparez deux voix côte à côte pour prendre des décisions de casting. L'application reste interactive.",
|
||||
"test_phrase": "Phrase de test",
|
||||
"voice_a": "Voix A",
|
||||
"voice_b": "Voix B",
|
||||
"select_voice": "— Sélectionnez la voix —",
|
||||
"preset_suffix": "(Préréglé)",
|
||||
"no_audio": "Pas encore de son",
|
||||
"close_btn": "Fermer",
|
||||
"comparing": "En comparant…",
|
||||
"compare_btn": "Comparez",
|
||||
"preparing_voice": "Préparation de la voix...",
|
||||
"generating_voice_a": "Génération de la voix A...",
|
||||
"generating_voice_b": "Génération de la voix B...",
|
||||
"comparison_complete": "Comparaison terminée !",
|
||||
"play_failed": "Échec de la lecture : {{message}}"
|
||||
},
|
||||
"models": {
|
||||
"hf_token_set_toast": "Ensemble de jetons HuggingFace – téléchargements plus rapides activés",
|
||||
"hf_token_save_failed": "Échec de l'enregistrement du jeton",
|
||||
"install_started": "Installation démarrée – progression dans la ligne",
|
||||
"delete_confirm": "Supprimer {{repoId}} ? Vous pourrez le réinstaller plus tard.",
|
||||
"delete_confirm_title": "Supprimer le modèle",
|
||||
"deleted": "Supprimé {{repoId}}",
|
||||
"reinstall_confirm": "Réinstaller {{repoId}} ? Cela supprimera la copie actuelle et la téléchargera à nouveau.",
|
||||
"reinstall_confirm_title": "Réinstaller le modèle",
|
||||
"reinstalling": "Réinstallation",
|
||||
"recommended_installed": "Les modèles recommandés sont déjà installés.",
|
||||
"started_downloading_one": "J'ai commencé à télécharger le modèle {{count}}",
|
||||
"started_downloading_other": "J'ai commencé à télécharger les modèles {{count}}",
|
||||
"install_failed": "Échec de l'installation : {{message}}",
|
||||
"removing_cached": "Suppression des révisions mises en cache…",
|
||||
"resolving_metadata": "Résolution des métadonnées du dépôt",
|
||||
"retry_attempt": "Nouvelle tentative {{attempt}} — {{error}}",
|
||||
"connecting_hf": "Connexion à HuggingFace…",
|
||||
"resolving_files_one": "Résolution du fichier {{count}}…",
|
||||
"resolving_files_other": "Résolution des fichiers {{count}}…",
|
||||
"resolving_files_active": "Résolution de {{count}} fichier(s)… · {{file}}",
|
||||
"measuring": "mesurer…",
|
||||
"files_progress": "Fichiers {{done}}/{{total}}",
|
||||
"install_error": "Échec de l'installation : {{error}}",
|
||||
"view_on_hf": "Voir sur HuggingFace",
|
||||
"install_btn": "Installer",
|
||||
"reinstall_btn": "Réinstaller",
|
||||
"downloading": "téléchargement",
|
||||
"deleting": "suppression",
|
||||
"working": "travailler",
|
||||
"installed": "installé",
|
||||
"not_installed": "non installé",
|
||||
"required_tag": "requis",
|
||||
"delete_btn": "Supprimer",
|
||||
"hf_token_btn": "Jeton HF",
|
||||
"hf_set_title": "Définir le jeton HuggingFace pour des téléchargements plus rapides",
|
||||
"get_token": "Obtenir un jeton →",
|
||||
"reco_installed_for": "Offre groupée recommandée installée pour {{device}}",
|
||||
"reco_for": "Recommandé pour {{device}}",
|
||||
"starting": "Commencer…",
|
||||
"required_size": "Requis ~{{size}} Go",
|
||||
"all_size": "Tous ~{{size}} Go",
|
||||
"req_tag": "demande",
|
||||
"search_placeholder": "Rechercher des modèles…",
|
||||
"search_label": "Rechercher des modèles",
|
||||
"no_matches": "Aucun modèle ne correspond à vos filtres.",
|
||||
"sort_by": "Trier par {{column}}",
|
||||
"column_model": "Modèle",
|
||||
"column_role": "Rôle",
|
||||
"column_size": "Taille",
|
||||
"column_status": "Statut",
|
||||
"ready_badge": "Prêt",
|
||||
"loading_badge": "Chargement…",
|
||||
"idle_badge": "Inactif",
|
||||
"started_downloading_required_one": "Début du téléchargement du modèle requis {{count}}",
|
||||
"started_downloading_required_other": "Début du téléchargement des {{count}} modèles requis"
|
||||
},
|
||||
"enterprise_faq": {
|
||||
"q_internal_tools": "Ai-je besoin d’une licence pour les outils internes ?",
|
||||
"a_internal_tools": "L'utilisation interne par vos employés et sous-traitants est une finalité autorisée en vertu du FSL — aucune licence n'est requise. Une licence commerciale est nécessaire lorsque vous mettez OmniVoice à la disposition de tiers dans le cadre d'un produit ou d'un service concurrent (revente, SaaS hébergé, marque blanche).",
|
||||
"q_try_before": "Puis-je essayer avant de m'engager ?",
|
||||
"a_try_before": "Oui. L'application complète peut être téléchargée et exécutée gratuitement localement pour être évaluée dans le cadre du FSL. Lorsque vous êtes prêt à discuter d'un déploiement commercial, envoyez-nous un e-mail et nous examinerons les détails ensemble.",
|
||||
"q_watermark": "Et le filigrane ?",
|
||||
"a_watermark": "Le filigrane invisible AudioSeal est intégré par défaut. Les titulaires de licence commerciale peuvent le désactiver dans Paramètres → Confidentialité. L’utilisation gratuite/personnelle inclut toujours le filigrane.",
|
||||
"q_apache": "La source devient-elle un jour Apache 2.0 ?",
|
||||
"a_apache": "Oui. Chaque version est automatiquement convertie en licence Apache, version 2.0 au deuxième anniversaire de sa publication. Cela signifie que la version d'aujourd'hui sera Apache 2.0 dans deux ans, aucune action n'est requise de notre part — le FSL le garantit irrévocablement."
|
||||
},
|
||||
"gallery_extra": {
|
||||
"save_prompt": "Saisissez un nom pour ce profil vocal :",
|
||||
"saved_as_profile": "Voix enregistrée en tant que profil !",
|
||||
"save_profile_failed": "Échec de l'enregistrement du profil",
|
||||
"download_failed": "Échec du téléchargement : {{message}}",
|
||||
"trim_load_failed": "Échec du chargement de l'audio à découper : {{message}}",
|
||||
"upload_crop_failed": "Échec de l'importation de la voix tronquée : {{message}}"
|
||||
},
|
||||
"dub_workflow": {
|
||||
"preparing_audio": "Préparation du son…",
|
||||
"preparing_video": "Préparation de la vidéo…",
|
||||
"extracting_audio_scenes": "Extraction de l'audio et des scènes…",
|
||||
"transcribing_audio": "Transcription audio…",
|
||||
"transcription_complete": "Transcription terminée",
|
||||
"upload_cancelled": "Téléchargement annulé",
|
||||
"upload_failed": "Échec du téléchargement : {{message}}",
|
||||
"downloading_video": "Téléchargement de la vidéo…",
|
||||
"ingested": "Ingéré {{url}}",
|
||||
"ingest_cancelled": "Ingestion annulée",
|
||||
"ingest_failed": "Échec de l'ingestion de l'URL : {{message}}",
|
||||
"retry_cancelled": "Nouvelle tentative annulée",
|
||||
"transcription_failed": "Échec de la transcription : {{message}}",
|
||||
"import_srt_no_job": "Téléchargez ou ingérez d’abord une vidéo – il n’y a aucune tâche à laquelle attacher des sous-titres.",
|
||||
"imported_cues": "Cues {{count}} importées de {{file}}",
|
||||
"skipped_malformed": "{{count}} ignoré (mal formé)",
|
||||
"dropped_overlap": "{{count}} supprimé (chevauchement)",
|
||||
"clamped_to_duration": "{{count}} fixé à la longueur du support",
|
||||
"srt_import_failed": "L'importation SRT a échoué",
|
||||
"cleaned_one": "Fragment {{count}} nettoyé",
|
||||
"cleaned_other": "Fragments {{count}} nettoyés",
|
||||
"segments_clean": "Segments déjà propres",
|
||||
"cleanup_failed": "Échec du nettoyage : {{message}}",
|
||||
"cinematic_no_llm": "La qualité cinématographique nécessite un LLM — définissez TRANSLATE_BASE_URL + TRANSLATE_API_KEY (Ollama fonctionne localement). Revenir à Fast.",
|
||||
"translate_errors": "Échec du ou des segments {{errorCount}}/{{totalCount}} : {{firstError}}",
|
||||
"translated_segments": "Segment(s) {{count}} traduit(s) → {{lang}}",
|
||||
"translated_cinematic_suffix": "(Cinématique)",
|
||||
"translation_failed": "Échec de la traduction : {{message}}",
|
||||
"regenerating": "Régénération de {{count}} segment(s)…",
|
||||
"generating_dub": "Génération du doublage…",
|
||||
"generating_progress": "Génération du doublage… {{current}}/{{total}}",
|
||||
"generation_aborted": "Génération avortée.",
|
||||
"dubbing_aborted": "Doublage interrompu",
|
||||
"generation_stream_ended": "Le flux de génération s'est terminé avant la fin",
|
||||
"dub_complete": "Doublage terminé",
|
||||
"stop_failed": "Impossible d'arrêter",
|
||||
"save_first": "Veuillez d'abord cliquer sur « Télécharger et transcrire » afin que la vidéo soit traitée sur le serveur avant de l'enregistrer.",
|
||||
"project_saved": "Projet enregistré",
|
||||
"project_created": "Projet créé",
|
||||
"save_failed": "Échec de l'enregistrement : {{message}}",
|
||||
"opened_project": "Ouvert : {{name}}",
|
||||
"delete_project_confirm": "Supprimer ce projet ? Cela ne peut pas être annulé.",
|
||||
"project_deleted": "Projet supprimé",
|
||||
"delete_history_confirm": "Supprimer cet élément de l'historique ?",
|
||||
"history_deleted": "Élément d'historique supprimé",
|
||||
"restored_state": "État de la génération précédente restauré",
|
||||
"upgrading_preview": "Mise à niveau de {{count}} segments en qualité d'aperçu vers une qualité complète…"
|
||||
},
|
||||
"tts_errors": {
|
||||
"enter_text": "Veuillez saisir du texte",
|
||||
"upload_or_select": "Téléchargez un audio ou sélectionnez un profil vocal",
|
||||
"trim_hint": "L'audio est de {{duration}}s — coupez à ≤{{max}}s pour un meilleur clonage",
|
||||
"timeout": "Le délai de génération a expiré : le téléchargement du modèle est peut-être encore en cours. Vérifiez Paramètres → Journaux, puis réessayez.",
|
||||
"error_prefix": "Erreur : {{message}}",
|
||||
"ignored_unsupported": "Instruction non prise en charge ignorée : {{items}}",
|
||||
"ignored_duplicate": "Ignoré (catégorie déjà définie) : {{items}}"
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Partage et accès à distance",
|
||||
"help": "Exposez cette instance OmniVoice en cours d'exécution à vos autres machines sans la redémarrer. Le bouclage uniquement est la valeur par défaut : rien n'est partagé jusqu'à ce que vous l'activiez ici.",
|
||||
"local_network": "Réseau local",
|
||||
"local_help": "Partagez sur votre Wi-Fi / Ethernet avec un code PIN d'accès unique. D'autres appareils scannent le code QR ou ouvrent le lien.",
|
||||
"ports_title": "Ports",
|
||||
"ports_help": "Celles-ci sont définies via des variables d'environnement lues au démarrage. Modifiez le port backend ou UI en définissant la variable et en redémarrant OmniVoice.",
|
||||
"backend_port": "Port principal",
|
||||
"ui_port": "Port d'interface utilisateur",
|
||||
"lan_share_port": "Port de partage LAN",
|
||||
"port_error": "Entrez un port entre 1024 et 65535",
|
||||
"port_saved": "Port de partage LAN enregistré : s'appliquera la prochaine fois que vous activerez le partage",
|
||||
"port_save_failed": "Impossible d'enregistrer le port : {{message}}",
|
||||
"saving": "Sauvegarde…",
|
||||
"ports_note": "Les ports backend et UI s’appliquent au redémarrage. Le port de partage LAN s'appliquera la prochaine fois que vous activerez le partage.",
|
||||
"tailscale_title": "Tailscale (accès à distance privé)",
|
||||
"tailscale_checking": "Vérification de la queue…",
|
||||
"tailscale_absent": "Échelle arrière non détectée. Installez-le pour accéder à OmniVoice en toute sécurité depuis n'importe où sur votre réseau privé.",
|
||||
"tailscale_install": "Installer la balance arrière",
|
||||
"tailscale_running": "Tailscale est en cours d’exécution. Servez OmniVoice sur votre réseau privé.",
|
||||
"tailscale_not_logged_in": "Tailscale est installé mais n’est pas connecté. Démarrez et connectez-vous d’abord à Tailscale.",
|
||||
"tailscale_enabled": "Service Tailscale activé",
|
||||
"tailscale_enable_failed": "Impossible d'activer Tailscale",
|
||||
"tailscale_enable_error": "Impossible d'activer Tailscale : {{message}}",
|
||||
"tailscale_disabled": "Service Tailscale désactivé",
|
||||
"tailscale_disable_failed": "Impossible de désactiver Tailscale",
|
||||
"tailscale_disable_error": "Impossible de désactiver Tailscale : {{message}}",
|
||||
"tailscale_enabling": "Activation…",
|
||||
"tailscale_enable_btn": "Activer le service Tailscale",
|
||||
"tailscale_disabling": "Désactivation…",
|
||||
"tailscale_disable_btn": "Arrêter le service Tailscale",
|
||||
"tailscale_copy": "Copier le lien",
|
||||
"tailscale_open": "Ouvrir dans le navigateur",
|
||||
"copied": "Copié",
|
||||
"tailscale_qr_alt": "Code QR pour l'URL Tailscale"
|
||||
},
|
||||
"reportBug": {
|
||||
"label": "Signaler un bug",
|
||||
"title": "Ouvre une page de problèmes GitHub pré-remplie dans votre navigateur. Rien n'est envoyé jusqu'à ce que vous cliquiez sur Soumettre."
|
||||
},
|
||||
"app": {
|
||||
"loading": "Chargement…",
|
||||
"trimmed_loaded": "Audio découpé chargé",
|
||||
"toast_exported": "Exporté : {{name}}",
|
||||
"toast_export_failed": "Échec de l'exportation : {{message}}",
|
||||
"toast_open_folder_failed": "Impossible d'ouvrir le dossier : {{message}}",
|
||||
"toast_saving": "Sauvegarde de {{name}}...",
|
||||
"toast_saved": "Enregistré : {{path}}",
|
||||
"toast_save_error": "Erreur d'enregistrement : {{message}}",
|
||||
"toast_processing": "Traitement {{name}}...",
|
||||
"toast_downloaded": "Téléchargé {{name}}",
|
||||
"toast_download_error": "Erreur de téléchargement : {{message}}",
|
||||
"toast_upload_first": "Veuillez d'abord cliquer sur « Télécharger et transcrire » afin que la vidéo soit traitée sur le serveur avant de l'enregistrer.",
|
||||
"toast_save_failed": "Échec de l'enregistrement : {{message}}",
|
||||
"toast_opened": "Ouvert : {{name}}",
|
||||
"toast_project_deleted": "Projet supprimé",
|
||||
"toast_restored_state": "État de la génération précédente restauré",
|
||||
"toast_history_deleted": "Élément d'historique supprimé",
|
||||
"toast_flushed": "Vidé — RAM {{ram}}G · VRAM {{vram}}G{{unloaded}}",
|
||||
"toast_model_unloaded": "· modèle déchargé",
|
||||
"toast_flush_failed": "Échec du rinçage : {{message}}",
|
||||
"toast_project_saved": "Projet enregistré",
|
||||
"toast_project_created": "Projet créé"
|
||||
},
|
||||
"update": {
|
||||
"available": "Mise à jour {{version}} disponible",
|
||||
"install": "Installer et redémarrer",
|
||||
"install_hint": "Télécharger la mise à jour et redémarrer dans la nouvelle version",
|
||||
"downloading": "Mise à jour… {{pct}} %",
|
||||
"restart": "Redémarrer pour mettre à jour",
|
||||
"busy": "Terminez d'abord votre doublage, puis installez la mise à jour.",
|
||||
"whats_new": "Quoi de neuf",
|
||||
"failed": "La mise à jour a échoué",
|
||||
"retry": "Réessayer",
|
||||
"dismiss": "Rejeter"
|
||||
},
|
||||
"archetypes": {
|
||||
"featured": "En vedette",
|
||||
@@ -946,5 +1578,25 @@
|
||||
"facet_accent": "Accent",
|
||||
"facet_lang": "Langue",
|
||||
"facet_whisper": "Chuchoter"
|
||||
},
|
||||
"support": {
|
||||
"tab_support": "Assistance",
|
||||
"tab_license": "Licence commerciale",
|
||||
"toggle_label": "Support ou licence commerciale",
|
||||
"other_ways": "Autres moyens d'aider",
|
||||
"star_github": "Étoile sur GitHub",
|
||||
"join_discord": "Rejoignez Discorde"
|
||||
},
|
||||
"updates": {
|
||||
"tab": "Mises à jour",
|
||||
"up_to_date": "À jour · v{{version}}",
|
||||
"check_now": "Vérifiez maintenant",
|
||||
"releases": "Versions",
|
||||
"current": "actuelle",
|
||||
"prerelease": "aperçu",
|
||||
"loading": "Chargement des versions…",
|
||||
"none": "Aucune version trouvée",
|
||||
"load_error": "Impossible de charger les versions (hors ligne ?)",
|
||||
"retry_load": "Réessayer"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,4 @@
|
||||
{
|
||||
"update": {
|
||||
"available": "अपडेट {{version}} उपलब्ध है",
|
||||
"install": "इंस्टॉल करें और पुनः आरंभ करें",
|
||||
"install_hint": "अपडेट डाउनलोड करें और नए संस्करण में पुनः आरंभ करें",
|
||||
"downloading": "अपडेट हो रहा है… {{pct}}%",
|
||||
"restart": "अपडेट करने के लिए पुनः आरंभ करें",
|
||||
"busy": "पहले अपनी डबिंग पूरी करें — फिर अपडेट इंस्टॉल करें।"
|
||||
},
|
||||
"nav": {
|
||||
"clone": "क्लोन",
|
||||
"design": "डिज़ाइन",
|
||||
@@ -16,7 +8,10 @@
|
||||
"launchpad": "लॉन्चपैड",
|
||||
"gallery": "गैलरी",
|
||||
"transcripts": "प्रतिलेख",
|
||||
"omnidrive": "ओमनीड्राइव"
|
||||
"omnidrive": "ओमनीड्राइव",
|
||||
"move_rail_right": "रेल को दाईं ओर ले जाएँ",
|
||||
"move_rail_left": "रेल को बाईं ओर ले जाएँ",
|
||||
"flip_rail": "रेल की ओर पलटें"
|
||||
},
|
||||
"settings": {
|
||||
"general": "सामान्य",
|
||||
@@ -45,7 +40,40 @@
|
||||
"ffmpeg_missing": "नहीं मिला",
|
||||
"ffmpeg_current": "वर्तमान पथ",
|
||||
"ffmpeg_desc": "यदि ऑटो-डिटेक्शन विफल हो जाता है तो एक कस्टम ffmpeg पथ सेट करें।",
|
||||
"ffmpeg_saved": "FFmpeg पथ सेट - लागू करने के लिए बैकएंड को पुनरारंभ करें।"
|
||||
"ffmpeg_saved": "FFmpeg पथ सेट - लागू करने के लिए बैकएंड को पुनरारंभ करें।",
|
||||
"diagnostics_copied": "डायग्नोस्टिक्स की प्रतिलिपि बनाई गई - अपनी समस्या रिपोर्ट में पेस्ट करें।",
|
||||
"updater_desktop": "अपडेटर केवल डेस्कटॉप ऐप में चलता है।",
|
||||
"latest_version": "आप नवीनतम संस्करण पर हैं.",
|
||||
"logs_load_failed": "लॉग लोड करने में विफल: {{message}}",
|
||||
"clear_frontend_confirm": "इन-मेमोरी फ़्रंटएंड लॉग बफ़र साफ़ करें?",
|
||||
"clear_frontend_title": "लॉग साफ़ करें",
|
||||
"frontend_logs_cleared": "फ़्रंटएंड लॉग साफ़ किए गए",
|
||||
"clear_tauri_confirm": "टॉरी-साइड लॉग फ़ाइलों को छोटा करें? OS नई प्रविष्टियाँ लिखना जारी रखेगा.",
|
||||
"clear_tauri_title": "टौरी लॉग साफ़ करें",
|
||||
"nothing_to_clear": "साफ़ करने के लिए कुछ भी नहीं - डिस्क पर अभी तक कोई टौरी लॉग फ़ाइल नहीं है।",
|
||||
"cleared_tauri_one": "{{count}} टौरी लॉग फ़ाइल साफ़ की गई",
|
||||
"cleared_tauri_other": "{{count}} टौरी लॉग फ़ाइलें साफ़ की गईं",
|
||||
"clear_tauri_failed": "टौरी लॉग साफ़ करने में विफल: {{message}}",
|
||||
"clear_backend_confirm": "बैकएंड रनटाइम + क्रैश लॉग साफ़ करें? इसे असंपादित नहीं किया जा सकता है।",
|
||||
"clear_backend_title": "लॉग साफ़ करें",
|
||||
"backend_logs_cleared": "बैकएंड लॉग साफ़ किए गए",
|
||||
"clear_backend_failed": "लॉग साफ़ करने में विफल",
|
||||
"copy_failed": "प्रतिलिपि विफल: {{message}}",
|
||||
"update_check_failed": "अद्यतन जाँच विफल: {{message}}",
|
||||
"save_failed": "सहेजना विफल: {{message}}",
|
||||
"clear_failed": "साफ़ विफल: {{message}}",
|
||||
"engine_switched": "{{family}} → {{engine}}",
|
||||
"channel_set_failed": "चैनल सेट करने में विफल: {{message}}",
|
||||
"updater_downloading": "डाउनलोड हो रहा है {{version}}…",
|
||||
"updater_installed": "इंस्टॉल किया गया - पुनः लॉन्च किया जा रहा है।",
|
||||
"updater_available_title": "अपडेट उपलब्ध है",
|
||||
"updater_available_body": "संस्करण {{version}} उपलब्ध है.\n\n{{notes}}\n\nअभी डाउनलोड करें और इंस्टॉल करें?",
|
||||
"updater_notes_fallback": "GitHub पर रिलीज़ नोट्स देखें।",
|
||||
"shortcut_load_failed": "शॉर्टकट लोड नहीं किया जा सका: {{message}}",
|
||||
"shortcut_set": "डिक्टेशन शॉर्टकट {{shortcut}} पर सेट किया गया",
|
||||
"shortcut_register_failed": "पंजीकरण नहीं हो सका: {{message}}",
|
||||
"shortcut_reset": "डिफ़ॉल्ट पर रीसेट करें",
|
||||
"shortcut_reset_failed": "रीसेट विफल: {{message}}"
|
||||
},
|
||||
"bootstrap": {
|
||||
"title": "OmniVoice Studio",
|
||||
@@ -69,7 +97,13 @@
|
||||
"waiting_output": "आउटपुट की प्रतीक्षा की जा रही है…",
|
||||
"auto_detect": "ऑटो-डिटेक्ट",
|
||||
"suggest_lang": "हिन्दी में बदलें?",
|
||||
"select_lang": "भाषा:"
|
||||
"select_lang": "भाषा:",
|
||||
"region_global": "वैश्विक (प्रत्यक्ष)",
|
||||
"region_china": "चीन (दर्पण)",
|
||||
"region_russia": "रूस (दर्पण)",
|
||||
"region_restricted": "प्रतिबंधित (दर्पण)",
|
||||
"unknown_error": "अज्ञात त्रुटि",
|
||||
"retrying": "पुनः प्रयास किया जा रहा है..."
|
||||
},
|
||||
"stories": {
|
||||
"title": "कहानियाँ संपादक",
|
||||
@@ -158,7 +192,17 @@
|
||||
"reload": "यूआई को बलपूर्वक पुनः लोड करें",
|
||||
"backend": "बैकएंड",
|
||||
"frontend": "अग्रभाग",
|
||||
"tauri": "टौरी"
|
||||
"tauri": "टौरी",
|
||||
"cancelOp": "ऑपरेशन रद्द करें",
|
||||
"dismiss": "ख़ारिज करें",
|
||||
"dismissStatus": "ख़ारिज स्थिति",
|
||||
"search": "खोजें…",
|
||||
"no_matches": "कोई मेल नहीं",
|
||||
"recent_and_popular": "नवीनतम एवं लोकप्रिय",
|
||||
"popular_label": "लोकप्रिय",
|
||||
"showing_of": "{{total}} में से {{shown}} दिखा रहा हूँ। खोजने के लिए टाइप करें...",
|
||||
"yes": "हाँ",
|
||||
"no": "नहीं"
|
||||
},
|
||||
"launchpad": {
|
||||
"greeting": "नमस्ते",
|
||||
@@ -180,7 +224,16 @@
|
||||
"locked": "ताला लगा दिया",
|
||||
"open": "खुला",
|
||||
"try_it": "इसे आज़माएं",
|
||||
"audio_only": "केवल ऑडियो"
|
||||
"audio_only": "केवल ऑडियो",
|
||||
"stories_title": "कहानियां",
|
||||
"stories_desc": "मल्टी-वॉइस ऑडियोबुक - अपने पात्रों को कास्ट करें, एक स्क्रिप्ट डालें, अध्याय के अनुसार निर्यात करें।",
|
||||
"gallery_title": "आवाज गैलरी",
|
||||
"gallery_desc": "उच्चारण, उम्र और शैली के आधार पर तैयार डिज़ाइन वाली आवाज़ें ब्राउज़ करें - कोई सेटअप नहीं।",
|
||||
"transcripts_title": "प्रतिलेख",
|
||||
"transcripts_desc": "ऑडियो या वीडियो को 646 भाषाओं में संपादन योग्य, खोजने योग्य टेक्स्ट में बदलें।",
|
||||
"recent_files": "हाल की फ़ाइलें",
|
||||
"view_all_files": "सभी फ़ाइलें देखें",
|
||||
"file": "फ़ाइल"
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "शीघ्र",
|
||||
@@ -271,11 +324,6 @@
|
||||
"outputs": "आउटपुट",
|
||||
"crash_log": "क्रैश लॉग",
|
||||
"update_endpoint": "समापन बिंदु अद्यतन करें",
|
||||
"update_channel": "अपडेट चैनल",
|
||||
"channel_stable": "स्थिर",
|
||||
"channel_preview": "प्रीव्यू",
|
||||
"channel_set": "अपडेट चैनल {{channel}} पर सेट किया गया",
|
||||
"channel_preview_hint": "प्रीव्यू नवीनतम main बिल्ड को ट्रैक करता है — नई सुविधाएँ, कम परीक्षण। यदि कोई स्थिर रिलीज़ आगे है तो स्थिर पर वापस आ जाता है।",
|
||||
"yes": "हाँ",
|
||||
"no": "नहीं",
|
||||
"web_preview": "वेब पूर्वावलोकन",
|
||||
@@ -284,7 +332,12 @@
|
||||
"copy_diagnostics": "डायग्नोस्टिक्स की प्रतिलिपि बनाएँ",
|
||||
"github": "GitHub पर ओमनीवॉइस",
|
||||
"model_card": "मॉडल कार्ड",
|
||||
"commercial_license": "वाणिज्यिक लाइसेंस"
|
||||
"commercial_license": "वाणिज्यिक लाइसेंस",
|
||||
"update_channel": "अपडेट चैनल",
|
||||
"channel_stable": "स्थिर",
|
||||
"channel_preview": "प्रीव्यू",
|
||||
"channel_set": "अपडेट चैनल {{channel}} पर सेट किया गया",
|
||||
"channel_preview_hint": "प्रीव्यू नवीनतम main बिल्ड को ट्रैक करता है — नई सुविधाएँ, कम परीक्षण। यदि कोई स्थिर रिलीज़ आगे है तो स्थिर पर वापस आ जाता है।"
|
||||
},
|
||||
"privacy": {
|
||||
"desc": "सब कुछ <1>इस मशीन</1> पर चलता है। जब तक आप स्पष्ट रूप से ऑनलाइन अनुवादक (Google, DeepL, आदि) का उपयोग नहीं करते हैं या HuggingFace पर नहीं जाते हैं, तब तक आपका ऑडियो, वीडियो और ट्रांसक्रिप्ट आपके कंप्यूटर को कभी नहीं छोड़ते हैं।",
|
||||
@@ -340,7 +393,30 @@
|
||||
"unavailable": "अनुपलब्ध",
|
||||
"use": "उपयोग करें",
|
||||
"loading": "इंजन लोड हो रहे हैं...",
|
||||
"refresh": "ताज़ा करें"
|
||||
"refresh": "ताज़ा करें",
|
||||
"matrixTitle": "इंजन अनुकूलता मैट्रिक्स",
|
||||
"loadFailed": "इंजन लोड करने में विफल: {{message}}",
|
||||
"couldNotLoad": "इंजन लोड नहीं हो सका: {{message}}",
|
||||
"retry": "पुनः प्रयास करें",
|
||||
"activeEngine": "सक्रिय {{family}}: {{engine}}",
|
||||
"engineCompatLabel": "{{family}} इंजन अनुकूलता",
|
||||
"active": "सक्रिय",
|
||||
"whyUnavailable": "अनुपलब्ध क्यों?",
|
||||
"lastError": "अंतिम त्रुटि: {{error}}",
|
||||
"installedAndReady": "स्थापित और तैयार",
|
||||
"notInstalled": "स्थापित नहीं",
|
||||
"available": "उपलब्ध",
|
||||
"subprocessTitle": "अपने स्वयं के उपप्रोसेस + वेनव में चलता है",
|
||||
"inProcessTitle": "ओमनीवॉइस पायथन प्रक्रिया में चलता है",
|
||||
"testEngine": "परीक्षण इंजन",
|
||||
"testing": "परीक्षण...",
|
||||
"recheck": "दोबारा जांचें",
|
||||
"rechecking": "दोबारा जांच...",
|
||||
"latencyMs": "{{ms}} एमएस",
|
||||
"failed": "विफल",
|
||||
"acceptLicense": "लाइसेंस स्वीकार करें",
|
||||
"noBackends": "कोई बैकएंड पंजीकृत नहीं है.",
|
||||
"switch_failed": "इंजन स्विच करने में विफल"
|
||||
},
|
||||
"capture": {
|
||||
"desc": "ग्लोबल हॉटकीज़ केवल डेस्कटॉप ऐप में काम करती हैं। वेब यूआई इन-पेज <1>Ctrl+Shift+Space</1> शॉर्टकट का उपयोग करता है जबकि विंडो पर फोकस होता है।",
|
||||
@@ -352,13 +428,55 @@
|
||||
"record_shortcut": "शॉर्टकट रिकॉर्ड करें",
|
||||
"recording": "रिकॉर्डिंग...",
|
||||
"save": "सहेजें",
|
||||
"reset_default": "डिफ़ॉल्ट पर रीसेट करें"
|
||||
"reset_default": "डिफ़ॉल्ट पर रीसेट करें",
|
||||
"listening_label": "सुन रहा हूँ...",
|
||||
"transcribing_label": "प्रतिलेखन...",
|
||||
"pasted": "चिपकाया गया",
|
||||
"no_speech": "कोई भाषण नहीं मिला",
|
||||
"mic_denied": "माइक का उपयोग अस्वीकृत",
|
||||
"mic_denied_toast": "माइक्रोफ़ोन पहुंच अस्वीकृत. {{hint}}",
|
||||
"mic_hint_mac": "macOS: सिस्टम सेटिंग्स → गोपनीयता और सुरक्षा → माइक्रोफ़ोन खोलें और ओमनीवॉइस सक्षम करें।",
|
||||
"mic_hint_windows": "विंडोज़: सेटिंग्स → गोपनीयता और सुरक्षा → माइक्रोफ़ोन खोलें और ओमनीवॉइस को अनुमति दें।",
|
||||
"mic_hint_linux": "लिनक्स: जांचें कि आपका उपयोगकर्ता ऑडियो समूह में है और वेबव्यू के पास माइक की पहुंच है।",
|
||||
"transcription_failed": "प्रतिलेखन विफल: {{message}}"
|
||||
},
|
||||
"logs": {
|
||||
"no_tauri_log": "अभी तक डिस्क पर कोई टौरी लॉग नहीं है - एक उत्पादन करने के लिए डेस्कटॉप बिल्ड के माध्यम से लॉन्च करें",
|
||||
"empty_frontend": "अभी तक कोई फ्रंटएंड कंसोल प्रविष्टियाँ कैप्चर नहीं की गईं। ऐप के साथ इंटरैक्ट करें - प्रत्येक कंसोल.* यहां दिखाई देगा।",
|
||||
"empty_tauri": "कोई टौरी लॉग उपलब्ध नहीं है. केवल डेस्कटॉप शेल में चलता है.",
|
||||
"empty_backend": "रनटाइम लॉग खाली है. जैसे ही बैकएंड इसे लॉग करेगा गतिविधि यहां दिखाई देगी।"
|
||||
"empty_backend": "रनटाइम लॉग खाली है. जैसे ही बैकएंड इसे लॉग करेगा गतिविधि यहां दिखाई देगी।",
|
||||
"title": "लॉग",
|
||||
"source_backend": "बैकएंड",
|
||||
"source_frontend": "अग्रभाग",
|
||||
"source_tauri": "टौरी",
|
||||
"expand": "लॉग का विस्तार करें",
|
||||
"collapse": "लॉग संक्षिप्त करें",
|
||||
"expand_aria": "लॉग पैनल का विस्तार करें",
|
||||
"collapse_aria": "लॉग पैनल को संक्षिप्त करें",
|
||||
"drag_resize": "आकार बदलने के लिए खींचें",
|
||||
"refresh": "ताज़ा करें",
|
||||
"refresh_aria": "लॉग ताज़ा करें",
|
||||
"copy_visible": "दृश्यमान लॉग की प्रतिलिपि बनाएँ",
|
||||
"copy_visible_aria": "दृश्यमान लॉग की प्रतिलिपि बनाएँ",
|
||||
"clear": "स्पष्ट",
|
||||
"clear_aria": "लॉग साफ़ करें",
|
||||
"report_issue": "रिपोर्ट समस्या (कॉपी डायग्नोस्टिक)",
|
||||
"report_issue_aria": "रिपोर्ट मुद्दा",
|
||||
"close": "बंद करें",
|
||||
"close_aria": "लॉग पैनल बंद करें",
|
||||
"join_discord": "हमारे कलह में शामिल हों",
|
||||
"join_discord_aria": "हमारे डिस्कोर्ड समुदाय में शामिल हों",
|
||||
"support_project": "इस प्रोजेक्ट का समर्थन करें",
|
||||
"support_project_aria": "इस प्रोजेक्ट का समर्थन करें",
|
||||
"empty_frontend_short": "अभी तक कोई फ्रंटएंड कंसोल आउटपुट नहीं है।",
|
||||
"empty_lines": "कोई पंक्तियाँ नहीं.",
|
||||
"all_clear": "✅ सब साफ़ - कोई समस्या नहीं पाई गई",
|
||||
"log_cleared": "{{source}} लॉग साफ़ हो गया",
|
||||
"clear_failed": "साफ़ विफल: {{message}}",
|
||||
"log_copied": "{{source}} लॉग कॉपी किया गया",
|
||||
"copy_failed": "प्रतिलिपि विफल: {{message}}",
|
||||
"report_copied": "डायग्नोस्टिक रिपोर्ट की प्रतिलिपि बनाई गई - इसे GitHub अंक में पेस्ट करें।",
|
||||
"report_failed": "रिपोर्ट विफल: {{message}}"
|
||||
},
|
||||
"voice_profile": {
|
||||
"test_text": "नमस्ते - यह इस आवाज़ का परीक्षण है।",
|
||||
@@ -526,7 +644,17 @@
|
||||
"install_already": "{{engine}} पहले से ही स्थापित था",
|
||||
"install_ok": "{{engine}} स्थापित",
|
||||
"install_failed": "इंस्टॉल विफल: {{message}}",
|
||||
"prep_elapsed": "{{time}} बीत गया"
|
||||
"prep_elapsed": "{{time}} बीत गया",
|
||||
"add_language": "भाषा जोड़ें",
|
||||
"search_languages": "भाषाएँ खोजें...",
|
||||
"languages_selected_one": "{{count}} भाषा चयनित",
|
||||
"languages_selected_other": "{{count}} भाषाएँ चयनित",
|
||||
"more_to_narrow": "+{{count}} अधिक - संकीर्ण करने के लिए टाइप करें",
|
||||
"no_matches": "कोई मेल नहीं",
|
||||
"diagnostic_copied": "डायग्नोस्टिक कॉपी किया गया",
|
||||
"copy_failed": "प्रतिलिपि विफल",
|
||||
"open_docs": "दस्तावेज़ खोलें",
|
||||
"copy_diagnostic": "डायग्नोस्टिक कॉपी करें"
|
||||
},
|
||||
"glossary": {
|
||||
"title": "शब्दावली",
|
||||
@@ -601,7 +729,17 @@
|
||||
"more_actions_title": "अधिक क्रियाएं",
|
||||
"speaker_pick": "उठाओ...",
|
||||
"speaker_title_detected": "स्पीकर - पता लगाए गए में से चुनें, या एक कस्टम नाम टाइप करें",
|
||||
"speaker_title_custom": "स्पीकर - एक नाम टाइप करें (कोई डायराइज़ेशन क्लोन नहीं पाया गया)"
|
||||
"speaker_title_custom": "स्पीकर - एक नाम टाइप करें (कोई डायराइज़ेशन क्लोन नहीं पाया गया)",
|
||||
"time_edit_title": "प्रारंभ समय (m:ss.s) संपादित करने के लिए क्लिक करें। प्रतिबद्ध करने के लिए दर्ज करें, रद्द करने के लिए Esc।",
|
||||
"fit_fits": "फिट बैठता है",
|
||||
"fit_fits_title": "प्राकृतिक दर वाला ऑडियो स्लॉट के अंदर फ़िट हो जाता है।",
|
||||
"fit_overflows": "अतिप्रवाह +{{seconds}}s",
|
||||
"fit_overflows_title": "अनुवादित पाठ मूल स्लॉट से {{seconds}}s अधिक लंबा था। ऑडियो को हार्ड-ट्रिम किया गया था; टेक्स्ट को छोटा करें या टाइमिंग को \"स्ट्रेच वीडियो\" पर स्विच करें।",
|
||||
"fit_stretched": "वीडियो {{ratio}}×",
|
||||
"fit_stretched_title": "स्ट्रेच वीडियो मोड: प्राकृतिक डब ऑडियो में फिट होने के लिए इस सेगमेंट के वीडियो को {{ratio}}× तक धीमा कर दिया गया था।",
|
||||
"fit_compressed_title": "टीटीएस ऑडियो स्लॉट का {{pct}}% है - अत्यधिक संपीड़ित।",
|
||||
"fit_audio_title": "ऑडियो स्लॉट के अंदर फ़िट हो जाता है।",
|
||||
"fit_ratio_title": "टीटीएस ऑडियो स्लॉट का {{pct}}% है।"
|
||||
},
|
||||
"voice": {
|
||||
"personality": "व्यक्तित्व",
|
||||
@@ -696,10 +834,24 @@
|
||||
"status_running": "चल रहा है",
|
||||
"status_done": "किया",
|
||||
"status_failed": "विफल",
|
||||
"status_cancelled": "रद्द कर दिया गया"
|
||||
"status_cancelled": "रद्द कर दिया गया",
|
||||
"add_to_queue_title": "कतार में वीडियो जोड़ें",
|
||||
"drop_hint_text": "वीडियो फ़ाइलें यहां छोड़ें या ब्राउज़ करने के लिए क्लिक करें",
|
||||
"drop_formats": "एमपी4 · एमओवी · एमकेवी · वेबएम",
|
||||
"files_kicker": "फ़ाइलें ({{count}})",
|
||||
"file_size_mb": "{{size}} एमबी",
|
||||
"target_languages": "लक्ष्य भाषाएँ",
|
||||
"voice_kicker": "आवाज",
|
||||
"default_option": "डिफ़ॉल्ट",
|
||||
"clone_profiles": "क्लोन प्रोफाइल",
|
||||
"presets": "प्रीसेट",
|
||||
"preserve_bg": "पृष्ठभूमि ऑडियो सुरक्षित रखें (संगीत/एफएक्स)",
|
||||
"estimate": "{{videos}} वीडियो × {{langs}} लैंग्वेज = {{jobs}} कार्य",
|
||||
"select_files_langs": "फ़ाइलें और भाषाएँ चुनें",
|
||||
"add_to_queue": "कतार में जोड़ें"
|
||||
},
|
||||
"gallery": {
|
||||
"title": "गैलरी",
|
||||
"title": "OmniVoice गैलरी",
|
||||
"search_placeholder": "यूट्यूब पर खोजें...",
|
||||
"all_voices": "सभी आवाज़ें ({{count}})",
|
||||
"no_voices": "अभी तक कोई आवाज़ नहीं",
|
||||
@@ -714,6 +866,14 @@
|
||||
"youtube_results": "यूट्यूब परिणाम ({{count}})",
|
||||
"clone_profile": "क्लोन प्रोफ़ाइल",
|
||||
"crop_audio": "ऑडियो काटें",
|
||||
"cat_disney": "डिज्नी",
|
||||
"cat_anime": "एनिमे",
|
||||
"cat_marvel": "मार्वल/डीसी",
|
||||
"cat_celebs": "मशहूर हस्तियाँ",
|
||||
"cat_politicians": "राजनेता",
|
||||
"cat_news": "समाचार एंकर",
|
||||
"cat_gaming": "गेमिंग",
|
||||
"cat_books": "किताबें/फिल्में",
|
||||
"subtitle": "सैकड़ों तैयार डिजाइन वाली आवाजें - एक चुनें और जाएं।",
|
||||
"zone_archetypes": "मूलरूप",
|
||||
"zone_imports": "मेरा आयात",
|
||||
@@ -818,7 +978,6 @@
|
||||
"badge": "वाणिज्यिक लाइसेंस",
|
||||
"hero_title": "उत्पादन में एआई आवाजें भेजें",
|
||||
"hero_desc": "ओमनीवॉइस स्टूडियो फंक्शनल सोर्स लाइसेंस (एफएसएल) के तहत स्रोत-उपलब्ध है। अधिकांश उपयोगकर्ता व्यावसायिक समझौते के बिना मूल्यांकन, प्रोटोटाइप और यहां तक कि आंतरिक रूप से तैनात भी कर सकते हैं। आपको वाणिज्यिक लाइसेंस की आवश्यकता केवल तभी होती है जब आप कोई प्रतिस्पर्धी उत्पाद या सेवा बना रहे हों, या यदि आपका उपयोग मामला एफएसएल की सीमाओं से बाहर हो।",
|
||||
"hero_note": "एक प्रतिस्पर्धी उत्पाद या सेवा का निर्माण या बड़े पैमाने पर तैनाती (उदाहरण के लिए, भुगतान-प्रति-उपयोग एपीआई की सेवा) के लिए एक वाणिज्यिक लाइसेंस की आवश्यकता होती है। मूल्य निर्धारण स्तर जल्द ही आ रहे हैं - इस बीच संपर्क करें।",
|
||||
"why_title": "व्यवसाय ओमनीवॉइस क्यों चुनते हैं?",
|
||||
"pricing_title": "मूल्य निर्धारण",
|
||||
"faq_title": "सामान्य प्रश्न",
|
||||
@@ -838,7 +997,8 @@
|
||||
"benefit_source": "स्रोत-उपलब्ध कोर",
|
||||
"benefit_source_desc": "स्टैक में पूर्ण दृश्यता. लाइसेंस शर्तों के भीतर ऑडिट, फोर्क और अनुकूलन करें।",
|
||||
"benefit_lang": "646 भाषाएँ",
|
||||
"benefit_lang_desc": "मानव-स्तरीय गुणवत्ता के साथ 646 भाषाओं में प्रतिलेखन, अनुवाद और डब करें।"
|
||||
"benefit_lang_desc": "मानव-स्तरीय गुणवत्ता के साथ 646 भाषाओं में प्रतिलेखन, अनुवाद और डब करें।",
|
||||
"hero_note": "आंतरिक उपयोग - यहां तक कि बड़े पैमाने पर - एफएसएल के तहत मुफ़्त है; एक वाणिज्यिक लाइसेंस की आवश्यकता केवल दूसरों को एक प्रतिस्पर्धी उत्पाद या सेवा (एक होस्टेड या भुगतान-प्रति-उपयोग एपीआई, एक पुनर्विक्रय या व्हाइट-लेबल ऐप) के रूप में ओमनीवॉइस की पेशकश करने के लिए आवश्यक है। मूल्य निर्धारण स्तर जल्द ही आ रहे हैं - इस बीच संपर्क करें।"
|
||||
},
|
||||
"exportModal": {
|
||||
"export": "निर्यात करें",
|
||||
@@ -927,7 +1087,479 @@
|
||||
"dubbing_title": "डबिंग क्रिया देखें",
|
||||
"dubbing_sync": "समन्वयित प्लेबैक",
|
||||
"dubbing_picker": "दूसरी भाषा आज़माएँ:",
|
||||
"dubbing_cta": "इसे अपने वीडियो पर चलाएँ →"
|
||||
"dubbing_cta": "इसे अपने वीडियो पर चलाएँ →",
|
||||
"dubbing_loading": "डबिंग डेमो लोड हो रहा है...",
|
||||
"dubbing_dismiss": "डबिंग डेमो ख़ारिज करें",
|
||||
"original_tag": "मौलिक",
|
||||
"dubbed_tag": "डब किया गया",
|
||||
"script_conversational": "संवादात्मक",
|
||||
"script_technical": "तकनीकी शब्दावली",
|
||||
"script_french": "गैर-अंग्रेज़ी (फ़्रेंच)",
|
||||
"aria_pause": "रोकें{{label}}",
|
||||
"aria_hear": "सुनें {{label}}",
|
||||
"aria_replay": "प्रतिलेखक के माध्यम से {{label}} को पुनः चलाएँ"
|
||||
},
|
||||
"direction": {
|
||||
"title": "खंड #{{id}} के लिए दिशा",
|
||||
"desc": "पाइपलाइन को बताएं कि यह लाइन कैसी लगनी चाहिए। सादा अंग्रेजी काम करता है - सिस्टम आपके शब्दों को एक स्थिर वर्गीकरण (ऊर्जा / भावना / गति / अंतरंगता / औपचारिकता) पर मैप करता है, फिर सिनेमाई अनुवाद, टीटीएस और स्लॉट-फिट के माध्यम से वर्गीकरण को पिरोता है।",
|
||||
"label": "दिशा",
|
||||
"lineHint": "पंक्ति: \"{{text}}\"",
|
||||
"placeholder": "जैसे अत्यावश्यक और आश्चर्यचकित / गर्म, आशावान / फुसफुसाए हुए, अंतरंग",
|
||||
"previewParse": "पूर्वावलोकन पार्स",
|
||||
"previewFailed": "पूर्वावलोकन विफल: {{message}}",
|
||||
"clear": "स्पष्ट",
|
||||
"cancel": "रद्द करें",
|
||||
"saveDirection": "दिशा सहेजें",
|
||||
"ttsInstruct": "टीटीएस निर्देश:",
|
||||
"nothingParsed": "- (कुछ भी पार्स नहीं किया गया)",
|
||||
"translateHint": "संकेत का अनुवाद करें:",
|
||||
"rateBias": "दर पूर्वाग्रह:",
|
||||
"speedsUp": "गति बढ़ाता है",
|
||||
"slowsDown": "धीमा हो जाता है",
|
||||
"taxonomyTokens": "टैक्सोनॉमी टोकन"
|
||||
},
|
||||
"errors": {
|
||||
"title": "इस टैब में खराबी आ गई.",
|
||||
"desc": "चिंता न करें - बाकी ऐप अभी भी काम करता है। आप टैब स्विच कर सकते हैं, या नीचे पुनः प्रयास कर सकते हैं।",
|
||||
"tryAgain": "पुनः प्रयास करें",
|
||||
"openDocs": "इस त्रुटि के लिए दस्तावेज़ खोलें"
|
||||
},
|
||||
"keyboard": {
|
||||
"title": "कीबोर्ड शॉर्टकट",
|
||||
"footer": "इसे खोलने के लिए किसी भी समय <1>?</1> दबाएँ।",
|
||||
"or": "या",
|
||||
"nav": "नेविगेशन",
|
||||
"nav_cheatsheet": "यह चीटशीट दिखाओ",
|
||||
"nav_closeModal": "मोडल बंद करें / रद्द करें",
|
||||
"nav_save": "प्रोजेक्ट सहेजें/कमिट ट्रिम करें",
|
||||
"segmentEditor": "खंड संपादक",
|
||||
"seg_split": "कर्सर पर खंड विभाजित करें",
|
||||
"seg_merge": "अगले खंड के साथ विलय करें",
|
||||
"seg_undo": "पूर्ववत करें",
|
||||
"seg_redo": "पुनः करें",
|
||||
"seg_click": "प्राथमिक क्रिया",
|
||||
"seg_shiftClick": "रेंज का चयन करें",
|
||||
"trimmer": "ऑडियो ट्रिमर",
|
||||
"trim_playPause": "पूर्वावलोकन चलाएं/रोकें",
|
||||
"trim_nudgeStart": "नज स्टार्ट हैंडल",
|
||||
"trim_nudgeEnd": "नज एंड हैंडल",
|
||||
"trim_fineNudge": "बढ़िया इशारा",
|
||||
"trim_coarseNudge": "स्थूल कुहनी",
|
||||
"trim_zoomIn": "ज़ूम इन/आउट करें",
|
||||
"trim_fitAll": "सभी को फ़िट करें / फ़िट चयन",
|
||||
"trim_confirm": "ट्रिम की पुष्टि करें",
|
||||
"dub": "डब",
|
||||
"dub_generate": "डब उत्पन्न करें",
|
||||
"dub_sidebar": "साइडबार टॉगल करें"
|
||||
},
|
||||
"network": {
|
||||
"sharing_on_title": "साझाकरण पर - विवरण के लिए क्लिक करें",
|
||||
"share_on_network": "अपने नेटवर्क पर साझा करें",
|
||||
"switching": "स्विचिंग...",
|
||||
"network": "नेटवर्क",
|
||||
"local": "स्थानीय",
|
||||
"share_confirm_title": "अपने नेटवर्क पर साझा करें?",
|
||||
"share_confirm_hint": "आपके वाई-फ़ाई/ईथरनेट पर अन्य उपकरण ओमनीवॉइस के चालू होने पर दिखाए गए एक्सेस पिन का उपयोग करके उस तक पहुंचने में सक्षम होंगे।",
|
||||
"enabling": "सक्षम किया जा रहा है...",
|
||||
"enable": "सक्षम करें",
|
||||
"shared_title": "आपके नेटवर्क पर साझा किया गया",
|
||||
"no_interface": "कोई पहुंच योग्य नेटवर्क इंटरफ़ेस नहीं - वाई-फ़ाई/ईथरनेट से कनेक्ट करें।",
|
||||
"copy_link": "लिंक कॉपी करें",
|
||||
"open_in_browser": "ब्राउज़र में खोलें",
|
||||
"qr_alt": "{{ip}} के लिए क्यूआर",
|
||||
"pin": "पिन:",
|
||||
"stop_sharing": "साझा करना बंद करो",
|
||||
"copied": "नकल की गई",
|
||||
"enable_error": "साझाकरण सक्षम नहीं किया जा सका: {{message}}",
|
||||
"disable_error": "अक्षम नहीं किया जा सका: {{message}}"
|
||||
},
|
||||
"readiness": {
|
||||
"checking_system": "सिस्टम की जाँच हो रही है...",
|
||||
"all_ready": "सभी सिस्टम तैयार",
|
||||
"system_readiness": "सिस्टम की तैयारी",
|
||||
"asr_model": "एएसआर मॉडल",
|
||||
"loaded_ready": "लोड और तैयार",
|
||||
"loading_first_run": "लोड हो रहा है... (पहली बार चलाने में 1-2 मिनट लग सकते हैं)",
|
||||
"failed_to_load": "लोड करने में विफल",
|
||||
"not_loaded_yet": "अभी तक लोड नहीं हुआ है - प्रथम प्रतिलेखन पर लोड होगा",
|
||||
"error_check_logs": "त्रुटि: {{error}}. लॉग जांचें और पुनः प्रारंभ करने का प्रयास करें।",
|
||||
"check_logs_restart": "मॉडल लोडिंग त्रुटियों के लिए लॉग की जाँच करें। पुनः आरंभ करने का प्रयास करें.",
|
||||
"llm_cinematic": "एलएलएम (सिनेमाई)",
|
||||
"llm_configure": "सिनेमाई अनुवाद गुणवत्ता के लिए TRANSLATE_BASE_URL कॉन्फ़िगर करें",
|
||||
"llm_set_env": "TRANSLATE_BASE_URL और TRANSLATE_API_KEY पर्यावरण चर सेट करें। ओलामा, ओपनएआई, एलएम स्टूडियो आदि के साथ काम करता है।",
|
||||
"llm_optional": "वैकल्पिक - सिनेमाई गुणवत्ता के लिए TRANSLATE_BASE_URL सेट करें"
|
||||
},
|
||||
"license": {
|
||||
"title": "सुपरटोनिक-3 - लाइसेंस स्वीकृति",
|
||||
"intro": "दो अलग-अलग लाइसेंस के तहत सुपरटोनिक-3 जहाज। कृपया इंजन को सक्षम करने से पहले दोनों की समीक्षा करें।",
|
||||
"sdk_heading": "एसडीके कोड · एमआईटी",
|
||||
"sdk_desc": "पायथन अनुमान एसडीके (सुपरटोनिक) एमआईटी-लाइसेंस प्राप्त है। वाणिज्यिक सहित अनुमेय उपयोग।",
|
||||
"read_mit": "एमआईटी लाइसेंस पढ़ें →",
|
||||
"model_heading": "मॉडल वज़न · ओपनरेल-एम",
|
||||
"model_desc": "सुपरटॉनिक-3 मॉडल वेट ओपनरेल-एम लाइसेंस के तहत जारी किए जाते हैं। यह लाइसेंस गैर-दुर्भावनापूर्ण उद्देश्यों के लिए उपयोग को प्रतिबंधित करता है - उपयोग-आधारित प्रतिबंधों के पूर्ण सेट के लिए लिंक किया गया लाइसेंस देखें।",
|
||||
"read_openrail": "OpenRAIL-M लाइसेंस पढ़ें →",
|
||||
"footer": "एक्सेप्ट पर क्लिक करने से ओमनीवॉइस की स्थानीय सेटिंग्स में आपकी स्वीकृति रिकॉर्ड हो जाती है और इंजन सक्षम हो जाता है। आपकी स्वीकृति केवल इस मशीन पर संग्रहीत है - सुपरटोन इंक. या किसी तीसरे पक्ष को कुछ भी सूचित नहीं किया जाता है।",
|
||||
"saving": "सहेजा जा रहा है...",
|
||||
"accept": "स्वीकार करो",
|
||||
"accepted_toast": "सुपरटॉनिक-3 लाइसेंस स्वीकृत।",
|
||||
"accept_error": "लाइसेंस स्वीकृति रिकॉर्ड करने में विफल: {{message}}"
|
||||
},
|
||||
"voicePreview": {
|
||||
"title": "ध्वनि पूर्वावलोकन",
|
||||
"close": "पूर्वावलोकन बंद करें",
|
||||
"default_text": "नमस्कार! यह इस बात का पूर्वावलोकन है कि मैं इस आवाज़ में कैसा लगता हूँ।",
|
||||
"default_voice": "डिफ़ॉल्ट आवाज",
|
||||
"clone_profiles": "क्लोन प्रोफाइल",
|
||||
"designed_voices": "डिज़ाइन की गई आवाज़ें",
|
||||
"presets": "प्रीसेट",
|
||||
"placeholder": "सुनने के लिए कुछ टाइप करें...",
|
||||
"stop": "रुकें",
|
||||
"regenerate": "पुनर्जीवित करें",
|
||||
"preview": "पूर्वावलोकन",
|
||||
"hint": "8 चरण · तेज़ पूर्वावलोकन"
|
||||
},
|
||||
"header": {
|
||||
"kicker_studio": "स्टूडियो",
|
||||
"kicker_library": "पुस्तकालय",
|
||||
"kicker_preferences": "प्राथमिकताएँ",
|
||||
"kicker_licensing": "लाइसेंसिंग",
|
||||
"label_launchpad": "लॉन्चपैड",
|
||||
"label_clone": "आवाज क्लोन",
|
||||
"label_design": "आवाज डिजाइन",
|
||||
"label_dub": "डबिंग",
|
||||
"label_projects": "ओमनीड्राइव",
|
||||
"label_gallery": "गैलरी",
|
||||
"label_transcriptions": "प्रतिलेखन",
|
||||
"label_settings": "सेटिंग्स",
|
||||
"label_enterprise": "वाणिज्यिक लाइसेंस",
|
||||
"status_ready": "तैयार",
|
||||
"status_loading": "लोड हो रहा है...",
|
||||
"status_idle": "निष्क्रिय",
|
||||
"memory_management": "स्मृति प्रबंधन",
|
||||
"flush": "फ्लश",
|
||||
"loaded_models": "भरे हुए मॉडल",
|
||||
"no_models": "कोई मॉडल लोड नहीं किया गया",
|
||||
"unload": "उतारो",
|
||||
"flush_caches": "कैश फ्लश करें",
|
||||
"unload_all_flush": "सभी को अनलोड करें + फ्लश करें"
|
||||
},
|
||||
"sidebar": {
|
||||
"tab_drive": "चलाओ",
|
||||
"tab_history": "इतिहास",
|
||||
"tab_exports": "निर्यात",
|
||||
"save_project": "डब प्रोजेक्ट सहेजें",
|
||||
"save_new_project": "नए डब प्रोजेक्ट के रूप में सहेजें",
|
||||
"dub_projects": "डब प्रोजेक्ट्स",
|
||||
"voice_clones": "आवाज क्लोन",
|
||||
"designed_voices": "डिज़ाइन की गई आवाज़ें",
|
||||
"no_dub_projects": "कोई सहेजा गया डब प्रोजेक्ट नहीं",
|
||||
"no_dub_hint": "एक वीडियो अपलोड करें और अपना काम जारी रखने के लिए सेव पर क्लिक करें।",
|
||||
"no_clones": "अभी तक कोई आवाज क्लोन नहीं है",
|
||||
"no_clones_hint": "ऑडियो रिकॉर्ड करें या अपलोड करें, फिर वॉयस प्रोफ़ाइल के रूप में सहेजें पर क्लिक करें।",
|
||||
"no_designs": "अभी तक कोई आवाजें डिज़ाइन नहीं की गई हैं",
|
||||
"no_designs_hint": "एक आवाज उत्पन्न करें और इसे इतिहास से सहेजें।",
|
||||
"history_subtitle": "पीढ़ी का इतिहास · SQLite में संग्रहीत",
|
||||
"no_history": "कोई पीढ़ी का इतिहास नहीं",
|
||||
"no_history_hint": "ऑडियो संश्लेषित करें या वीडियो डब करें - परिणाम यहां दिखाई देंगे।",
|
||||
"clear_history": "साफ़ इतिहास",
|
||||
"clear_confirm": "सभी {{count}} इतिहास आइटम साफ़ करें? इसे असंपादित नहीं किया जा सकता है।",
|
||||
"history_cleared": "इतिहास साफ हो गया",
|
||||
"recent_exports": "हालिया निर्यात",
|
||||
"no_exports": "कोई डाउनलोड किया गया आउटपुट नहीं",
|
||||
"no_exports_hint": "किसी फ़ाइल को यहां ट्रैक किया हुआ देखने के लिए उसे टौरी के माध्यम से निर्यात करें।",
|
||||
"show_in_folder": "फ़ोल्डर में दिखाएँ",
|
||||
"open": "खुला",
|
||||
"select": "चयन करें",
|
||||
"try_voice": "प्रयास करें",
|
||||
"consistent": "सुसंगत",
|
||||
"locked": "ताला लगा दिया",
|
||||
"clone_label": "क्लोन",
|
||||
"design_label": "डिज़ाइन",
|
||||
"dub_label": "डब",
|
||||
"save_label": "सहेजें",
|
||||
"lock_identity": "आवाज की पहचान लॉक करें",
|
||||
"in_folder": "{{folder}} में"
|
||||
},
|
||||
"trimmer": {
|
||||
"title": "संदर्भ ऑडियो ट्रिम करें",
|
||||
"decoding": "ऑडियो डिकोड करना...",
|
||||
"meta_length": "लंबाई {{duration}} · {{sampleRate}} हर्ट्ज",
|
||||
"meta_rendering": "तरंगरूप प्रतिपादन {{percent}}%",
|
||||
"keyboard_hint": "स्क्रॉल = ज़ूम · शिफ्ट+स्क्रॉल = पैन · ऑल्ट+ड्रैग = पैन · ⏐ ⟵ ⟶ ⏐ कुंजियाँ हैंडल समायोजित करें",
|
||||
"zoom_in": "ज़ूम इन करें (+)",
|
||||
"zoom_out": "ज़ूम आउट करें (-)",
|
||||
"fit_all": "सभी को फिट करें (होम)",
|
||||
"fit_selection": "फ़िट चयन (अंत)",
|
||||
"fit_sel_btn": "फिट एसईएल",
|
||||
"view_range": "देखें {{start}} → {{end}} ({{duration}})",
|
||||
"start_label": "प्रारंभ करें",
|
||||
"end_label": "ख़त्म",
|
||||
"length_label": "लंबाई",
|
||||
"too_long": ">{{max}}s",
|
||||
"too_short": "बहुत छोटा",
|
||||
"length_ok": "ठीक है",
|
||||
"loop_preview": "लूप पूर्वावलोकन",
|
||||
"pause": "विराम",
|
||||
"preview_selection": "पूर्वावलोकन चयन",
|
||||
"play_hint": "खेलने के लिए स्थान · पुष्टि करने के लिए दर्ज करें · रद्द करने के लिए Esc",
|
||||
"cancel": "रद्द करें",
|
||||
"use_trimmed": "छंटाई का उपयोग करें",
|
||||
"decode_failed": "डिकोड विफल: {{message}}",
|
||||
"playback_failed": "प्लेबैक विफल: {{message}}",
|
||||
"audio_load_failed": "ऑडियो लोड विफल रहा",
|
||||
"unit_seconds": "एस"
|
||||
},
|
||||
"casting": {
|
||||
"title": "स्पीकर कास्टिंग",
|
||||
"auto_assign_title": "निकाले गए स्पीकर क्लोन से आवाजें स्वतः असाइन करें",
|
||||
"auto_cast": "स्वतः-कास्ट",
|
||||
"all_cast": "सभी कलाकार",
|
||||
"segments_count": "{{count}} खंड",
|
||||
"assign_voice": "आवाज असाइन करें...",
|
||||
"from_video": "🎤वीडियो से ({{name}})",
|
||||
"no_profiles": "अभी तक कोई ध्वनि प्रोफ़ाइल सहेजी नहीं गई है.",
|
||||
"preview_voice": "आवाज का पूर्वावलोकन करें"
|
||||
},
|
||||
"checkpoint": {
|
||||
"asr_title": "प्रतिलेख तैयार",
|
||||
"asr_cta": "अनुवाद करें",
|
||||
"asr_hint": "किसी भी एएसआर त्रुटि को अभी ठीक करें - सटीक उच्चारण बाद में टीटीएस प्रयासों को बचाता है।",
|
||||
"translate_title": "अनुवाद तैयार",
|
||||
"translate_cta": "डब उत्पन्न करें",
|
||||
"translate_hint": "लक्ष्य पाठ को स्किम करें. अधिक लंबाई वाली लाइनों की गति बढ़ जाती है; आप सीधे भी संपादित कर सकते हैं.",
|
||||
"done_title": "डब पूरा हुआ",
|
||||
"done_hint": "समय और सिंक अनुपात की समीक्षा करें। किसी भी लाइन में बदलाव करें और तेजी से आंशिक रूप से दोबारा करने के लिए \"Regen Changed\" पर क्लिक करें।",
|
||||
"segment_one": "{{count}} खंड",
|
||||
"segment_other": "{{count}} खंड",
|
||||
"dismiss_title": "ख़ारिज करें - पुनः लोड होने तक इस चरण के लिए पुनः प्रकट नहीं होगा"
|
||||
},
|
||||
"compare": {
|
||||
"title": "ए/बी आवाज तुलना",
|
||||
"close": "तुलना बंद करें",
|
||||
"desc": "निर्णायक निर्णय लेने के लिए दो आवाजों की एक साथ तुलना करें। ऐप पीछे इंटरैक्टिव रहता है।",
|
||||
"test_phrase": "परीक्षण वाक्यांश",
|
||||
"voice_a": "आवाज़ ए",
|
||||
"voice_b": "आवाज़ बी",
|
||||
"select_voice": "— आवाज़ चुनें —",
|
||||
"preset_suffix": "(पूर्व निर्धारित)",
|
||||
"no_audio": "अभी तक कोई ऑडियो नहीं",
|
||||
"close_btn": "बंद करें",
|
||||
"comparing": "तुलना...",
|
||||
"compare_btn": "तुलना करें",
|
||||
"preparing_voice": "आवाज तैयार की जा रही है...",
|
||||
"generating_voice_a": "आवाज उत्पन्न करना...",
|
||||
"generating_voice_b": "ध्वनि उत्पन्न करना...",
|
||||
"comparison_complete": "तुलना पूर्ण!",
|
||||
"play_failed": "प्ले विफल: {{message}}"
|
||||
},
|
||||
"models": {
|
||||
"hf_token_set_toast": "हगिंगफेस टोकन सेट - तेज़ डाउनलोड सक्षम",
|
||||
"hf_token_save_failed": "टोकन सहेजने में विफल",
|
||||
"install_started": "इंस्टालेशन प्रारंभ - पंक्ति में प्रगति",
|
||||
"delete_confirm": "{{repoId}} हटाएं? आप इसे बाद में पुनः इंस्टॉल कर सकते हैं.",
|
||||
"delete_confirm_title": "मॉडल हटाएँ",
|
||||
"deleted": "हटा दिया गया {{repoId}}",
|
||||
"reinstall_confirm": "{{repoId}} को पुनः स्थापित करें? इससे वर्तमान प्रतिलिपि हट जाएगी और पुनः डाउनलोड हो जाएगी.",
|
||||
"reinstall_confirm_title": "मॉडल पुनः स्थापित करें",
|
||||
"reinstalling": "पुनः स्थापित किया जा रहा है",
|
||||
"recommended_installed": "अनुशंसित मॉडल पहले से ही स्थापित हैं.",
|
||||
"started_downloading_one": "{{count}} मॉडल डाउनलोड करना प्रारंभ किया",
|
||||
"started_downloading_other": "{{count}} मॉडल डाउनलोड करना प्रारंभ किया",
|
||||
"install_failed": "इंस्टॉल विफल: {{message}}",
|
||||
"removing_cached": "कैश्ड संशोधन हटाये जा रहे हैं...",
|
||||
"resolving_metadata": "रेपो मेटाडेटा का समाधान",
|
||||
"retry_attempt": "पुनः प्रयास करें {{attempt}} — {{error}}",
|
||||
"connecting_hf": "हगिंगफेस से कनेक्ट हो रहा है...",
|
||||
"resolving_files_one": "{{count}} फ़ाइल का समाधान किया जा रहा है…",
|
||||
"resolving_files_other": "{{count}} फ़ाइलों का समाधान किया जा रहा है...",
|
||||
"resolving_files_active": "{{count}} फ़ाइल(फ़ाइलों) का समाधान किया जा रहा है… · {{file}}",
|
||||
"measuring": "मापना...",
|
||||
"files_progress": "{{done}}/{{total}} फ़ाइलें",
|
||||
"install_error": "इंस्टॉल विफल: {{error}}",
|
||||
"view_on_hf": "हगिंगफेस पर देखें",
|
||||
"install_btn": "स्थापित करें",
|
||||
"reinstall_btn": "पुनः स्थापित करें",
|
||||
"downloading": "डाउनलोड हो रहा है",
|
||||
"deleting": "हटाना",
|
||||
"working": "काम कर रहे हैं",
|
||||
"installed": "स्थापित",
|
||||
"not_installed": "स्थापित नहीं",
|
||||
"required_tag": "आवश्यक",
|
||||
"delete_btn": "हटाएँ",
|
||||
"hf_token_btn": "एचएफ टोकन",
|
||||
"hf_set_title": "तेज़ डाउनलोड के लिए हगिंगफेस टोकन सेट करें",
|
||||
"get_token": "टोकन प्राप्त करें →",
|
||||
"reco_installed_for": "{{device}} के लिए अनुशंसित बंडल स्थापित किया गया",
|
||||
"reco_for": "{{device}} के लिए अनुशंसित",
|
||||
"starting": "शुरू हो रहा है...",
|
||||
"required_size": "आवश्यक ~{{size}} GB",
|
||||
"all_size": "सभी ~{{size}} GB",
|
||||
"req_tag": "अनुरोध",
|
||||
"search_placeholder": "मॉडल खोजें…",
|
||||
"search_label": "मॉडल खोजें",
|
||||
"no_matches": "कोई भी मॉडल आपके फ़िल्टर से मेल नहीं खाता.",
|
||||
"sort_by": "{{column}} के अनुसार क्रमबद्ध करें",
|
||||
"column_model": "मॉडल",
|
||||
"column_role": "भूमिका",
|
||||
"column_size": "आकार",
|
||||
"column_status": "स्थिति",
|
||||
"ready_badge": "तैयार",
|
||||
"loading_badge": "लोड हो रहा है...",
|
||||
"idle_badge": "निष्क्रिय",
|
||||
"started_downloading_required_one": "{{count}} आवश्यक मॉडल डाउनलोड करना प्रारंभ किया",
|
||||
"started_downloading_required_other": "{{count}} आवश्यक मॉडल डाउनलोड करना प्रारंभ किया"
|
||||
},
|
||||
"enterprise_faq": {
|
||||
"q_internal_tools": "क्या मुझे आंतरिक उपकरणों के लिए लाइसेंस की आवश्यकता है?",
|
||||
"a_internal_tools": "आपके कर्मचारियों और ठेकेदारों द्वारा आंतरिक उपयोग एफएसएल के तहत एक अनुमत उद्देश्य है - किसी लाइसेंस की आवश्यकता नहीं है। जब आप किसी प्रतिस्पर्धी उत्पाद या सेवा (पुनर्विक्रय, होस्ट किए गए SaaS, व्हाइट-लेबल) के हिस्से के रूप में ओमनीवॉइस को दूसरों के लिए उपलब्ध कराते हैं तो एक वाणिज्यिक लाइसेंस की आवश्यकता होती है।",
|
||||
"q_try_before": "क्या मैं प्रतिबद्ध होने से पहले प्रयास कर सकता हूँ?",
|
||||
"a_try_before": "हाँ. पूरा ऐप एफएसएल के तहत मूल्यांकन के लिए स्थानीय स्तर पर डाउनलोड करने और चलाने के लिए मुफ़्त है। जब आप किसी व्यावसायिक तैनाती पर चर्चा करने के लिए तैयार हों, तो हमें ईमेल करें और हम विवरण पर मिलकर काम करेंगे।",
|
||||
"q_watermark": "वॉटरमार्क के बारे में क्या?",
|
||||
"a_watermark": "अदृश्य ऑडियोसील वॉटरमार्क डिफ़ॉल्ट रूप से एम्बेडेड है। वाणिज्यिक लाइसेंसधारी इसे सेटिंग्स → गोपनीयता में अक्षम कर सकते हैं। निःशुल्क/व्यक्तिगत उपयोग में हमेशा वॉटरमार्क शामिल होता है।",
|
||||
"q_apache": "क्या स्रोत कभी अपाचे 2.0 बन जाता है?",
|
||||
"a_apache": "हाँ. प्रत्येक रिलीज़ अपने प्रकाशन की दूसरी वर्षगांठ पर स्वचालित रूप से अपाचे लाइसेंस, संस्करण 2.0 में परिवर्तित हो जाती है। इसका मतलब है कि आज की रिलीज़ दो वर्षों में अपाचे 2.0 है, हमारी ओर से किसी कार्रवाई की आवश्यकता नहीं है - एफएसएल इसकी अपरिवर्तनीय गारंटी देता है।"
|
||||
},
|
||||
"gallery_extra": {
|
||||
"save_prompt": "इस ध्वनि प्रोफ़ाइल के लिए एक नाम दर्ज करें:",
|
||||
"saved_as_profile": "आवाज़ प्रोफ़ाइल के रूप में सहेजी गई!",
|
||||
"save_profile_failed": "प्रोफ़ाइल सहेजने में विफल",
|
||||
"download_failed": "डाउनलोड विफल: {{message}}",
|
||||
"trim_load_failed": "ट्रिमिंग के लिए ऑडियो लोड करने में विफल: {{message}}",
|
||||
"upload_crop_failed": "क्रॉप की गई आवाज अपलोड करने में विफल: {{message}}"
|
||||
},
|
||||
"dub_workflow": {
|
||||
"preparing_audio": "ऑडियो तैयार किया जा रहा है…",
|
||||
"preparing_video": "वीडियो तैयार किया जा रहा है...",
|
||||
"extracting_audio_scenes": "ऑडियो और दृश्य निकाले जा रहे हैं...",
|
||||
"transcribing_audio": "ऑडियो ट्रांसक्राइब किया जा रहा है...",
|
||||
"transcription_complete": "प्रतिलेखन पूर्ण",
|
||||
"upload_cancelled": "अपलोड रद्द कर दिया गया",
|
||||
"upload_failed": "अपलोड विफल: {{message}}",
|
||||
"downloading_video": "वीडियो डाउनलोड हो रहा है...",
|
||||
"ingested": "अंतर्ग्रहण {{url}}",
|
||||
"ingest_cancelled": "निगलना रद्द कर दिया गया",
|
||||
"ingest_failed": "URL अंतर्ग्रहण विफल: {{message}}",
|
||||
"retry_cancelled": "पुनः प्रयास रद्द कर दिया गया",
|
||||
"transcription_failed": "प्रतिलेखन विफल: {{message}}",
|
||||
"import_srt_no_job": "पहले एक वीडियो अपलोड या इंजेस्ट करें - इसमें उपशीर्षक संलग्न करना कोई काम नहीं है।",
|
||||
"imported_cues": "{{file}} से आयातित {{count}} संकेत",
|
||||
"skipped_malformed": "{{count}} छोड़ दिया गया (विकृत)",
|
||||
"dropped_overlap": "{{count}} गिरा दिया गया (ओवरलैप)",
|
||||
"clamped_to_duration": "{{count}} मीडिया की लंबाई से जुड़ा हुआ",
|
||||
"srt_import_failed": "SRT आयात विफल रहा",
|
||||
"cleaned_one": "साफ किया गया {{count}} टुकड़ा",
|
||||
"cleaned_other": "साफ किये गये {{count}} टुकड़े",
|
||||
"segments_clean": "खंड पहले से ही साफ़ हैं",
|
||||
"cleanup_failed": "सफ़ाई विफल: {{message}}",
|
||||
"cinematic_no_llm": "सिनेमाई गुणवत्ता के लिए एलएलएम की आवश्यकता होती है - सेट TRANSLATE_BASE_URL + TRANSLATE_API_KEY (ओलामा स्थानीय स्तर पर काम करता है)। तेजी से वापस गिरना।",
|
||||
"translate_errors": "{{errorCount}}/{{totalCount}} खंड विफल: {{firstError}}",
|
||||
"translated_segments": "अनूदित {{count}} खंड → {{lang}}",
|
||||
"translated_cinematic_suffix": "(सिनेमाई)",
|
||||
"translation_failed": "अनुवाद विफल: {{message}}",
|
||||
"regenerating": "{{count}} खंड(खंडों) को पुनर्जीवित किया जा रहा है…",
|
||||
"generating_dub": "डब उत्पन्न हो रहा है...",
|
||||
"generating_progress": "डब उत्पन्न हो रहा है… {{current}}/{{total}}",
|
||||
"generation_aborted": "पीढ़ी का पतन हो गया.",
|
||||
"dubbing_aborted": "डबिंग निरस्त कर दी गई",
|
||||
"generation_stream_ended": "जनरेशन स्ट्रीम पूर्ण होने से पहले समाप्त हो गई",
|
||||
"dub_complete": "डब पूरा हुआ",
|
||||
"stop_failed": "रोकने में विफल",
|
||||
"save_first": "कृपया पहले 'अपलोड और ट्रांसक्राइब' पर क्लिक करें ताकि वीडियो सहेजने से पहले सर्वर पर संसाधित हो जाए।",
|
||||
"project_saved": "प्रोजेक्ट सहेजा गया",
|
||||
"project_created": "प्रोजेक्ट बनाया गया",
|
||||
"save_failed": "सहेजना विफल: {{message}}",
|
||||
"opened_project": "खोला गया: {{name}}",
|
||||
"delete_project_confirm": "यह प्रोजेक्ट हटाएं? इसे असंपादित नहीं किया जा सकता है।",
|
||||
"project_deleted": "प्रोजेक्ट हटा दिया गया",
|
||||
"delete_history_confirm": "यह इतिहास आइटम हटाएँ?",
|
||||
"history_deleted": "इतिहास आइटम हटा दिया गया",
|
||||
"restored_state": "पिछली पीढ़ी की स्थिति बहाल की गई",
|
||||
"upgrading_preview": "{{count}} पूर्वावलोकन-गुणवत्ता खंड(खंडों) को पूर्ण गुणवत्ता में अपग्रेड किया जा रहा है..."
|
||||
},
|
||||
"tts_errors": {
|
||||
"enter_text": "कृपया पाठ दर्ज करें",
|
||||
"upload_or_select": "एक ऑडियो अपलोड करें या एक वॉयस प्रोफ़ाइल चुनें",
|
||||
"trim_hint": "ऑडियो {{duration}}s है - सर्वोत्तम क्लोनिंग के लिए ≤{{max}}s तक ट्रिम करें",
|
||||
"timeout": "जनरेशन का समय समाप्त हो गया - मॉडल अभी भी डाउनलोड हो रहा होगा। सेटिंग्स → लॉग जांचें, फिर पुनः प्रयास करें।",
|
||||
"error_prefix": "त्रुटि: {{message}}",
|
||||
"ignored_unsupported": "अनदेखा असमर्थित निर्देश: {{items}}",
|
||||
"ignored_duplicate": "अनदेखा (श्रेणी पहले से ही सेट): {{items}}"
|
||||
},
|
||||
"sharing": {
|
||||
"title": "साझाकरण और रिमोट एक्सेस",
|
||||
"help": "इस चल रहे ओमनीवॉइस इंस्टेंस को पुनः आरंभ किए बिना अपनी अन्य मशीनों पर प्रदर्शित करें। लूपबैक-ओनली डिफ़ॉल्ट है - जब तक आप इसे यहां चालू नहीं करते तब तक कुछ भी साझा नहीं किया जाता है।",
|
||||
"local_network": "स्थानीय नेटवर्क",
|
||||
"local_help": "एक बार के एक्सेस पिन के साथ अपने वाई-फ़ाई/ईथरनेट पर साझा करें। अन्य डिवाइस क्यूआर कोड को स्कैन करते हैं या लिंक खोलते हैं।",
|
||||
"ports_title": "बंदरगाह",
|
||||
"ports_help": "इन्हें स्टार्टअप पर पढ़े गए पर्यावरण चर के माध्यम से सेट किया जाता है। वेरिएबल सेट करके और ओमनीवॉइस को पुनरारंभ करके बैकएंड या यूआई पोर्ट बदलें।",
|
||||
"backend_port": "बैकएंड पोर्ट",
|
||||
"ui_port": "यूआई पोर्ट",
|
||||
"lan_share_port": "लैन-शेयर पोर्ट",
|
||||
"port_error": "1024 और 65535 के बीच एक पोर्ट दर्ज करें",
|
||||
"port_saved": "LAN-शेयर पोर्ट सहेजा गया - अगली बार जब आप साझाकरण सक्षम करते हैं तो लागू होता है",
|
||||
"port_save_failed": "पोर्ट सहेजा नहीं जा सका: {{message}}",
|
||||
"saving": "सहेजा जा रहा है...",
|
||||
"ports_note": "बैकएंड और यूआई पोर्ट पुनरारंभ पर लागू होते हैं। अगली बार जब आप साझाकरण सक्षम करते हैं तो LAN-शेयर पोर्ट लागू होता है।",
|
||||
"tailscale_title": "टेलस्केल (निजी रिमोट एक्सेस)",
|
||||
"tailscale_checking": "टेलस्केल की जाँच हो रही है...",
|
||||
"tailscale_absent": "टेलस्केल का पता नहीं चला. अपने निजी टेलनेट पर कहीं से भी ओमनीवॉइस तक सुरक्षित रूप से पहुंचने के लिए इसे इंस्टॉल करें।",
|
||||
"tailscale_install": "टेलस्केल स्थापित करें",
|
||||
"tailscale_running": "टेलस्केल चल रहा है. अपने निजी टेलनेट पर ओमनीवॉइस परोसें।",
|
||||
"tailscale_not_logged_in": "टेलस्केल स्थापित है लेकिन लॉग इन नहीं है। प्रारंभ करें और पहले टेलस्केल में साइन इन करें।",
|
||||
"tailscale_enabled": "टेलस्केल सर्व सक्षम",
|
||||
"tailscale_enable_failed": "टेलस्केल सक्षम नहीं किया जा सका",
|
||||
"tailscale_enable_error": "टेलस्केल सक्षम नहीं कर सका: {{message}}",
|
||||
"tailscale_disabled": "टेलस्केल सेवा अक्षम है",
|
||||
"tailscale_disable_failed": "टेलस्केल को अक्षम नहीं किया जा सका",
|
||||
"tailscale_disable_error": "टेलस्केल को अक्षम नहीं किया जा सका: {{message}}",
|
||||
"tailscale_enabling": "सक्षम किया जा रहा है...",
|
||||
"tailscale_enable_btn": "टेलस्केल सर्व सक्षम करें",
|
||||
"tailscale_disabling": "अक्षम किया जा रहा है...",
|
||||
"tailscale_disable_btn": "टेलस्केल सेवा बंद करो",
|
||||
"tailscale_copy": "लिंक कॉपी करें",
|
||||
"tailscale_open": "ब्राउज़र में खोलें",
|
||||
"copied": "नकल की गई",
|
||||
"tailscale_qr_alt": "टेलस्केल यूआरएल के लिए क्यूआर कोड"
|
||||
},
|
||||
"reportBug": {
|
||||
"label": "बग की रिपोर्ट करें",
|
||||
"title": "आपके ब्राउज़र में पहले से भरा हुआ GitHub इश्यूज़ पेज खोलता है। जब तक आप सबमिट पर क्लिक नहीं करते तब तक कुछ भी नहीं भेजा जाता है।"
|
||||
},
|
||||
"app": {
|
||||
"loading": "लोड हो रहा है...",
|
||||
"trimmed_loaded": "ट्रिम किया गया ऑडियो लोड किया गया",
|
||||
"toast_exported": "निर्यातित: {{name}}",
|
||||
"toast_export_failed": "निर्यात विफल: {{message}}",
|
||||
"toast_open_folder_failed": "फ़ोल्डर नहीं खुल सका: {{message}}",
|
||||
"toast_saving": "सहेजा जा रहा है {{name}}...",
|
||||
"toast_saved": "सहेजा गया: {{path}}",
|
||||
"toast_save_error": "त्रुटि सहेजें: {{message}}",
|
||||
"toast_processing": "प्रसंस्करण {{name}}...",
|
||||
"toast_downloaded": "डाउनलोड किया गया {{name}}",
|
||||
"toast_download_error": "डाउनलोड त्रुटि: {{message}}",
|
||||
"toast_upload_first": "कृपया पहले 'अपलोड और ट्रांसक्राइब' पर क्लिक करें ताकि वीडियो सहेजने से पहले सर्वर पर संसाधित हो जाए।",
|
||||
"toast_save_failed": "सहेजना विफल: {{message}}",
|
||||
"toast_opened": "खोला गया: {{name}}",
|
||||
"toast_project_deleted": "प्रोजेक्ट हटा दिया गया",
|
||||
"toast_restored_state": "पिछली पीढ़ी की स्थिति बहाल की गई",
|
||||
"toast_history_deleted": "इतिहास आइटम हटा दिया गया",
|
||||
"toast_flushed": "फ्लश - RAM {{ram}}G · VRAM {{vram}}G{{unloaded}}",
|
||||
"toast_model_unloaded": "· मॉडल उतार दिया गया",
|
||||
"toast_flush_failed": "फ्लश विफल: {{message}}",
|
||||
"toast_project_saved": "प्रोजेक्ट सहेजा गया",
|
||||
"toast_project_created": "प्रोजेक्ट बनाया गया"
|
||||
},
|
||||
"update": {
|
||||
"available": "अपडेट {{version}} उपलब्ध है",
|
||||
"install": "इंस्टॉल करें और पुनः आरंभ करें",
|
||||
"install_hint": "अपडेट डाउनलोड करें और नए संस्करण में पुनः आरंभ करें",
|
||||
"downloading": "अपडेट हो रहा है… {{pct}}%",
|
||||
"restart": "अपडेट करने के लिए पुनः आरंभ करें",
|
||||
"busy": "पहले अपनी डबिंग पूरी करें — फिर अपडेट इंस्टॉल करें।",
|
||||
"whats_new": "नया क्या है",
|
||||
"failed": "अद्यतन विफल रहा",
|
||||
"retry": "पुनः प्रयास करें",
|
||||
"dismiss": "ख़ारिज करें"
|
||||
},
|
||||
"archetypes": {
|
||||
"featured": "विशेष रुप से प्रदर्शित",
|
||||
@@ -945,5 +1577,25 @@
|
||||
"facet_accent": "उच्चारण",
|
||||
"facet_lang": "भाषा",
|
||||
"facet_whisper": "कानाफूसी"
|
||||
},
|
||||
"support": {
|
||||
"tab_support": "समर्थन",
|
||||
"tab_license": "वाणिज्यिक लाइसेंस",
|
||||
"toggle_label": "समर्थन या वाणिज्यिक लाइसेंस",
|
||||
"other_ways": "मदद करने के अन्य तरीके",
|
||||
"star_github": "GitHub पर स्टार लगाएं",
|
||||
"join_discord": "कलह में शामिल हों"
|
||||
},
|
||||
"updates": {
|
||||
"tab": "अद्यतन",
|
||||
"up_to_date": "अद्यतित · v{{version}}",
|
||||
"check_now": "अभी जांचें",
|
||||
"releases": "रिलीज़",
|
||||
"current": "वर्तमान",
|
||||
"prerelease": "पूर्वावलोकन",
|
||||
"loading": "रिलीज़ लोड हो रही हैं...",
|
||||
"none": "कोई रिलीज़ नहीं मिली",
|
||||
"load_error": "रिलीज़ लोड नहीं हो सकी (ऑफ़लाइन?)",
|
||||
"retry_load": "पुनः प्रयास करें"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,4 @@
|
||||
{
|
||||
"update": {
|
||||
"available": "Pembaruan {{version}} tersedia",
|
||||
"install": "Instal dan mulai ulang",
|
||||
"install_hint": "Unduh pembaruan dan mulai ulang ke versi baru",
|
||||
"downloading": "Memperbarui… {{pct}}%",
|
||||
"restart": "Mulai ulang untuk memperbarui",
|
||||
"busy": "Selesaikan sulih suara Anda dulu — lalu instal pembaruannya."
|
||||
},
|
||||
"nav": {
|
||||
"clone": "Kloning",
|
||||
"design": "Desain",
|
||||
@@ -16,7 +8,10 @@
|
||||
"launchpad": "landasan peluncuran",
|
||||
"gallery": "Galeri",
|
||||
"transcripts": "Transkrip",
|
||||
"omnidrive": "OmniDrive"
|
||||
"omnidrive": "OmniDrive",
|
||||
"move_rail_right": "Pindahkan rel ke kanan",
|
||||
"move_rail_left": "Pindahkan rel ke kiri",
|
||||
"flip_rail": "Balik sisi rel"
|
||||
},
|
||||
"settings": {
|
||||
"general": "Umum",
|
||||
@@ -45,7 +40,40 @@
|
||||
"ffmpeg_missing": "Tidak ditemukan",
|
||||
"ffmpeg_current": "Jalur saat ini",
|
||||
"ffmpeg_desc": "Tetapkan jalur ffmpeg khusus jika deteksi otomatis gagal.",
|
||||
"ffmpeg_saved": "Kumpulan jalur FFmpeg — mulai ulang backend untuk menerapkan."
|
||||
"ffmpeg_saved": "Kumpulan jalur FFmpeg — mulai ulang backend untuk menerapkan.",
|
||||
"diagnostics_copied": "Diagnostik disalin — tempelkan ke laporan masalah Anda.",
|
||||
"updater_desktop": "Updater hanya berjalan di aplikasi desktop.",
|
||||
"latest_version": "Anda menggunakan versi terbaru.",
|
||||
"logs_load_failed": "Gagal memuat log: {{message}}",
|
||||
"clear_frontend_confirm": "Hapus buffer log frontend dalam memori?",
|
||||
"clear_frontend_title": "Hapus log",
|
||||
"frontend_logs_cleared": "Log frontend dihapus",
|
||||
"clear_tauri_confirm": "Potong file log sisi Tauri? OS akan terus menulis entri baru.",
|
||||
"clear_tauri_title": "Hapus log Tauri",
|
||||
"nothing_to_clear": "Tidak ada yang perlu dihapus — belum ada file log Tauri di disk.",
|
||||
"cleared_tauri_one": "Menghapus file log {{count}} Tauri",
|
||||
"cleared_tauri_other": "Menghapus file log {{count}} Tauri",
|
||||
"clear_tauri_failed": "Gagal menghapus log Tauri: {{message}}",
|
||||
"clear_backend_confirm": "Hapus runtime backend + log kerusakan? Hal ini tidak dapat dibatalkan.",
|
||||
"clear_backend_title": "Hapus log",
|
||||
"backend_logs_cleared": "Log backend dihapus",
|
||||
"clear_backend_failed": "Gagal menghapus log",
|
||||
"copy_failed": "Penyalinan gagal: {{message}}",
|
||||
"update_check_failed": "Pemeriksaan pembaruan gagal: {{message}}",
|
||||
"save_failed": "Gagal menyimpan: {{message}}",
|
||||
"clear_failed": "Hapus gagal: {{message}}",
|
||||
"engine_switched": "{{family}} → {{engine}}",
|
||||
"channel_set_failed": "Gagal menyetel saluran: {{message}}",
|
||||
"updater_downloading": "Mengunduh {{version}}…",
|
||||
"updater_installed": "Dipasang — diluncurkan kembali.",
|
||||
"updater_available_title": "Pembaruan tersedia",
|
||||
"updater_available_body": "Versi {{version}} tersedia.\n\n{{notes}}\n\nUnduh dan instal sekarang?",
|
||||
"updater_notes_fallback": "Lihat catatan rilis di GitHub.",
|
||||
"shortcut_load_failed": "Tidak dapat memuat pintasan: {{message}}",
|
||||
"shortcut_set": "Pintasan dikte disetel ke {{shortcut}}",
|
||||
"shortcut_register_failed": "Tidak dapat mendaftar: {{message}}",
|
||||
"shortcut_reset": "Atur ulang ke default",
|
||||
"shortcut_reset_failed": "Penyetelan ulang gagal: {{message}}"
|
||||
},
|
||||
"bootstrap": {
|
||||
"title": "OmniVoice Studio",
|
||||
@@ -69,7 +97,13 @@
|
||||
"waiting_output": "Menunggu hasil…",
|
||||
"auto_detect": "Deteksi otomatis",
|
||||
"suggest_lang": "Ubah ke Bahasa Indonesia?",
|
||||
"select_lang": "Bahasa:"
|
||||
"select_lang": "Bahasa:",
|
||||
"region_global": "Global (langsung)",
|
||||
"region_china": "Cina (cermin)",
|
||||
"region_russia": "Rusia (cermin)",
|
||||
"region_restricted": "Dibatasi (cermin)",
|
||||
"unknown_error": "Kesalahan tidak diketahui",
|
||||
"retrying": "Mencoba lagi…"
|
||||
},
|
||||
"stories": {
|
||||
"title": "Editor Cerita",
|
||||
@@ -158,7 +192,17 @@
|
||||
"reload": "Paksa Muat Ulang UI",
|
||||
"backend": "Bagian belakang",
|
||||
"frontend": "Bagian depan",
|
||||
"tauri": "Tauri"
|
||||
"tauri": "Tauri",
|
||||
"cancelOp": "Batalkan operasi",
|
||||
"dismiss": "Singkirkan",
|
||||
"dismissStatus": "Tutup statusnya",
|
||||
"search": "Cari…",
|
||||
"no_matches": "Tidak ada kecocokan",
|
||||
"recent_and_popular": "Terbaru & Populer",
|
||||
"popular_label": "Populer",
|
||||
"showing_of": "Menampilkan {{shown}} dari {{total}}. Ketik untuk mencari…",
|
||||
"yes": "Ya",
|
||||
"no": "Tidak"
|
||||
},
|
||||
"launchpad": {
|
||||
"greeting": "halo",
|
||||
@@ -180,7 +224,16 @@
|
||||
"locked": "TERKUNCI",
|
||||
"open": "Buka",
|
||||
"try_it": "Cobalah",
|
||||
"audio_only": "Hanya Audio"
|
||||
"audio_only": "Hanya Audio",
|
||||
"stories_title": "Cerita",
|
||||
"stories_desc": "Buku audio multi-suara — masukkan karakter Anda, masukkan skrip, ekspor per bab.",
|
||||
"gallery_title": "Galeri Suara",
|
||||
"gallery_desc": "Telusuri suara rancangan siap pakai berdasarkan aksen, usia, dan gaya — tanpa penyiapan.",
|
||||
"transcripts_title": "Transkrip",
|
||||
"transcripts_desc": "Ubah audio atau video menjadi teks yang dapat diedit dan dicari — dalam 646 bahasa.",
|
||||
"recent_files": "File terbaru",
|
||||
"view_all_files": "Lihat semua file",
|
||||
"file": "Mengajukan"
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "Cepat",
|
||||
@@ -271,11 +324,6 @@
|
||||
"outputs": "Keluaran",
|
||||
"crash_log": "Catatan kerusakan",
|
||||
"update_endpoint": "Perbarui titik akhir",
|
||||
"update_channel": "Saluran pembaruan",
|
||||
"channel_stable": "Stabil",
|
||||
"channel_preview": "Pratinjau",
|
||||
"channel_set": "Saluran pembaruan disetel ke {{channel}}",
|
||||
"channel_preview_hint": "Pratinjau mengikuti build main terbaru — fitur lebih baru, lebih sedikit diuji. Kembali ke stabil jika ada rilis stabil yang lebih baru.",
|
||||
"yes": "ya",
|
||||
"no": "tidak",
|
||||
"web_preview": "pratinjau web",
|
||||
@@ -284,7 +332,12 @@
|
||||
"copy_diagnostics": "Salin diagnostik",
|
||||
"github": "OmniVoice di GitHub",
|
||||
"model_card": "Kartu model",
|
||||
"commercial_license": "Lisensi Komersial"
|
||||
"commercial_license": "Lisensi Komersial",
|
||||
"update_channel": "Saluran pembaruan",
|
||||
"channel_stable": "Stabil",
|
||||
"channel_preview": "Pratinjau",
|
||||
"channel_set": "Saluran pembaruan disetel ke {{channel}}",
|
||||
"channel_preview_hint": "Pratinjau mengikuti build main terbaru — fitur lebih baru, lebih sedikit diuji. Kembali ke stabil jika ada rilis stabil yang lebih baru."
|
||||
},
|
||||
"privacy": {
|
||||
"desc": "Semuanya berjalan di <1>mesin ini</1>. Audio, video, dan transkrip Anda tidak pernah keluar dari komputer kecuali Anda secara eksplisit menggunakan penerjemah online (Google, DeepL, dll.) atau mendorong ke HuggingFace.",
|
||||
@@ -340,7 +393,30 @@
|
||||
"unavailable": "tidak tersedia",
|
||||
"use": "Gunakan",
|
||||
"loading": "Memuat mesin…",
|
||||
"refresh": "Segarkan"
|
||||
"refresh": "Segarkan",
|
||||
"matrixTitle": "Matriks Kompatibilitas Mesin",
|
||||
"loadFailed": "Gagal memuat mesin: {{message}}",
|
||||
"couldNotLoad": "Tidak dapat memuat mesin: {{message}}",
|
||||
"retry": "Coba lagi",
|
||||
"activeEngine": "Aktif {{family}}: {{engine}}",
|
||||
"engineCompatLabel": "{{family}} kompatibilitas mesin",
|
||||
"active": "aktif",
|
||||
"whyUnavailable": "Mengapa tidak tersedia?",
|
||||
"lastError": "Kesalahan terakhir: {{error}}",
|
||||
"installedAndReady": "Terpasang dan siap",
|
||||
"notInstalled": "Tidak dipasang",
|
||||
"available": "Tersedia",
|
||||
"subprocessTitle": "Berjalan dalam subprosesnya sendiri + venv",
|
||||
"inProcessTitle": "Berjalan dalam proses OmniVoice Python",
|
||||
"testEngine": "Mesin uji",
|
||||
"testing": "Pengujian…",
|
||||
"recheck": "Periksa kembali",
|
||||
"rechecking": "Memeriksa ulang…",
|
||||
"latencyMs": "{{ms}} ms",
|
||||
"failed": "gagal",
|
||||
"acceptLicense": "Terima lisensi",
|
||||
"noBackends": "Tidak ada backend yang terdaftar.",
|
||||
"switch_failed": "Gagal mengganti mesin"
|
||||
},
|
||||
"capture": {
|
||||
"desc": "Tombol pintas global hanya berfungsi di aplikasi desktop. UI web menggunakan pintasan <1>Ctrl+Shift+Space</1> dalam halaman saat jendela memiliki fokus.",
|
||||
@@ -352,13 +428,55 @@
|
||||
"record_shortcut": "Rekam pintasan",
|
||||
"recording": "Merekam…",
|
||||
"save": "Simpan",
|
||||
"reset_default": "Atur ulang ke default"
|
||||
"reset_default": "Atur ulang ke default",
|
||||
"listening_label": "Mendengarkan…",
|
||||
"transcribing_label": "Mentranskripsikan…",
|
||||
"pasted": "Ditempel",
|
||||
"no_speech": "Tidak ada ucapan yang terdeteksi",
|
||||
"mic_denied": "Akses mikrofon ditolak",
|
||||
"mic_denied_toast": "Akses mikrofon ditolak. {{hint}}",
|
||||
"mic_hint_mac": "macOS: buka Pengaturan Sistem → Privasi & Keamanan → Mikrofon dan aktifkan OmniVoice.",
|
||||
"mic_hint_windows": "Windows: buka Pengaturan → Privasi & keamanan → Mikrofon dan izinkan OmniVoice.",
|
||||
"mic_hint_linux": "Linux: periksa apakah pengguna Anda ada di grup audio dan WebView memiliki akses mikrofon.",
|
||||
"transcription_failed": "Transkripsi gagal: {{message}}"
|
||||
},
|
||||
"logs": {
|
||||
"no_tauri_log": "Belum ada Tauri yang masuk ke disk — luncurkan melalui versi desktop untuk menghasilkannya",
|
||||
"empty_frontend": "Belum ada entri konsol frontend yang diambil. Berinteraksi dengan aplikasi — setiap konsol.* akan muncul di sini.",
|
||||
"empty_tauri": "Tidak ada log Tauri yang tersedia. Berjalan di shell desktop saja.",
|
||||
"empty_backend": "Log waktu proses kosong. Aktivitas akan muncul di sini saat backend mencatatnya."
|
||||
"empty_backend": "Log waktu proses kosong. Aktivitas akan muncul di sini saat backend mencatatnya.",
|
||||
"title": "Log",
|
||||
"source_backend": "Bagian belakang",
|
||||
"source_frontend": "Bagian depan",
|
||||
"source_tauri": "Tauri",
|
||||
"expand": "Perluas log",
|
||||
"collapse": "Ciutkan log",
|
||||
"expand_aria": "Luaskan panel log",
|
||||
"collapse_aria": "Ciutkan panel log",
|
||||
"drag_resize": "Seret untuk mengubah ukuran",
|
||||
"refresh": "Segarkan",
|
||||
"refresh_aria": "Segarkan log",
|
||||
"copy_visible": "Salin log yang terlihat",
|
||||
"copy_visible_aria": "Salin log yang terlihat",
|
||||
"clear": "Jelas",
|
||||
"clear_aria": "Hapus catatan",
|
||||
"report_issue": "Laporkan masalah (salin diagnostik)",
|
||||
"report_issue_aria": "Laporkan masalah",
|
||||
"close": "Tutup",
|
||||
"close_aria": "Tutup panel log",
|
||||
"join_discord": "Bergabunglah dengan Perselisihan kami",
|
||||
"join_discord_aria": "Bergabunglah dengan komunitas Perselisihan kami",
|
||||
"support_project": "Dukung proyek ini",
|
||||
"support_project_aria": "Dukung proyek ini",
|
||||
"empty_frontend_short": "Belum ada keluaran konsol frontend.",
|
||||
"empty_lines": "Tidak ada garis.",
|
||||
"all_clear": "✅ Semua aman — tidak ada masalah yang terdeteksi",
|
||||
"log_cleared": "{{source}} log dihapus",
|
||||
"clear_failed": "Hapus gagal: {{message}}",
|
||||
"log_copied": "Menyalin {{source}} log",
|
||||
"copy_failed": "Penyalinan gagal: {{message}}",
|
||||
"report_copied": "Laporan diagnostik disalin — tempelkan ke masalah GitHub.",
|
||||
"report_failed": "Laporan gagal: {{message}}"
|
||||
},
|
||||
"voice_profile": {
|
||||
"test_text": "Halo - ini adalah ujian untuk suara ini.",
|
||||
@@ -526,7 +644,17 @@
|
||||
"install_already": "{{engine}} sudah diinstal",
|
||||
"install_ok": "{{engine}} terpasang",
|
||||
"install_failed": "Pemasangan gagal: {{message}}",
|
||||
"prep_elapsed": "{{time}} telah berlalu"
|
||||
"prep_elapsed": "{{time}} telah berlalu",
|
||||
"add_language": "Tambahkan bahasa",
|
||||
"search_languages": "Cari bahasa…",
|
||||
"languages_selected_one": "{{count}} bahasa dipilih",
|
||||
"languages_selected_other": "{{count}} bahasa dipilih",
|
||||
"more_to_narrow": "+{{count}} more — ketik untuk mempersempit",
|
||||
"no_matches": "Tidak ada kecocokan",
|
||||
"diagnostic_copied": "Diagnostik disalin",
|
||||
"copy_failed": "Penyalinan gagal",
|
||||
"open_docs": "Buka dokumen",
|
||||
"copy_diagnostic": "Salin diagnostik"
|
||||
},
|
||||
"glossary": {
|
||||
"title": "Glosarium",
|
||||
@@ -601,7 +729,17 @@
|
||||
"more_actions_title": "Lebih banyak tindakan",
|
||||
"speaker_pick": "Pilih…",
|
||||
"speaker_title_detected": "Speaker — pilih dari yang terdeteksi, atau ketikkan nama khusus",
|
||||
"speaker_title_custom": "Pembicara — ketikkan nama (tidak ada klon diarisasi yang terdeteksi)"
|
||||
"speaker_title_custom": "Pembicara — ketikkan nama (tidak ada klon diarisasi yang terdeteksi)",
|
||||
"time_edit_title": "Klik untuk mengedit waktu mulai (m:ss.s). Enter untuk melakukan, Esc untuk membatalkan.",
|
||||
"fit_fits": "Cocok",
|
||||
"fit_fits_title": "Audio dengan kecepatan alami pas di dalam slot.",
|
||||
"fit_overflows": "Meluap +{{seconds}}s",
|
||||
"fit_overflows_title": "Teks yang diterjemahkan lebih panjang dari slot aslinya sebanyak {{seconds}}s. Audionya terpotong dengan keras; persingkat teks atau alihkan Timing ke \"Regangkan Video\".",
|
||||
"fit_stretched": "Video {{ratio}}×",
|
||||
"fit_stretched_title": "Mode Peregangan Video: video segmen ini diperlambat ke {{ratio}}× agar sesuai dengan audio sulih suara alami.",
|
||||
"fit_compressed_title": "Audio TTS menempati {{pct}}% dari slot — sangat terkompresi.",
|
||||
"fit_audio_title": "Audio pas di dalam slot.",
|
||||
"fit_ratio_title": "Audio TTS adalah {{pct}}% dari slot."
|
||||
},
|
||||
"voice": {
|
||||
"personality": "Kepribadian",
|
||||
@@ -696,10 +834,24 @@
|
||||
"status_running": "berlari",
|
||||
"status_done": "selesai",
|
||||
"status_failed": "gagal",
|
||||
"status_cancelled": "dibatalkan"
|
||||
"status_cancelled": "dibatalkan",
|
||||
"add_to_queue_title": "Tambahkan Video ke Antrean",
|
||||
"drop_hint_text": "Letakkan file video di sini atau klik untuk menelusuri",
|
||||
"drop_formats": "MP4 · MOV · MKV · WEBM",
|
||||
"files_kicker": "FILE ({{count}})",
|
||||
"file_size_mb": "{{size}} MB",
|
||||
"target_languages": "BAHASA TARGET",
|
||||
"voice_kicker": "SUARA",
|
||||
"default_option": "Bawaan",
|
||||
"clone_profiles": "Profil Klon",
|
||||
"presets": "Preset",
|
||||
"preserve_bg": "Pertahankan audio latar belakang (musik/FX)",
|
||||
"estimate": "{{videos}} video × {{langs}} bahasa = {{jobs}} pekerjaan",
|
||||
"select_files_langs": "Pilih file dan bahasa",
|
||||
"add_to_queue": "Tambahkan ke Antrean"
|
||||
},
|
||||
"gallery": {
|
||||
"title": "Galeri",
|
||||
"title": "OmniVoice Galeri",
|
||||
"search_placeholder": "Telusuri YouTube…",
|
||||
"all_voices": "Semua Suara ({{count}})",
|
||||
"no_voices": "Belum ada suara",
|
||||
@@ -714,6 +866,14 @@
|
||||
"youtube_results": "Hasil YouTube ({{count}})",
|
||||
"clone_profile": "Profil klon",
|
||||
"crop_audio": "Pangkas audio",
|
||||
"cat_disney": "disney",
|
||||
"cat_anime": "anime",
|
||||
"cat_marvel": "Keajaiban/DC",
|
||||
"cat_celebs": "Selebriti",
|
||||
"cat_politicians": "Politisi",
|
||||
"cat_news": "Pembawa Berita",
|
||||
"cat_gaming": "Permainan",
|
||||
"cat_books": "Buku/Film",
|
||||
"subtitle": "Ratusan desain suara siap pakai — pilih satu dan mulai.",
|
||||
"zone_archetypes": "Arketipe",
|
||||
"zone_imports": "Impor Saya",
|
||||
@@ -818,7 +978,6 @@
|
||||
"badge": "Lisensi Komersial",
|
||||
"hero_title": "Kirimkan suara AI dalam produksi",
|
||||
"hero_desc": "OmniVoice Studio tersedia sumbernya di bawah Lisensi Sumber Fungsional (FSL). Sebagian besar pengguna dapat mengevaluasi, membuat prototipe, dan bahkan menerapkan secara internal tanpa perjanjian komersial. Anda memerlukan lisensi komersial hanya jika Anda membuat produk atau layanan pesaing, atau jika kasus penggunaan Anda berada di luar batasan FSL.",
|
||||
"hero_note": "Membangun produk atau layanan pesaing atau menerapkannya dalam skala besar (misalnya, menyajikan API bayar per penggunaan) memerlukan lisensi komersial. Tingkatan harga akan segera hadir — hubungi kami sementara itu.",
|
||||
"why_title": "Mengapa bisnis memilih OmniVoice",
|
||||
"pricing_title": "Harga",
|
||||
"faq_title": "Pertanyaan umum",
|
||||
@@ -838,7 +997,8 @@
|
||||
"benefit_source": "Inti yang tersedia sumber",
|
||||
"benefit_source_desc": "Visibilitas penuh ke dalam tumpukan. Audit, fork, dan adaptasi sesuai ketentuan lisensi.",
|
||||
"benefit_lang": "646 bahasa",
|
||||
"benefit_lang_desc": "Transkripsikan, terjemahkan, dan sulih suara dalam 646 bahasa dengan kualitas setingkat manusia."
|
||||
"benefit_lang_desc": "Transkripsikan, terjemahkan, dan sulih suara dalam 646 bahasa dengan kualitas setingkat manusia.",
|
||||
"hero_note": "Penggunaan internal — bahkan dalam skala besar — gratis di bawah FSL; lisensi komersial hanya diperlukan untuk menawarkan OmniVoice kepada orang lain sebagai produk atau layanan pesaing (API yang dihosting atau bayar per penggunaan, aplikasi yang dijual kembali atau diberi label putih). Tingkatan harga akan segera hadir — hubungi kami sementara ini."
|
||||
},
|
||||
"exportModal": {
|
||||
"export": "Ekspor",
|
||||
@@ -927,7 +1087,479 @@
|
||||
"dubbing_title": "Lihat aksi sulih suara",
|
||||
"dubbing_sync": "Pemutaran yang disinkronkan",
|
||||
"dubbing_picker": "Coba bahasa lain:",
|
||||
"dubbing_cta": "Jalankan ini di video Anda sendiri →"
|
||||
"dubbing_cta": "Jalankan ini di video Anda sendiri →",
|
||||
"dubbing_loading": "Memuat demo sulih suara…",
|
||||
"dubbing_dismiss": "Tolak demo sulih suara",
|
||||
"original_tag": "asli",
|
||||
"dubbed_tag": "dijuluki",
|
||||
"script_conversational": "Percakapan",
|
||||
"script_technical": "Kosakata teknis",
|
||||
"script_french": "Non-Inggris (Prancis)",
|
||||
"aria_pause": "Jeda {{label}}",
|
||||
"aria_hear": "Dengarkan {{label}}",
|
||||
"aria_replay": "Putar ulang {{label}} melalui transcriber"
|
||||
},
|
||||
"direction": {
|
||||
"title": "Arah untuk segmen #{{id}}",
|
||||
"desc": "Beritahukan kepada saluran pipa bagaimana seharusnya saluran ini terasa. Karya bahasa Inggris biasa — sistem memetakan kata-kata Anda ke dalam taksonomi yang stabil (energi / emosi / kecepatan / keintiman / formalitas), lalu merangkai taksonomi melalui terjemahan Sinematik, TTS, dan slot-fit.",
|
||||
"label": "Arah",
|
||||
"lineHint": "Baris: \"{{text}}\"",
|
||||
"placeholder": "misalnya mendesak dan terkejut / hangat, penuh harapan / berbisik, intim",
|
||||
"previewParse": "Pratinjau parse",
|
||||
"previewFailed": "Pratinjau gagal: {{message}}",
|
||||
"clear": "Jelas",
|
||||
"cancel": "Batalkan",
|
||||
"saveDirection": "Simpan arah",
|
||||
"ttsInstruct": "TTS menginstruksikan:",
|
||||
"nothingParsed": "— (tidak ada yang diuraikan)",
|
||||
"translateHint": "Terjemahkan petunjuk:",
|
||||
"rateBias": "Bias nilai:",
|
||||
"speedsUp": "mempercepat",
|
||||
"slowsDown": "melambat",
|
||||
"taxonomyTokens": "token taksonomi"
|
||||
},
|
||||
"errors": {
|
||||
"title": "Tab ini menemui hambatan.",
|
||||
"desc": "Jangan khawatir — aplikasi lainnya masih berfungsi. Anda dapat berpindah tab, atau coba lagi di bawah.",
|
||||
"tryAgain": "Coba lagi",
|
||||
"openDocs": "Buka dokumen untuk kesalahan ini"
|
||||
},
|
||||
"keyboard": {
|
||||
"title": "Pintasan keyboard",
|
||||
"footer": "Tekan <1>?</1> kapan saja untuk membuka ini.",
|
||||
"or": "atau",
|
||||
"nav": "Navigasi",
|
||||
"nav_cheatsheet": "Tunjukkan lembar contekan ini",
|
||||
"nav_closeModal": "Tutup modal / batalkan",
|
||||
"nav_save": "Simpan proyek/komit trim",
|
||||
"segmentEditor": "Editor segmen",
|
||||
"seg_split": "Pisahkan segmen di kursor",
|
||||
"seg_merge": "Gabungkan dengan segmen berikutnya",
|
||||
"seg_undo": "Membatalkan",
|
||||
"seg_redo": "Ulangi",
|
||||
"seg_click": "Tindakan utama",
|
||||
"seg_shiftClick": "Pilihan rentang",
|
||||
"trimmer": "Pemangkas audio",
|
||||
"trim_playPause": "Pratinjau putar / jeda",
|
||||
"trim_nudgeStart": "Dorongan untuk memulai pegangan",
|
||||
"trim_nudgeEnd": "Pegangan ujung dorong",
|
||||
"trim_fineNudge": "Dorongan yang bagus",
|
||||
"trim_coarseNudge": "Dorongan kasar",
|
||||
"trim_zoomIn": "Memperbesar / memperkecil",
|
||||
"trim_fitAll": "Cocok untuk semua / Pilihan pas",
|
||||
"trim_confirm": "Konfirmasikan pemangkasan",
|
||||
"dub": "sulih suara",
|
||||
"dub_generate": "Hasilkan sulih suara",
|
||||
"dub_sidebar": "Alihkan bilah sisi"
|
||||
},
|
||||
"network": {
|
||||
"sharing_on_title": "Berbagi di — klik untuk detailnya",
|
||||
"share_on_network": "Bagikan di jaringan Anda",
|
||||
"switching": "Beralih…",
|
||||
"network": "Jaringan",
|
||||
"local": "Lokal",
|
||||
"share_confirm_title": "Bagikan di jaringan Anda?",
|
||||
"share_confirm_hint": "Perangkat lain di Wi-Fi/Ethernet Anda akan dapat menjangkau OmniVoice menggunakan PIN akses yang ditampilkan setelah aktif.",
|
||||
"enabling": "Mengaktifkan…",
|
||||
"enable": "Aktifkan",
|
||||
"shared_title": "Dibagikan di jaringan Anda",
|
||||
"no_interface": "Tidak ada antarmuka jaringan yang dapat dijangkau — sambungkan ke Wi-Fi/Ethernet.",
|
||||
"copy_link": "Salin tautan",
|
||||
"open_in_browser": "Buka di peramban",
|
||||
"qr_alt": "QR untuk {{ip}}",
|
||||
"pin": "PIN:",
|
||||
"stop_sharing": "Berhenti berbagi",
|
||||
"copied": "Disalin",
|
||||
"enable_error": "Tidak dapat mengaktifkan berbagi: {{message}}",
|
||||
"disable_error": "Tidak dapat menonaktifkan: {{message}}"
|
||||
},
|
||||
"readiness": {
|
||||
"checking_system": "Memeriksa sistem…",
|
||||
"all_ready": "Semua sistem siap",
|
||||
"system_readiness": "Kesiapan Sistem",
|
||||
"asr_model": "Model ASR",
|
||||
"loaded_ready": "Sudah terisi dan siap",
|
||||
"loading_first_run": "Memuat… (ini mungkin memakan waktu 1-2 menit saat pertama kali dijalankan)",
|
||||
"failed_to_load": "Gagal memuat",
|
||||
"not_loaded_yet": "Belum dimuat — akan dimuat pada transkripsi pertama",
|
||||
"error_check_logs": "Kesalahan: {{error}}. Periksa log dan coba mulai ulang.",
|
||||
"check_logs_restart": "Periksa log untuk mengetahui kesalahan pemuatan model. Coba mulai ulang.",
|
||||
"llm_cinematic": "LLM (Sinematik)",
|
||||
"llm_configure": "Konfigurasikan TRANSLATE_BASE_URL untuk kualitas terjemahan Sinematik",
|
||||
"llm_set_env": "Tetapkan variabel lingkungan TRANSLATE_BASE_URL dan TRANSLATE_API_KEY. Bekerja dengan Ollama, OpenAI, LM Studio, dll.",
|
||||
"llm_optional": "Opsional — atur TRANSLATE_BASE_URL untuk kualitas Sinematik"
|
||||
},
|
||||
"license": {
|
||||
"title": "Supertonic-3 — Penerimaan Lisensi",
|
||||
"intro": "Supertonic-3 dikirimkan di bawah dua lisensi berbeda. Harap tinjau keduanya sebelum mengaktifkan mesin.",
|
||||
"sdk_heading": "Kode SDK · MIT",
|
||||
"sdk_desc": "SDK inferensi Python (supertonic) berlisensi MIT. Penggunaan permisif, termasuk komersial.",
|
||||
"read_mit": "Baca lisensi MIT →",
|
||||
"model_heading": "Bobot Model · OpenRAIL-M",
|
||||
"model_desc": "Anak timbangan model Supertonic-3 dirilis di bawah lisensi OpenRAIL-M. Lisensi ini membatasi penggunaan untuk tujuan yang tidak berbahaya — lihat lisensi tertaut untuk mengetahui rangkaian lengkap pembatasan berbasis penggunaan.",
|
||||
"read_openrail": "Baca lisensi OpenRAIL-M →",
|
||||
"footer": "Mengklik Terima mencatat penerimaan Anda di pengaturan lokal OmniVoice dan mengaktifkan mesin. Penerimaan Anda hanya disimpan di mesin ini — tidak ada yang dilaporkan ke Supertone Inc. atau pihak ketiga mana pun.",
|
||||
"saving": "Menyimpan…",
|
||||
"accept": "Terima",
|
||||
"accepted_toast": "Lisensi Supertonic-3 diterima.",
|
||||
"accept_error": "Gagal mencatat penerimaan lisensi: {{message}}"
|
||||
},
|
||||
"voicePreview": {
|
||||
"title": "Pratinjau Suara",
|
||||
"close": "Tutup pratinjau",
|
||||
"default_text": "Halo! Ini adalah pratinjau suara saya.",
|
||||
"default_voice": "Suara bawaan",
|
||||
"clone_profiles": "Profil Klon",
|
||||
"designed_voices": "Suara yang Dirancang",
|
||||
"presets": "Preset",
|
||||
"placeholder": "Ketik sesuatu untuk didengar…",
|
||||
"stop": "Berhenti",
|
||||
"regenerate": "Regenerasi",
|
||||
"preview": "Pratinjau",
|
||||
"hint": "8 langkah · pratinjau cepat"
|
||||
},
|
||||
"header": {
|
||||
"kicker_studio": "Studio",
|
||||
"kicker_library": "Perpustakaan",
|
||||
"kicker_preferences": "Preferensi",
|
||||
"kicker_licensing": "Perizinan",
|
||||
"label_launchpad": "landasan peluncuran",
|
||||
"label_clone": "Klon Suara",
|
||||
"label_design": "Desain Suara",
|
||||
"label_dub": "Sulih suara",
|
||||
"label_projects": "OmniDrive",
|
||||
"label_gallery": "Galeri",
|
||||
"label_transcriptions": "Transkripsi",
|
||||
"label_settings": "Pengaturan",
|
||||
"label_enterprise": "Lisensi Komersial",
|
||||
"status_ready": "Siap",
|
||||
"status_loading": "Memuat…",
|
||||
"status_idle": "Menganggur",
|
||||
"memory_management": "Manajemen memori",
|
||||
"flush": "Siram",
|
||||
"loaded_models": "Model yang Dimuat",
|
||||
"no_models": "Tidak ada model yang dimuat",
|
||||
"unload": "Bongkar",
|
||||
"flush_caches": "Siram cache",
|
||||
"unload_all_flush": "Bongkar semua + siram"
|
||||
},
|
||||
"sidebar": {
|
||||
"tab_drive": "Berkendara",
|
||||
"tab_history": "Sejarah",
|
||||
"tab_exports": "Ekspor",
|
||||
"save_project": "Simpan Proyek Dub",
|
||||
"save_new_project": "Simpan sebagai Proyek Dub Baru",
|
||||
"dub_projects": "Proyek Sulih Suara",
|
||||
"voice_clones": "Klon Suara",
|
||||
"designed_voices": "Suara yang Dirancang",
|
||||
"no_dub_projects": "Tidak ada proyek sulih suara yang disimpan",
|
||||
"no_dub_hint": "Unggah video dan klik Simpan untuk menyimpan pekerjaan Anda.",
|
||||
"no_clones": "Belum ada klon suara",
|
||||
"no_clones_hint": "Rekam atau unggah audio, lalu klik Simpan sebagai Profil Suara.",
|
||||
"no_designs": "Belum ada suara yang dirancang",
|
||||
"no_designs_hint": "Hasilkan suara dan simpan dari Riwayat.",
|
||||
"history_subtitle": "Riwayat generasi · Disimpan dalam SQLite",
|
||||
"no_history": "Tidak ada sejarah generasi",
|
||||
"no_history_hint": "Sintesis audio atau sulih suara video — hasilnya akan muncul di sini.",
|
||||
"clear_history": "Hapus Riwayat",
|
||||
"clear_confirm": "Hapus semua item riwayat {{count}}? Hal ini tidak dapat dibatalkan.",
|
||||
"history_cleared": "Sejarah dihapus",
|
||||
"recent_exports": "Ekspor Terbaru",
|
||||
"no_exports": "Tidak ada keluaran yang diunduh",
|
||||
"no_exports_hint": "Ekspor file melalui Tauri untuk melihatnya dilacak di sini.",
|
||||
"show_in_folder": "Tampilkan di folder",
|
||||
"open": "Buka",
|
||||
"select": "Pilih",
|
||||
"try_voice": "Coba",
|
||||
"consistent": "konsisten",
|
||||
"locked": "Terkunci",
|
||||
"clone_label": "Klon",
|
||||
"design_label": "Desain",
|
||||
"dub_label": "sulih suara",
|
||||
"save_label": "Simpan",
|
||||
"lock_identity": "Kunci identitas suara",
|
||||
"in_folder": "di {{folder}}"
|
||||
},
|
||||
"trimmer": {
|
||||
"title": "Pangkas audio referensi",
|
||||
"decoding": "Menguraikan kode audio…",
|
||||
"meta_length": "Panjang {{duration}} · {{sampleRate}} Hz",
|
||||
"meta_rendering": "rendering bentuk gelombang {{percent}}%",
|
||||
"keyboard_hint": "gulir = zoom · shift+gulir = pan · alt+drag = pan · ⏐ ⟵ ⟶ ⏐ tombol sesuaikan pegangan",
|
||||
"zoom_in": "Perbesar (+)",
|
||||
"zoom_out": "Perkecil (-)",
|
||||
"fit_all": "Cocok untuk semua (Rumah)",
|
||||
"fit_selection": "Pilihan pas (Akhir)",
|
||||
"fit_sel_btn": "COCOK SEL",
|
||||
"view_range": "Lihat {{start}} → {{end}} ({{duration}})",
|
||||
"start_label": "Mulai",
|
||||
"end_label": "Akhir",
|
||||
"length_label": "Panjangnya",
|
||||
"too_long": ">{{max}}s",
|
||||
"too_short": "terlalu pendek",
|
||||
"length_ok": "oke",
|
||||
"loop_preview": "Pratinjau lingkaran",
|
||||
"pause": "Jeda",
|
||||
"preview_selection": "Pratinjau pilihan",
|
||||
"play_hint": "Ruang untuk bermain · Enter untuk mengonfirmasi · Esc untuk membatalkan",
|
||||
"cancel": "Batalkan",
|
||||
"use_trimmed": "Gunakan dipangkas",
|
||||
"decode_failed": "Dekode gagal: {{message}}",
|
||||
"playback_failed": "Pemutaran gagal: {{message}}",
|
||||
"audio_load_failed": "Gagal memuat audio",
|
||||
"unit_seconds": "s"
|
||||
},
|
||||
"casting": {
|
||||
"title": "Pengecoran Pembicara",
|
||||
"auto_assign_title": "Menetapkan suara secara otomatis dari klon speaker yang diekstrak",
|
||||
"auto_cast": "Transmisi otomatis",
|
||||
"all_cast": "Semua pemeran",
|
||||
"segments_count": "{{count}} segmen",
|
||||
"assign_voice": "Tetapkan suara…",
|
||||
"from_video": "🎤 Dari video ({{name}})",
|
||||
"no_profiles": "Belum ada profil suara yang disimpan.",
|
||||
"preview_voice": "Pratinjau suara"
|
||||
},
|
||||
"checkpoint": {
|
||||
"asr_title": "Transkrip sudah siap",
|
||||
"asr_cta": "Terjemahkan",
|
||||
"asr_hint": "Perbaiki kesalahan ASR sekarang — diksi yang ketat akan menghemat upaya TTS nanti.",
|
||||
"translate_title": "Terjemahan sudah siap",
|
||||
"translate_cta": "Hasilkan sulih suara",
|
||||
"translate_hint": "Telusuri teks target. Jalur yang terlalu panjang akan ditingkatkan kecepatannya; Anda juga dapat mengedit secara langsung.",
|
||||
"done_title": "Sulih suara selesai",
|
||||
"done_hint": "Tinjau waktu dan rasio sinkronisasi. Sesuaikan baris mana pun dan tekan \"Regen berubah\" untuk pengulangan sebagian yang cepat.",
|
||||
"segment_one": "segmen {{count}}",
|
||||
"segment_other": "{{count}} segmen",
|
||||
"dismiss_title": "Singkirkan — tidak akan muncul kembali pada tahap ini hingga dimuat ulang"
|
||||
},
|
||||
"compare": {
|
||||
"title": "Perbandingan Suara A/B",
|
||||
"close": "Perbandingan yang dekat",
|
||||
"desc": "Bandingkan dua suara secara berdampingan untuk membuat keputusan casting. Aplikasi tetap interaktif.",
|
||||
"test_phrase": "Frasa tes",
|
||||
"voice_a": "Suara A",
|
||||
"voice_b": "Suara B",
|
||||
"select_voice": "— Pilih suara —",
|
||||
"preset_suffix": "(Preset)",
|
||||
"no_audio": "Belum ada audionya",
|
||||
"close_btn": "Tutup",
|
||||
"comparing": "Membandingkan…",
|
||||
"compare_btn": "Bandingkan",
|
||||
"preparing_voice": "Mempersiapkan suara...",
|
||||
"generating_voice_a": "Menghasilkan Suara ...",
|
||||
"generating_voice_b": "Menghasilkan Suara B...",
|
||||
"comparison_complete": "Perbandingan selesai!",
|
||||
"play_failed": "Gagal bermain: {{message}}"
|
||||
},
|
||||
"models": {
|
||||
"hf_token_set_toast": "Kumpulan token HuggingFace — pengunduhan lebih cepat diaktifkan",
|
||||
"hf_token_save_failed": "Gagal menyimpan token",
|
||||
"install_started": "Penginstalan dimulai — kemajuan dalam baris",
|
||||
"delete_confirm": "Hapus {{repoId}}? Anda dapat menginstalnya kembali nanti.",
|
||||
"delete_confirm_title": "Hapus modelnya",
|
||||
"deleted": "Dihapus {{repoId}}",
|
||||
"reinstall_confirm": "Instal ulang {{repoId}}? Ini akan menghapus salinan saat ini dan mengunduhnya lagi.",
|
||||
"reinstall_confirm_title": "Instal ulang modelnya",
|
||||
"reinstalling": "Menginstal ulang",
|
||||
"recommended_installed": "Model yang direkomendasikan sudah terpasang.",
|
||||
"started_downloading_one": "Mulai mengunduh model {{count}}",
|
||||
"started_downloading_other": "Mulai mengunduh model {{count}}",
|
||||
"install_failed": "Pemasangan gagal: {{message}}",
|
||||
"removing_cached": "Menghapus revisi yang di-cache…",
|
||||
"resolving_metadata": "Menyelesaikan metadata repo",
|
||||
"retry_attempt": "Coba lagi {{attempt}} — {{error}}",
|
||||
"connecting_hf": "Menghubungkan ke HuggingFace…",
|
||||
"resolving_files_one": "Menyelesaikan file {{count}}…",
|
||||
"resolving_files_other": "Menyelesaikan file {{count}}…",
|
||||
"resolving_files_active": "Menyelesaikan {{count}} file… · {{file}}",
|
||||
"measuring": "mengukur…",
|
||||
"files_progress": "{{done}}/{{total}} file",
|
||||
"install_error": "Pemasangan gagal: {{error}}",
|
||||
"view_on_hf": "Lihat di HuggingFace",
|
||||
"install_btn": "Instal",
|
||||
"reinstall_btn": "Instal ulang",
|
||||
"downloading": "mengunduh",
|
||||
"deleting": "menghapus",
|
||||
"working": "bekerja",
|
||||
"installed": "dipasang",
|
||||
"not_installed": "tidak dipasang",
|
||||
"required_tag": "diperlukan",
|
||||
"delete_btn": "Hapus",
|
||||
"hf_token_btn": "Token HF",
|
||||
"hf_set_title": "Setel token HuggingFace untuk pengunduhan yang lebih cepat",
|
||||
"get_token": "Dapatkan token →",
|
||||
"reco_installed_for": "Paket yang direkomendasikan dipasang untuk {{device}}",
|
||||
"reco_for": "Direkomendasikan untuk {{device}}",
|
||||
"starting": "Mulai…",
|
||||
"required_size": "Diperlukan ~{{size}} GB",
|
||||
"all_size": "Semua ~{{size}} GB",
|
||||
"req_tag": "permintaan",
|
||||
"search_placeholder": "Model pencarian…",
|
||||
"search_label": "Model pencarian",
|
||||
"no_matches": "Tidak ada model yang cocok dengan filter Anda.",
|
||||
"sort_by": "Urutkan berdasarkan {{column}}",
|
||||
"column_model": "Model",
|
||||
"column_role": "Peran",
|
||||
"column_size": "Ukuran",
|
||||
"column_status": "Status",
|
||||
"ready_badge": "Siap",
|
||||
"loading_badge": "Memuat…",
|
||||
"idle_badge": "Menganggur",
|
||||
"started_downloading_required_one": "Mulai mengunduh {{count}} model yang diperlukan",
|
||||
"started_downloading_required_other": "Mulai mengunduh {{count}} model yang diperlukan"
|
||||
},
|
||||
"enterprise_faq": {
|
||||
"q_internal_tools": "Apakah saya memerlukan lisensi untuk alat internal?",
|
||||
"a_internal_tools": "Penggunaan internal oleh karyawan dan kontraktor Anda adalah Tujuan yang Diizinkan berdasarkan FSL — tidak diperlukan lisensi. Lisensi komersial diperlukan saat Anda membuat OmniVoice tersedia bagi orang lain sebagai bagian dari produk atau layanan pesaing (dijual kembali, SaaS yang dihosting, label putih).",
|
||||
"q_try_before": "Bisakah saya mencoba sebelum melakukan?",
|
||||
"a_try_before": "Ya. Aplikasi lengkapnya gratis untuk diunduh dan dijalankan secara lokal untuk evaluasi di bawah FSL. Saat Anda siap mendiskusikan penerapan komersial, kirimkan email kepada kami dan kami akan membahas detailnya bersama-sama.",
|
||||
"q_watermark": "Bagaimana dengan tanda airnya?",
|
||||
"a_watermark": "Tanda air AudioSeal yang tidak terlihat tertanam secara default. Pemegang lisensi komersial dapat menonaktifkannya di Pengaturan → Privasi. Penggunaan gratis/pribadi selalu menyertakan tanda air.",
|
||||
"q_apache": "Apakah sumbernya pernah menjadi Apache 2.0?",
|
||||
"a_apache": "Ya. Setiap rilis dikonversi secara otomatis ke Lisensi Apache, Versi 2.0 pada ulang tahun kedua penerbitannya. Itu berarti rilis hari ini adalah Apache 2.0 dalam dua tahun, kami tidak perlu mengambil tindakan apa pun — FSL menjaminnya tanpa dapat ditarik kembali."
|
||||
},
|
||||
"gallery_extra": {
|
||||
"save_prompt": "Masukkan nama untuk profil suara ini:",
|
||||
"saved_as_profile": "Suara disimpan sebagai profil!",
|
||||
"save_profile_failed": "Gagal menyimpan profil",
|
||||
"download_failed": "Pengunduhan gagal: {{message}}",
|
||||
"trim_load_failed": "Gagal memuat audio untuk pemangkasan: {{message}}",
|
||||
"upload_crop_failed": "Gagal mengunggah suara yang dipotong: {{message}}"
|
||||
},
|
||||
"dub_workflow": {
|
||||
"preparing_audio": "Mempersiapkan audio…",
|
||||
"preparing_video": "Mempersiapkan video…",
|
||||
"extracting_audio_scenes": "Mengekstraksi audio & adegan…",
|
||||
"transcribing_audio": "Mentranskripsikan audio…",
|
||||
"transcription_complete": "Transkripsi selesai",
|
||||
"upload_cancelled": "Pengunggahan dibatalkan",
|
||||
"upload_failed": "Gagal mengunggah: {{message}}",
|
||||
"downloading_video": "Mengunduh video…",
|
||||
"ingested": "Tertelan {{url}}",
|
||||
"ingest_cancelled": "Penyerapan dibatalkan",
|
||||
"ingest_failed": "Gagal menyerap URL: {{message}}",
|
||||
"retry_cancelled": "Coba lagi dibatalkan",
|
||||
"transcription_failed": "Transkripsi gagal: {{message}}",
|
||||
"import_srt_no_job": "Unggah atau serap video terlebih dahulu — tidak ada tugas untuk melampirkan subtitle.",
|
||||
"imported_cues": "{{count}} isyarat yang diimpor dari {{file}}",
|
||||
"skipped_malformed": "{{count}} dilewati (format salah)",
|
||||
"dropped_overlap": "{{count}} terjatuh (tumpang tindih)",
|
||||
"clamped_to_duration": "{{count}} dijepit sesuai panjang media",
|
||||
"srt_import_failed": "Impor SRT gagal",
|
||||
"cleaned_one": "Membersihkan fragmen {{count}}",
|
||||
"cleaned_other": "Membersihkan pecahan {{count}}",
|
||||
"segments_clean": "Segmen sudah bersih",
|
||||
"cleanup_failed": "Pembersihan gagal: {{message}}",
|
||||
"cinematic_no_llm": "Kualitas sinematik memerlukan LLM — setel TRANSLATE_BASE_URL + TRANSLATE_API_KEY (Ollama berfungsi secara lokal). Kembali ke Fast.",
|
||||
"translate_errors": "{{errorCount}}/{{totalCount}} segmen gagal: {{firstError}}",
|
||||
"translated_segments": "Diterjemahkan {{count}} segmen → {{lang}}",
|
||||
"translated_cinematic_suffix": "(Sinematik)",
|
||||
"translation_failed": "Terjemahan gagal: {{message}}",
|
||||
"regenerating": "Meregenerasi {{count}} segmen…",
|
||||
"generating_dub": "Menghasilkan sulih suara…",
|
||||
"generating_progress": "Menghasilkan sulih suara… {{current}}/{{total}}",
|
||||
"generation_aborted": "Generasi dibatalkan.",
|
||||
"dubbing_aborted": "Sulih suara dibatalkan",
|
||||
"generation_stream_ended": "Aliran generasi berakhir sebelum selesai",
|
||||
"dub_complete": "Sulih suara selesai",
|
||||
"stop_failed": "Gagal berhenti",
|
||||
"save_first": "Silakan klik 'Unggah & Transkripsikan' terlebih dahulu agar video diproses di server sebelum disimpan.",
|
||||
"project_saved": "Proyek disimpan",
|
||||
"project_created": "Proyek dibuat",
|
||||
"save_failed": "Gagal menyimpan: {{message}}",
|
||||
"opened_project": "Dibuka: {{name}}",
|
||||
"delete_project_confirm": "Hapus proyek ini? Hal ini tidak dapat dibatalkan.",
|
||||
"project_deleted": "Proyek dihapus",
|
||||
"delete_history_confirm": "Hapus item riwayat ini?",
|
||||
"history_deleted": "Item riwayat dihapus",
|
||||
"restored_state": "Memulihkan keadaan generasi sebelumnya",
|
||||
"upgrading_preview": "Meningkatkan {{count}} segmen kualitas pratinjau ke kualitas penuh…"
|
||||
},
|
||||
"tts_errors": {
|
||||
"enter_text": "Silakan masukkan teks",
|
||||
"upload_or_select": "Unggah audio atau pilih profil suara",
|
||||
"trim_hint": "Audio adalah {{duration}}s — potong ke ≤{{max}}s untuk kloning terbaik",
|
||||
"timeout": "Waktu pembuatan habis — model mungkin masih diunduh. Periksa Pengaturan → Log, lalu coba lagi.",
|
||||
"error_prefix": "Kesalahan: {{message}}",
|
||||
"ignored_unsupported": "Mengabaikan instruksi yang tidak didukung: {{items}}",
|
||||
"ignored_duplicate": "Diabaikan (kategori sudah ditetapkan): {{items}}"
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Berbagi & Akses Jarak Jauh",
|
||||
"help": "Ekspos instans OmniVoice yang sedang berjalan ini ke mesin Anda yang lain tanpa memulai ulang. Hanya loopback yang merupakan default — tidak ada yang dibagikan sampai Anda mengaktifkannya di sini.",
|
||||
"local_network": "Jaringan lokal",
|
||||
"local_help": "Bagikan di Wi-Fi / Ethernet Anda dengan PIN akses satu kali. Perangkat lain memindai kode QR atau membuka tautan.",
|
||||
"ports_title": "Pelabuhan",
|
||||
"ports_help": "Ini diatur melalui variabel lingkungan yang dibaca saat startup. Ubah backend atau port UI dengan mengatur variabel dan memulai ulang OmniVoice.",
|
||||
"backend_port": "Pelabuhan ujung belakang",
|
||||
"ui_port": "Pelabuhan UI",
|
||||
"lan_share_port": "Port berbagi LAN",
|
||||
"port_error": "Masukkan port antara 1024 dan 65535",
|
||||
"port_saved": "Port berbagi LAN disimpan — berlaku saat berikutnya Anda mengaktifkan berbagi",
|
||||
"port_save_failed": "Tidak dapat menyimpan port: {{message}}",
|
||||
"saving": "Menyimpan…",
|
||||
"ports_note": "Port backend dan UI berlaku saat restart. Port berbagi LAN berlaku saat berikutnya Anda mengaktifkan berbagi.",
|
||||
"tailscale_title": "Tailscale (akses jarak jauh pribadi)",
|
||||
"tailscale_checking": "Memeriksa Skala Ekor…",
|
||||
"tailscale_absent": "Sisik ekor tidak terdeteksi. Instal untuk menjangkau OmniVoice dengan aman dari mana saja di tailnet pribadi Anda.",
|
||||
"tailscale_install": "Instal Skala Ekor",
|
||||
"tailscale_running": "Skala ekor sedang berjalan. Sajikan OmniVoice melalui tailnet pribadi Anda.",
|
||||
"tailscale_not_logged_in": "Tailscale diinstal tetapi belum login. Mulai dan masuk ke Tailscale terlebih dahulu.",
|
||||
"tailscale_enabled": "Pelayanan tailscale diaktifkan",
|
||||
"tailscale_enable_failed": "Tidak dapat mengaktifkan Tailscale",
|
||||
"tailscale_enable_error": "Tidak dapat mengaktifkan Tailscale: {{message}}",
|
||||
"tailscale_disabled": "Pelayanan tailscale dinonaktifkan",
|
||||
"tailscale_disable_failed": "Tidak dapat menonaktifkan Tailscale",
|
||||
"tailscale_disable_error": "Tidak dapat menonaktifkan Skala Ekor: {{message}}",
|
||||
"tailscale_enabling": "Mengaktifkan…",
|
||||
"tailscale_enable_btn": "Aktifkan penayangan Tailscale",
|
||||
"tailscale_disabling": "Menonaktifkan…",
|
||||
"tailscale_disable_btn": "Hentikan servis Tailscale",
|
||||
"tailscale_copy": "Salin tautan",
|
||||
"tailscale_open": "Buka di peramban",
|
||||
"copied": "Disalin",
|
||||
"tailscale_qr_alt": "Kode QR untuk URL Tailscale"
|
||||
},
|
||||
"reportBug": {
|
||||
"label": "Laporkan bug",
|
||||
"title": "Membuka halaman Masalah GitHub yang telah diisi sebelumnya di browser Anda. Tidak ada yang dikirim sampai Anda mengklik Kirim."
|
||||
},
|
||||
"app": {
|
||||
"loading": "Memuat…",
|
||||
"trimmed_loaded": "Audio yang dipangkas dimuat",
|
||||
"toast_exported": "Diekspor: {{name}}",
|
||||
"toast_export_failed": "Ekspor gagal: {{message}}",
|
||||
"toast_open_folder_failed": "Tidak dapat membuka folder: {{message}}",
|
||||
"toast_saving": "Menyimpan {{name}}...",
|
||||
"toast_saved": "Disimpan: {{path}}",
|
||||
"toast_save_error": "Kesalahan penyimpanan: {{message}}",
|
||||
"toast_processing": "Memproses {{name}}...",
|
||||
"toast_downloaded": "Diunduh {{name}}",
|
||||
"toast_download_error": "Kesalahan pengunduhan: {{message}}",
|
||||
"toast_upload_first": "Silakan klik 'Unggah & Transkripsikan' terlebih dahulu agar video diproses di server sebelum disimpan.",
|
||||
"toast_save_failed": "Gagal menyimpan: {{message}}",
|
||||
"toast_opened": "Dibuka: {{name}}",
|
||||
"toast_project_deleted": "Proyek dihapus",
|
||||
"toast_restored_state": "Memulihkan keadaan generasi sebelumnya",
|
||||
"toast_history_deleted": "Item riwayat dihapus",
|
||||
"toast_flushed": "Dibilas — RAM {{ram}}G · VRAM {{vram}}G{{unloaded}}",
|
||||
"toast_model_unloaded": "· model dibongkar",
|
||||
"toast_flush_failed": "Gagal menyiram: {{message}}",
|
||||
"toast_project_saved": "Proyek disimpan",
|
||||
"toast_project_created": "Proyek dibuat"
|
||||
},
|
||||
"update": {
|
||||
"available": "Pembaruan {{version}} tersedia",
|
||||
"install": "Instal dan mulai ulang",
|
||||
"install_hint": "Unduh pembaruan dan mulai ulang ke versi baru",
|
||||
"downloading": "Memperbarui… {{pct}}%",
|
||||
"restart": "Mulai ulang untuk memperbarui",
|
||||
"busy": "Selesaikan sulih suara Anda dulu — lalu instal pembaruannya.",
|
||||
"whats_new": "Apa yang baru",
|
||||
"failed": "Pembaruan gagal",
|
||||
"retry": "Coba lagi",
|
||||
"dismiss": "Singkirkan"
|
||||
},
|
||||
"archetypes": {
|
||||
"featured": "Unggulan",
|
||||
@@ -945,5 +1577,25 @@
|
||||
"facet_accent": "Aksen",
|
||||
"facet_lang": "Bahasa",
|
||||
"facet_whisper": "Bisikan"
|
||||
},
|
||||
"support": {
|
||||
"tab_support": "Dukungan",
|
||||
"tab_license": "Lisensi Komersial",
|
||||
"toggle_label": "Dukungan atau lisensi komersial",
|
||||
"other_ways": "Cara lain untuk membantu",
|
||||
"star_github": "Bintangi di GitHub",
|
||||
"join_discord": "Bergabunglah dengan Perselisihan"
|
||||
},
|
||||
"updates": {
|
||||
"tab": "Pembaruan",
|
||||
"up_to_date": "Terkini · v{{version}}",
|
||||
"check_now": "Periksa sekarang",
|
||||
"releases": "Rilis",
|
||||
"current": "saat ini",
|
||||
"prerelease": "pratinjau",
|
||||
"loading": "Memuat rilis…",
|
||||
"none": "Tidak ada rilis yang ditemukan",
|
||||
"load_error": "Tidak dapat memuat rilis (offline?)",
|
||||
"retry_load": "Coba lagi"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,4 @@
|
||||
{
|
||||
"update": {
|
||||
"available": "Aggiornamento {{version}} disponibile",
|
||||
"install": "Installa e riavvia",
|
||||
"install_hint": "Scarica l'aggiornamento e riavvia nella nuova versione",
|
||||
"downloading": "Aggiornamento… {{pct}}%",
|
||||
"restart": "Riavvia per aggiornare",
|
||||
"busy": "Completa prima il tuo doppiaggio, poi installa l'aggiornamento."
|
||||
},
|
||||
"nav": {
|
||||
"clone": "Clona",
|
||||
"design": "Progetta",
|
||||
@@ -16,7 +8,10 @@
|
||||
"launchpad": "Launchpad",
|
||||
"gallery": "Galleria",
|
||||
"transcripts": "Trascrizioni",
|
||||
"omnidrive": "OmniDrive"
|
||||
"omnidrive": "OmniDrive",
|
||||
"move_rail_right": "Spostare il binario a destra",
|
||||
"move_rail_left": "Spostare il binario a sinistra",
|
||||
"flip_rail": "Ribaltare il lato della guida"
|
||||
},
|
||||
"settings": {
|
||||
"general": "Generale",
|
||||
@@ -45,7 +40,40 @@
|
||||
"ffmpeg_missing": "Non trovato",
|
||||
"ffmpeg_current": "Percorso attuale",
|
||||
"ffmpeg_desc": "Imposta un percorso ffmpeg personalizzato se il rilevamento automatico fallisce.",
|
||||
"ffmpeg_saved": "Set di percorsi FFmpeg: riavvia il backend per applicare."
|
||||
"ffmpeg_saved": "Set di percorsi FFmpeg: riavvia il backend per applicare.",
|
||||
"diagnostics_copied": "Diagnostica copiata: incollala nella segnalazione del problema.",
|
||||
"updater_desktop": "Il programma di aggiornamento viene eseguito solo nell'app desktop.",
|
||||
"latest_version": "Sei sull'ultima versione.",
|
||||
"logs_load_failed": "Impossibile caricare i registri: {{message}}",
|
||||
"clear_frontend_confirm": "Cancellare il buffer del registro del frontend in memoria?",
|
||||
"clear_frontend_title": "Cancella registri",
|
||||
"frontend_logs_cleared": "Registri del frontend cancellati",
|
||||
"clear_tauri_confirm": "Troncare i file di registro lato Tauri? Il sistema operativo continuerà a scrivere nuove voci.",
|
||||
"clear_tauri_title": "Cancella i registri Tauri",
|
||||
"nothing_to_clear": "Niente da cancellare: ancora nessun file di registro Tauri sul disco.",
|
||||
"cleared_tauri_one": "Cancellato il file di registro {{count}} Tauri",
|
||||
"cleared_tauri_other": "Cancellati i file di registro {{count}} Tauri",
|
||||
"clear_tauri_failed": "Impossibile cancellare i registri Tauri: {{message}}",
|
||||
"clear_backend_confirm": "Cancellare il runtime del backend + i registri degli arresti anomali? Questa operazione non può essere annullata.",
|
||||
"clear_backend_title": "Cancella registri",
|
||||
"backend_logs_cleared": "Registri di backend cancellati",
|
||||
"clear_backend_failed": "Impossibile cancellare i registri",
|
||||
"copy_failed": "Copia non riuscita: {{message}}",
|
||||
"update_check_failed": "Controllo aggiornamento non riuscito: {{message}}",
|
||||
"save_failed": "Salvataggio non riuscito: {{message}}",
|
||||
"clear_failed": "Cancellazione non riuscita: {{message}}",
|
||||
"engine_switched": "{{family}} → {{engine}}",
|
||||
"channel_set_failed": "Impossibile impostare il canale: {{message}}",
|
||||
"updater_downloading": "Download in corso di {{version}}...",
|
||||
"updater_installed": "Installato: riavvio.",
|
||||
"updater_available_title": "Aggiornamento disponibile",
|
||||
"updater_available_body": "La versione {{version}} è disponibile.\n\n{{notes}}\n\nScaricare e installare adesso?",
|
||||
"updater_notes_fallback": "Vedi le note di rilascio su GitHub.",
|
||||
"shortcut_load_failed": "Impossibile caricare il collegamento: {{message}}",
|
||||
"shortcut_set": "Scorciatoia per la dettatura impostata su {{shortcut}}",
|
||||
"shortcut_register_failed": "Impossibile registrarsi: {{message}}",
|
||||
"shortcut_reset": "Ripristina le impostazioni predefinite",
|
||||
"shortcut_reset_failed": "Reimpostazione non riuscita: {{message}}"
|
||||
},
|
||||
"bootstrap": {
|
||||
"title": "OmniVoice Studio",
|
||||
@@ -69,7 +97,13 @@
|
||||
"waiting_output": "In attesa di output…",
|
||||
"auto_detect": "Rileva automaticamente",
|
||||
"suggest_lang": "Passare all'Italiano?",
|
||||
"select_lang": "Lingua:"
|
||||
"select_lang": "Lingua:",
|
||||
"region_global": "Globale (diretto)",
|
||||
"region_china": "Cina (specchio)",
|
||||
"region_russia": "Russia (specchio)",
|
||||
"region_restricted": "Limitato (specchio)",
|
||||
"unknown_error": "Errore sconosciuto",
|
||||
"retrying": "Nuovo tentativo…"
|
||||
},
|
||||
"stories": {
|
||||
"title": "Redattore di storie",
|
||||
@@ -158,7 +192,17 @@
|
||||
"reload": "Forza ricarica interfaccia utente",
|
||||
"backend": "Backend",
|
||||
"frontend": "Frontend",
|
||||
"tauri": "Tauri"
|
||||
"tauri": "Tauri",
|
||||
"cancelOp": "Annulla l'operazione",
|
||||
"dismiss": "Ignora",
|
||||
"dismissStatus": "Ignora stato",
|
||||
"search": "Cerca…",
|
||||
"no_matches": "Nessuna corrispondenza",
|
||||
"recent_and_popular": "Recenti e popolari",
|
||||
"popular_label": "Popolare",
|
||||
"showing_of": "Mostra {{shown}} di {{total}}. Digita per cercare...",
|
||||
"yes": "Sì",
|
||||
"no": "No"
|
||||
},
|
||||
"launchpad": {
|
||||
"greeting": "ciao a tutti",
|
||||
@@ -180,7 +224,16 @@
|
||||
"locked": "BLOCCATO",
|
||||
"open": "Aperto",
|
||||
"try_it": "Provalo",
|
||||
"audio_only": "Solo audio"
|
||||
"audio_only": "Solo audio",
|
||||
"stories_title": "Storie",
|
||||
"stories_desc": "Audiolibri a più voci: lancia i tuoi personaggi, inserisci una sceneggiatura, esporta per capitolo.",
|
||||
"gallery_title": "Galleria vocale",
|
||||
"gallery_desc": "Sfoglia le voci già pronte per accento, età e stile, senza alcuna configurazione.",
|
||||
"transcripts_title": "Trascrizioni",
|
||||
"transcripts_desc": "Trasforma audio o video in testo modificabile e ricercabile, in 646 lingue.",
|
||||
"recent_files": "File recenti",
|
||||
"view_all_files": "Visualizza tutti i file",
|
||||
"file": "Archivio"
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "Richiedi",
|
||||
@@ -271,11 +324,6 @@
|
||||
"outputs": "Uscite",
|
||||
"crash_log": "Registro degli arresti anomali",
|
||||
"update_endpoint": "Aggiorna endpoint",
|
||||
"update_channel": "Canale di aggiornamento",
|
||||
"channel_stable": "Stabile",
|
||||
"channel_preview": "Anteprima",
|
||||
"channel_set": "Canale di aggiornamento impostato su {{channel}}",
|
||||
"channel_preview_hint": "L'anteprima segue l'ultima build di main: funzionalità più recenti, meno testate. Torna alla versione stabile se una release stabile è più avanti.",
|
||||
"yes": "sì",
|
||||
"no": "no",
|
||||
"web_preview": "anteprima web",
|
||||
@@ -284,7 +332,12 @@
|
||||
"copy_diagnostics": "Copia la diagnostica",
|
||||
"github": "OmniVoice su GitHub",
|
||||
"model_card": "Scheda modello",
|
||||
"commercial_license": "Licenza commerciale"
|
||||
"commercial_license": "Licenza commerciale",
|
||||
"update_channel": "Canale di aggiornamento",
|
||||
"channel_stable": "Stabile",
|
||||
"channel_preview": "Anteprima",
|
||||
"channel_set": "Canale di aggiornamento impostato su {{channel}}",
|
||||
"channel_preview_hint": "L'anteprima segue l'ultima build di main: funzionalità più recenti, meno testate. Torna alla versione stabile se una release stabile è più avanti."
|
||||
},
|
||||
"privacy": {
|
||||
"desc": "Tutto funziona su <1>questa macchina</1>. I tuoi audio, video e trascrizioni non lasciano mai il tuo computer a meno che tu non utilizzi esplicitamente un traduttore online (Google, DeepL, ecc.) o invii a HuggingFace.",
|
||||
@@ -340,7 +393,30 @@
|
||||
"unavailable": "non disponibile",
|
||||
"use": "Utilizzare",
|
||||
"loading": "Caricamento motori…",
|
||||
"refresh": "Aggiorna"
|
||||
"refresh": "Aggiorna",
|
||||
"matrixTitle": "Matrice di compatibilità del motore",
|
||||
"loadFailed": "Impossibile caricare i motori: {{message}}",
|
||||
"couldNotLoad": "Impossibile caricare i motori: {{message}}",
|
||||
"retry": "Riprova",
|
||||
"activeEngine": "Attivo {{family}}: {{engine}}",
|
||||
"engineCompatLabel": "{{family}} compatibilità motore",
|
||||
"active": "attivo",
|
||||
"whyUnavailable": "Perché non disponibile?",
|
||||
"lastError": "Ultimo errore: {{error}}",
|
||||
"installedAndReady": "Installato e pronto",
|
||||
"notInstalled": "Non installato",
|
||||
"available": "Disponibile",
|
||||
"subprocessTitle": "Viene eseguito nel proprio sottoprocesso + venv",
|
||||
"inProcessTitle": "Viene eseguito nel processo OmniVoice Python",
|
||||
"testEngine": "Motore di prova",
|
||||
"testing": "Test…",
|
||||
"recheck": "Ricontrolla",
|
||||
"rechecking": "Ricontrollo…",
|
||||
"latencyMs": "{{ms}}ms",
|
||||
"failed": "fallito",
|
||||
"acceptLicense": "Accetta la licenza",
|
||||
"noBackends": "Nessun backend registrato.",
|
||||
"switch_failed": "Impossibile cambiare motore"
|
||||
},
|
||||
"capture": {
|
||||
"desc": "I tasti di scelta rapida globali funzionano solo nell'app desktop. L'interfaccia utente Web utilizza una scorciatoia <1>Ctrl+Shift+Spazio</1> nella pagina mentre la finestra è attiva.",
|
||||
@@ -352,13 +428,55 @@
|
||||
"record_shortcut": "Registra la scorciatoia",
|
||||
"recording": "Registrazione…",
|
||||
"save": "Salva",
|
||||
"reset_default": "Ripristina le impostazioni predefinite"
|
||||
"reset_default": "Ripristina le impostazioni predefinite",
|
||||
"listening_label": "Ascoltando...",
|
||||
"transcribing_label": "Trascrizione…",
|
||||
"pasted": "Incollato",
|
||||
"no_speech": "Nessun parlato rilevato",
|
||||
"mic_denied": "Accesso al microfono negato",
|
||||
"mic_denied_toast": "Accesso al microfono negato. {{hint}}",
|
||||
"mic_hint_mac": "macOS: apri Impostazioni di sistema → Privacy e sicurezza → Microfono e attiva OmniVoice.",
|
||||
"mic_hint_windows": "Windows: apri Impostazioni → Privacy e sicurezza → Microfono e consenti OmniVoice.",
|
||||
"mic_hint_linux": "Linux: controlla che il tuo utente sia nel gruppo audio e che WebView abbia accesso al microfono.",
|
||||
"transcription_failed": "Trascrizione non riuscita: {{message}}"
|
||||
},
|
||||
"logs": {
|
||||
"no_tauri_log": "Nessun accesso Tauri ancora sul disco: avvia tramite la build desktop per produrne uno",
|
||||
"empty_frontend": "Nessuna voce della console frontend ancora catturata. Interagisci con l'app: ogni console.* apparirà qui.",
|
||||
"empty_tauri": "Nessun registro Tauri disponibile. Funziona solo nella shell del desktop.",
|
||||
"empty_backend": "Il registro di runtime è vuoto. L'attività verrà visualizzata qui man mano che il backend la registra."
|
||||
"empty_backend": "Il registro di runtime è vuoto. L'attività verrà visualizzata qui man mano che il backend la registra.",
|
||||
"title": "Registri",
|
||||
"source_backend": "Backend",
|
||||
"source_frontend": "Frontend",
|
||||
"source_tauri": "Tauri",
|
||||
"expand": "Espandi i log",
|
||||
"collapse": "Comprimi i log",
|
||||
"expand_aria": "Espandi il pannello dei registri",
|
||||
"collapse_aria": "Comprimi il pannello dei registri",
|
||||
"drag_resize": "Trascina per ridimensionare",
|
||||
"refresh": "Aggiorna",
|
||||
"refresh_aria": "Aggiorna i registri",
|
||||
"copy_visible": "Copia il registro visibile",
|
||||
"copy_visible_aria": "Copia il registro visibile",
|
||||
"clear": "Chiaro",
|
||||
"clear_aria": "Cancella registro",
|
||||
"report_issue": "Segnala un problema (diagnostica della copia)",
|
||||
"report_issue_aria": "Segnala il problema",
|
||||
"close": "Chiudi",
|
||||
"close_aria": "Chiudi il pannello dei registri",
|
||||
"join_discord": "Unisciti alla nostra Discordia",
|
||||
"join_discord_aria": "Unisciti alla nostra comunità Discord",
|
||||
"support_project": "Sostieni questo progetto",
|
||||
"support_project_aria": "Sostieni questo progetto",
|
||||
"empty_frontend_short": "Nessun output della console frontend ancora.",
|
||||
"empty_lines": "Nessuna linea.",
|
||||
"all_clear": "✅ Tutto chiaro: nessun problema rilevato",
|
||||
"log_cleared": "{{source}} registro cancellato",
|
||||
"clear_failed": "Cancellazione non riuscita: {{message}}",
|
||||
"log_copied": "Registro {{source}} copiato",
|
||||
"copy_failed": "Copia non riuscita: {{message}}",
|
||||
"report_copied": "Report diagnostico copiato: incollalo in un problema di GitHub.",
|
||||
"report_failed": "Segnalazione non riuscita: {{message}}"
|
||||
},
|
||||
"voice_profile": {
|
||||
"test_text": "Ciao, questo è un test di questa voce.",
|
||||
@@ -526,7 +644,17 @@
|
||||
"install_already": "{{engine}} era già installato",
|
||||
"install_ok": "{{engine}} installato",
|
||||
"install_failed": "Installazione non riuscita: {{message}}",
|
||||
"prep_elapsed": "{{time}} trascorso"
|
||||
"prep_elapsed": "{{time}} trascorso",
|
||||
"add_language": "Aggiungi lingua",
|
||||
"search_languages": "Cerca lingue…",
|
||||
"languages_selected_one": "{{count}} lingua selezionata",
|
||||
"languages_selected_other": "{{count}} lingue selezionate",
|
||||
"more_to_narrow": "+{{count}} altro: digita per restringere",
|
||||
"no_matches": "Nessuna corrispondenza",
|
||||
"diagnostic_copied": "Diagnostica copiata",
|
||||
"copy_failed": "Copia non riuscita",
|
||||
"open_docs": "Apri documenti",
|
||||
"copy_diagnostic": "Copia diagnostica"
|
||||
},
|
||||
"glossary": {
|
||||
"title": "Glossario",
|
||||
@@ -601,7 +729,17 @@
|
||||
"more_actions_title": "Più azioni",
|
||||
"speaker_pick": "Scegli…",
|
||||
"speaker_title_detected": "Altoparlante: scegli tra quelli rilevati o digita un nome personalizzato",
|
||||
"speaker_title_custom": "Altoparlante: digita un nome (nessun clone di diarizzazione rilevato)"
|
||||
"speaker_title_custom": "Altoparlante: digita un nome (nessun clone di diarizzazione rilevato)",
|
||||
"time_edit_title": "Fare clic per modificare l'ora di inizio (m:ss.s). Invio per confermare, Esc per annullare.",
|
||||
"fit_fits": "Adatto",
|
||||
"fit_fits_title": "L'audio a velocità naturale si adatta all'interno dello slot.",
|
||||
"fit_overflows": "Overflow +{{seconds}}s",
|
||||
"fit_overflows_title": "Il testo tradotto era più lungo dello spazio originale di {{seconds}}s. L'audio è stato ridotto al minimo; accorcia il testo o imposta il timing su \"Stretch Video\".",
|
||||
"fit_stretched": "Video {{ratio}}×",
|
||||
"fit_stretched_title": "Modalità Stretch Video: il video di questo segmento è stato rallentato a {{ratio}}× per adattarsi all'audio naturale del doppiaggio.",
|
||||
"fit_compressed_title": "L'audio TTS è il {{pct}}% dello slot: fortemente compresso.",
|
||||
"fit_audio_title": "L'audio si inserisce all'interno dello slot.",
|
||||
"fit_ratio_title": "L'audio TTS è il {{pct}}% dello slot."
|
||||
},
|
||||
"voice": {
|
||||
"personality": "Personalità",
|
||||
@@ -696,10 +834,24 @@
|
||||
"status_running": "correre",
|
||||
"status_done": "fatto",
|
||||
"status_failed": "fallito",
|
||||
"status_cancelled": "annullato"
|
||||
"status_cancelled": "annullato",
|
||||
"add_to_queue_title": "Aggiungi video alla coda",
|
||||
"drop_hint_text": "Trascina qui i file video o fai clic per sfogliarli",
|
||||
"drop_formats": "MP4 · MOV · MKV · WEBM",
|
||||
"files_kicker": "FILE ({{count}})",
|
||||
"file_size_mb": "{{size}}MB",
|
||||
"target_languages": "LINGUE DI DESTINAZIONE",
|
||||
"voice_kicker": "VOCE",
|
||||
"default_option": "Predefinito",
|
||||
"clone_profiles": "Clona profili",
|
||||
"presets": "Preimpostazioni",
|
||||
"preserve_bg": "Conserva l'audio di sottofondo (musica/effetti)",
|
||||
"estimate": "{{videos}} video/i × {{langs}} lingua/e = {{jobs}} lavoro/i",
|
||||
"select_files_langs": "Seleziona file e lingue",
|
||||
"add_to_queue": "Aggiungi alla coda"
|
||||
},
|
||||
"gallery": {
|
||||
"title": "Galleria",
|
||||
"title": "OmniVoice Galleria",
|
||||
"search_placeholder": "Cerca su YouTube...",
|
||||
"all_voices": "Tutte le voci ({{count}})",
|
||||
"no_voices": "Nessuna voce ancora",
|
||||
@@ -714,6 +866,14 @@
|
||||
"youtube_results": "Risultati di YouTube ({{count}})",
|
||||
"clone_profile": "Profilo clone",
|
||||
"crop_audio": "Ritaglia l'audio",
|
||||
"cat_disney": "Disney",
|
||||
"cat_anime": "Anime",
|
||||
"cat_marvel": "Marvel/DC",
|
||||
"cat_celebs": "Celebrità",
|
||||
"cat_politicians": "Politici",
|
||||
"cat_news": "Conduttori di notizie",
|
||||
"cat_gaming": "Gioco",
|
||||
"cat_books": "Libri/film",
|
||||
"subtitle": "Centinaia di voci progettate già pronte: scegline una e vai.",
|
||||
"zone_archetypes": "Archetipi",
|
||||
"zone_imports": "Le mie importazioni",
|
||||
@@ -818,7 +978,6 @@
|
||||
"badge": "Licenza commerciale",
|
||||
"hero_title": "Spedisci voci AI in produzione",
|
||||
"hero_desc": "OmniVoice Studio è disponibile come codice sorgente sotto la Functional Source License (FSL). La maggior parte degli utenti può valutare, prototipare e persino implementare internamente senza un accordo commerciale. Hai bisogno di una licenza commerciale solo se stai creando un prodotto o servizio concorrente o se il tuo caso d'uso non rientra nei confini dell'FSL.",
|
||||
"hero_note": "La creazione di un prodotto o servizio concorrente o la distribuzione su larga scala (ad esempio, la fornitura di API pay-per-use) richiede una licenza commerciale. Livelli di prezzo in arrivo: contattaci nel frattempo.",
|
||||
"why_title": "Perché le aziende scelgono OmniVoice",
|
||||
"pricing_title": "Prezzi",
|
||||
"faq_title": "Domande comuni",
|
||||
@@ -838,7 +997,8 @@
|
||||
"benefit_source": "Nucleo disponibile alla fonte",
|
||||
"benefit_source_desc": "Visibilità completa nello stack. Controlla, effettua il fork e adatta entro i termini della licenza.",
|
||||
"benefit_lang": "646 lingue",
|
||||
"benefit_lang_desc": "Trascrivi, traduci e doppia in 646 lingue con una qualità a livello umano."
|
||||
"benefit_lang_desc": "Trascrivi, traduci e doppia in 646 lingue con una qualità a livello umano.",
|
||||
"hero_note": "L’uso interno – anche su larga scala – è gratuito secondo l’FSL; è necessaria una licenza commerciale solo per offrire OmniVoice ad altri come prodotto o servizio concorrente (un'API ospitata o a pagamento, un'app rivenduta o con etichetta bianca). I livelli di prezzo arriveranno presto: contattaci nel frattempo."
|
||||
},
|
||||
"exportModal": {
|
||||
"export": "Esportazione",
|
||||
@@ -927,7 +1087,479 @@
|
||||
"dubbing_title": "Guarda il doppiaggio in azione",
|
||||
"dubbing_sync": "Riproduzione sincronizzata",
|
||||
"dubbing_picker": "Prova un'altra lingua:",
|
||||
"dubbing_cta": "Eseguilo sul tuo video →"
|
||||
"dubbing_cta": "Eseguilo sul tuo video →",
|
||||
"dubbing_loading": "Caricamento demo del doppiaggio…",
|
||||
"dubbing_dismiss": "Ignora la demo del doppiaggio",
|
||||
"original_tag": "originale",
|
||||
"dubbed_tag": "doppiato",
|
||||
"script_conversational": "Conversazionale",
|
||||
"script_technical": "Vocabolario tecnico",
|
||||
"script_french": "Non inglese (francese)",
|
||||
"aria_pause": "Pausa {{label}}",
|
||||
"aria_hear": "Ascolta {{label}}",
|
||||
"aria_replay": "Riproduci {{label}} tramite il trascrittore"
|
||||
},
|
||||
"direction": {
|
||||
"title": "Direzione per il segmento #{{id}}",
|
||||
"desc": "Di' alla pipeline come dovrebbe sentirsi questa linea. L'inglese semplice funziona: il sistema mappa le tue parole su una tassonomia stabile (energia/emozione/ritmo/intimità/formalità), quindi inserisce la tassonomia attraverso la traduzione cinematografica, il TTS e l'adattamento agli slot.",
|
||||
"label": "Direzione",
|
||||
"lineHint": "Riga: \"{{text}}\"",
|
||||
"placeholder": "ad es. urgente e sorpreso / caldo, speranzoso / sussurrato, intimo",
|
||||
"previewParse": "Anteprima dell'analisi",
|
||||
"previewFailed": "Anteprima non riuscita: {{message}}",
|
||||
"clear": "Chiaro",
|
||||
"cancel": "Annulla",
|
||||
"saveDirection": "Salva la direzione",
|
||||
"ttsInstruct": "TTS istruisce:",
|
||||
"nothingParsed": "— (niente analizzato)",
|
||||
"translateHint": "Suggerimento di traduzione:",
|
||||
"rateBias": "Distorsione tariffaria:",
|
||||
"speedsUp": "accelera",
|
||||
"slowsDown": "rallenta",
|
||||
"taxonomyTokens": "gettoni tassonomici"
|
||||
},
|
||||
"errors": {
|
||||
"title": "Questa scheda ha avuto un intoppo.",
|
||||
"desc": "Non preoccuparti: il resto dell'app funziona ancora. Puoi cambiare scheda o riprovare di seguito.",
|
||||
"tryAgain": "Riprova",
|
||||
"openDocs": "Apri i documenti per questo errore"
|
||||
},
|
||||
"keyboard": {
|
||||
"title": "Scorciatoie da tastiera",
|
||||
"footer": "Premi <1>?</1> in qualsiasi momento per aprirlo.",
|
||||
"or": "o",
|
||||
"nav": "Navigazione",
|
||||
"nav_cheatsheet": "Mostra questo foglietto illustrativo",
|
||||
"nav_closeModal": "Chiudi modale/annulla",
|
||||
"nav_save": "Salva progetto / conferma ritaglio",
|
||||
"segmentEditor": "Redattore di segmenti",
|
||||
"seg_split": "Segmento diviso in corrispondenza del cursore",
|
||||
"seg_merge": "Unisci con il segmento successivo",
|
||||
"seg_undo": "Annulla",
|
||||
"seg_redo": "Rifare",
|
||||
"seg_click": "Azione primaria",
|
||||
"seg_shiftClick": "Selezione della gamma",
|
||||
"trimmer": "Rifinitore audio",
|
||||
"trim_playPause": "Anteprima riproduzione/pausa",
|
||||
"trim_nudgeStart": "Spingi la maniglia di avvio",
|
||||
"trim_nudgeEnd": "Maniglia all'estremità spinta",
|
||||
"trim_fineNudge": "Bella spinta",
|
||||
"trim_coarseNudge": "Spinta grossolana",
|
||||
"trim_zoomIn": "Zoom avanti/indietro",
|
||||
"trim_fitAll": "Adatta a tutti/Adatta alla selezione",
|
||||
"trim_confirm": "Conferma il taglio",
|
||||
"dub": "Duplicare",
|
||||
"dub_generate": "Genera doppiaggio",
|
||||
"dub_sidebar": "Attiva/disattiva la barra laterale"
|
||||
},
|
||||
"network": {
|
||||
"sharing_on_title": "Condivisione su: fai clic per i dettagli",
|
||||
"share_on_network": "Condividi sulla tua rete",
|
||||
"switching": "Cambio…",
|
||||
"network": "Rete",
|
||||
"local": "Locale",
|
||||
"share_confirm_title": "Condividere sulla tua rete?",
|
||||
"share_confirm_hint": "Altri dispositivi sul tuo Wi-Fi/Ethernet potranno raggiungere OmniVoice utilizzando il PIN di accesso mostrato una volta acceso.",
|
||||
"enabling": "Abilitazione...",
|
||||
"enable": "Abilita",
|
||||
"shared_title": "Condiviso sulla tua rete",
|
||||
"no_interface": "Nessuna interfaccia di rete raggiungibile: connettiti a Wi-Fi/Ethernet.",
|
||||
"copy_link": "Copia collegamento",
|
||||
"open_in_browser": "Apri nel browser",
|
||||
"qr_alt": "QR per {{ip}}",
|
||||
"pin": "PIN:",
|
||||
"stop_sharing": "Smetti di condividere",
|
||||
"copied": "Copiato",
|
||||
"enable_error": "Impossibile abilitare la condivisione: {{message}}",
|
||||
"disable_error": "Impossibile disabilitare: {{message}}"
|
||||
},
|
||||
"readiness": {
|
||||
"checking_system": "Controllo del sistema...",
|
||||
"all_ready": "Tutti i sistemi pronti",
|
||||
"system_readiness": "Prontezza del sistema",
|
||||
"asr_model": "Modello ASR",
|
||||
"loaded_ready": "Carico e pronto",
|
||||
"loading_first_run": "Caricamento... (l'operazione potrebbe richiedere 1-2 minuti alla prima esecuzione)",
|
||||
"failed_to_load": "Impossibile caricare",
|
||||
"not_loaded_yet": "Non ancora caricato: verrà caricato alla prima trascrizione",
|
||||
"error_check_logs": "Errore: {{error}}. Controlla i log e prova a riavviare.",
|
||||
"check_logs_restart": "Controlla i log per eventuali errori di caricamento del modello. Prova a riavviare.",
|
||||
"llm_cinematic": "LLM (Cinematologico)",
|
||||
"llm_configure": "Configura TRANSLATE_BASE_URL per la qualità di traduzione cinematografica",
|
||||
"llm_set_env": "Imposta le variabili di ambiente TRANSLATE_BASE_URL e TRANSLATE_API_KEY. Funziona con Ollama, OpenAI, LM Studio, ecc.",
|
||||
"llm_optional": "Facoltativo: imposta TRANSLATE_BASE_URL per la qualità cinematografica"
|
||||
},
|
||||
"license": {
|
||||
"title": "Supertonic-3 — Accettazione della licenza",
|
||||
"intro": "Supertonic-3 viene spedito con due licenze distinte. Si prega di esaminarli entrambi prima di abilitare il motore.",
|
||||
"sdk_heading": "Codice SDK · MIT",
|
||||
"sdk_desc": "L'SDK di inferenza Python (supertonico) è concesso in licenza dal MIT. Uso permissivo, anche commerciale.",
|
||||
"read_mit": "Leggi la licenza MIT →",
|
||||
"model_heading": "Pesi del modello · OpenRAIL-M",
|
||||
"model_desc": "I pesi del modello Supertonic-3 sono rilasciati con la licenza OpenRAIL-M. Questa licenza limita l'uso a scopi non dannosi: consulta la licenza collegata per la serie completa di restrizioni basate sull'uso.",
|
||||
"read_openrail": "Leggi la licenza OpenRAIL-M →",
|
||||
"footer": "Facendo clic su Accetta si registra l'accettazione nelle impostazioni locali di OmniVoice e si abilita il motore. La tua accettazione viene memorizzata solo su questa macchina: nulla viene segnalato a Supertone Inc. o a terze parti.",
|
||||
"saving": "Salvataggio…",
|
||||
"accept": "Accetta",
|
||||
"accepted_toast": "Licenza Supertonic-3 accettata.",
|
||||
"accept_error": "Impossibile registrare l'accettazione della licenza: {{message}}"
|
||||
},
|
||||
"voicePreview": {
|
||||
"title": "Anteprima vocale",
|
||||
"close": "Chiudi l'anteprima",
|
||||
"default_text": "Ciao! Questa è un'anteprima di come suono con questa voce.",
|
||||
"default_voice": "Voce predefinita",
|
||||
"clone_profiles": "Clona profili",
|
||||
"designed_voices": "Voci progettate",
|
||||
"presets": "Preimpostazioni",
|
||||
"placeholder": "Digita qualcosa da ascoltare...",
|
||||
"stop": "Fermati",
|
||||
"regenerate": "Rigenerarsi",
|
||||
"preview": "Anteprima",
|
||||
"hint": "8 passaggi · anteprima veloce"
|
||||
},
|
||||
"header": {
|
||||
"kicker_studio": "Studio",
|
||||
"kicker_library": "Biblioteca",
|
||||
"kicker_preferences": "Preferenze",
|
||||
"kicker_licensing": "Licenza",
|
||||
"label_launchpad": "Launchpad",
|
||||
"label_clone": "Clonazione vocale",
|
||||
"label_design": "Progettazione vocale",
|
||||
"label_dub": "Doppiaggio",
|
||||
"label_projects": "OmniDrive",
|
||||
"label_gallery": "Galleria",
|
||||
"label_transcriptions": "Trascrizioni",
|
||||
"label_settings": "Impostazioni",
|
||||
"label_enterprise": "Licenza commerciale",
|
||||
"status_ready": "Pronto",
|
||||
"status_loading": "Caricamento…",
|
||||
"status_idle": "Inattivo",
|
||||
"memory_management": "Gestione della memoria",
|
||||
"flush": "A filo",
|
||||
"loaded_models": "Modelli caricati",
|
||||
"no_models": "Nessun modello caricato",
|
||||
"unload": "Scarica",
|
||||
"flush_caches": "Svuota cache",
|
||||
"unload_all_flush": "Scarica tutto + scarica"
|
||||
},
|
||||
"sidebar": {
|
||||
"tab_drive": "Guidare",
|
||||
"tab_history": "Storia",
|
||||
"tab_exports": "Esportazioni",
|
||||
"save_project": "Salva progetto doppiato",
|
||||
"save_new_project": "Salva come nuovo progetto doppiaggio",
|
||||
"dub_projects": "Duplicare progetti",
|
||||
"voice_clones": "Cloni vocali",
|
||||
"designed_voices": "Voci progettate",
|
||||
"no_dub_projects": "Nessun progetto doppiato salvato",
|
||||
"no_dub_hint": "Carica un video e fai clic su Salva per conservare il tuo lavoro.",
|
||||
"no_clones": "Nessun clone vocale ancora",
|
||||
"no_clones_hint": "Registra o carica l'audio, quindi fai clic su Salva come profilo vocale.",
|
||||
"no_designs": "Nessuna voce progettata ancora",
|
||||
"no_designs_hint": "Genera una voce e salvala dalla Cronologia.",
|
||||
"history_subtitle": "Cronologia della generazione · Memorizzata in SQLite",
|
||||
"no_history": "Nessuna storia generazionale",
|
||||
"no_history_hint": "Sintetizza l'audio o doppia un video: i risultati verranno visualizzati qui.",
|
||||
"clear_history": "Cancella cronologia",
|
||||
"clear_confirm": "Cancellare tutti gli elementi della cronologia {{count}}? Questa operazione non può essere annullata.",
|
||||
"history_cleared": "La cronologia è stata cancellata",
|
||||
"recent_exports": "Esportazioni recenti",
|
||||
"no_exports": "Nessun output scaricato",
|
||||
"no_exports_hint": "Esporta un file tramite Tauri per vederlo tracciato qui.",
|
||||
"show_in_folder": "Mostra nella cartella",
|
||||
"open": "Aperto",
|
||||
"select": "Seleziona",
|
||||
"try_voice": "Prova",
|
||||
"consistent": "coerente",
|
||||
"locked": "Bloccato",
|
||||
"clone_label": "Clonare",
|
||||
"design_label": "Progettazione",
|
||||
"dub_label": "Duplicare",
|
||||
"save_label": "Salva",
|
||||
"lock_identity": "Blocca l'identità vocale",
|
||||
"in_folder": "tra {{folder}}"
|
||||
},
|
||||
"trimmer": {
|
||||
"title": "Taglia l'audio di riferimento",
|
||||
"decoding": "Decodifica audio…",
|
||||
"meta_length": "Lunghezza {{duration}} · {{sampleRate}} Hz",
|
||||
"meta_rendering": "rendering della forma d'onda {{percent}}%",
|
||||
"keyboard_hint": "scroll = zoom · shift+scroll = pan · alt+trascina = pan · ⏐ ⟵ ⟶ ⏐ i tasti regolano le maniglie",
|
||||
"zoom_in": "Ingrandisci (+)",
|
||||
"zoom_out": "Rimpicciolisci (-)",
|
||||
"fit_all": "Adatto a tutti (casa)",
|
||||
"fit_selection": "Selezione adatta (Fine)",
|
||||
"fit_sel_btn": "ADATTA SEL",
|
||||
"view_range": "Visualizza {{start}} → {{end}} ({{duration}})",
|
||||
"start_label": "Inizia",
|
||||
"end_label": "Fine",
|
||||
"length_label": "Lunghezza",
|
||||
"too_long": ">{{max}}s",
|
||||
"too_short": "troppo corto",
|
||||
"length_ok": "ok",
|
||||
"loop_preview": "Anteprima del ciclo",
|
||||
"pause": "Pausa",
|
||||
"preview_selection": "Anteprima della selezione",
|
||||
"play_hint": "Spazio per giocare · Invio per confermare · Esc per annullare",
|
||||
"cancel": "Annulla",
|
||||
"use_trimmed": "Utilizzare tagliato",
|
||||
"decode_failed": "Decodifica non riuscita: {{message}}",
|
||||
"playback_failed": "Riproduzione non riuscita: {{message}}",
|
||||
"audio_load_failed": "Caricamento dell'audio non riuscito",
|
||||
"unit_seconds": "s"
|
||||
},
|
||||
"casting": {
|
||||
"title": "Casting dei relatori",
|
||||
"auto_assign_title": "Assegna automaticamente le voci dai cloni degli altoparlanti estratti",
|
||||
"auto_cast": "Trasmissione automatica",
|
||||
"all_cast": "Tutto il cast",
|
||||
"segments_count": "{{count}} segmenti",
|
||||
"assign_voice": "Assegna voce...",
|
||||
"from_video": "🎤 Dal video ({{name}})",
|
||||
"no_profiles": "Nessun profilo vocale ancora salvato.",
|
||||
"preview_voice": "Anteprima della voce"
|
||||
},
|
||||
"checkpoint": {
|
||||
"asr_title": "Trascrizioni pronte",
|
||||
"asr_cta": "Traduci",
|
||||
"asr_hint": "Correggi subito eventuali errori ASR: la dizione precisa salva i tentativi TTS in un secondo momento.",
|
||||
"translate_title": "Traduzioni pronte",
|
||||
"translate_cta": "Genera doppiaggio",
|
||||
"translate_hint": "Scorri il testo di destinazione. Le linee troppo lunghe aumentano la velocità; puoi anche modificare direttamente.",
|
||||
"done_title": "Duplicazione completata",
|
||||
"done_hint": "Rivedi i tempi e i rapporti di sincronizzazione. Modifica qualsiasi riga e premi \"Regen change\" per una rapida ripetizione parziale.",
|
||||
"segment_one": "{{count}} segmento",
|
||||
"segment_other": "{{count}} segmenti",
|
||||
"dismiss_title": "Ignora: non riapparirà per questa fase finché non verrà ricaricato"
|
||||
},
|
||||
"compare": {
|
||||
"title": "Confronto delle voci A/B",
|
||||
"close": "Confronto ravvicinato",
|
||||
"desc": "Confronta due voci fianco a fianco per prendere decisioni sul casting. L'app rimane interattiva dietro.",
|
||||
"test_phrase": "Frase di prova",
|
||||
"voice_a": "Voce A",
|
||||
"voice_b": "Voce B",
|
||||
"select_voice": "— Seleziona la voce —",
|
||||
"preset_suffix": "(Preimpostato)",
|
||||
"no_audio": "Nessun audio ancora",
|
||||
"close_btn": "Chiudi",
|
||||
"comparing": "Confrontando…",
|
||||
"compare_btn": "Confronta",
|
||||
"preparing_voice": "Preparazione della voce...",
|
||||
"generating_voice_a": "Generazione della voce A...",
|
||||
"generating_voice_b": "Generazione della voce B...",
|
||||
"comparison_complete": "Confronto completato!",
|
||||
"play_failed": "Riproduzione non riuscita: {{message}}"
|
||||
},
|
||||
"models": {
|
||||
"hf_token_set_toast": "Set di token HuggingFace: download più rapidi abilitati",
|
||||
"hf_token_save_failed": "Impossibile salvare il token",
|
||||
"install_started": "Installazione avviata: avanzamento nella riga",
|
||||
"delete_confirm": "Eliminare {{repoId}}? Puoi reinstallarlo più tardi.",
|
||||
"delete_confirm_title": "Elimina modello",
|
||||
"deleted": "Eliminato {{repoId}}",
|
||||
"reinstall_confirm": "Reinstallare {{repoId}}? Ciò eliminerà la copia corrente e verrà scaricata nuovamente.",
|
||||
"reinstall_confirm_title": "Reinstallare il modello",
|
||||
"reinstalling": "Reinstallazione",
|
||||
"recommended_installed": "I modelli consigliati sono già installati.",
|
||||
"started_downloading_one": "Iniziato il download del modello {{count}}",
|
||||
"started_downloading_other": "Ho iniziato a scaricare i modelli {{count}}",
|
||||
"install_failed": "Installazione non riuscita: {{message}}",
|
||||
"removing_cached": "Rimozione delle revisioni memorizzate nella cache…",
|
||||
"resolving_metadata": "Risoluzione dei metadati del repository",
|
||||
"retry_attempt": "Nuovo tentativo {{attempt}} — {{error}}",
|
||||
"connecting_hf": "Connessione a HuggingFace…",
|
||||
"resolving_files_one": "Risoluzione del file {{count}}…",
|
||||
"resolving_files_other": "Risoluzione dei file {{count}}…",
|
||||
"resolving_files_active": "Risoluzione dei file {{count}}… · {{file}}",
|
||||
"measuring": "misurazione…",
|
||||
"files_progress": "{{done}}/{{total}} file",
|
||||
"install_error": "Installazione non riuscita: {{error}}",
|
||||
"view_on_hf": "Visualizza su HuggingFace",
|
||||
"install_btn": "Installa",
|
||||
"reinstall_btn": "Reinstallare",
|
||||
"downloading": "scaricamento",
|
||||
"deleting": "eliminazione",
|
||||
"working": "lavorando",
|
||||
"installed": "installato",
|
||||
"not_installed": "non installato",
|
||||
"required_tag": "richiesto",
|
||||
"delete_btn": "Elimina",
|
||||
"hf_token_btn": "Gettone HF",
|
||||
"hf_set_title": "Imposta il token HuggingFace per download più rapidi",
|
||||
"get_token": "Ottieni gettone →",
|
||||
"reco_installed_for": "Pacchetto consigliato installato per {{device}}",
|
||||
"reco_for": "Consigliato per {{device}}",
|
||||
"starting": "A partire…",
|
||||
"required_size": "Richiesto ~{{size}} GB",
|
||||
"all_size": "Tutti ~{{size}} GB",
|
||||
"req_tag": "rich",
|
||||
"search_placeholder": "Cerca modelli…",
|
||||
"search_label": "Cerca modelli",
|
||||
"no_matches": "Nessun modello corrisponde ai tuoi filtri.",
|
||||
"sort_by": "Ordina per {{column}}",
|
||||
"column_model": "Modello",
|
||||
"column_role": "Ruolo",
|
||||
"column_size": "Dimensioni",
|
||||
"column_status": "Stato",
|
||||
"ready_badge": "Pronto",
|
||||
"loading_badge": "Caricamento…",
|
||||
"idle_badge": "Inattivo",
|
||||
"started_downloading_required_one": "Avviato il download del modello richiesto {{count}}",
|
||||
"started_downloading_required_other": "Iniziato il download dei {{count}} modelli richiesti"
|
||||
},
|
||||
"enterprise_faq": {
|
||||
"q_internal_tools": "Ho bisogno di una licenza per gli strumenti interni?",
|
||||
"a_internal_tools": "L'uso interno da parte dei tuoi dipendenti e appaltatori è uno scopo consentito dalla FSL: non è richiesta alcuna licenza. È necessaria una licenza commerciale quando rendi OmniVoice disponibile ad altri come parte di un prodotto o servizio concorrente (rivendita, SaaS in hosting, white label).",
|
||||
"q_try_before": "Posso provare prima di impegnarmi?",
|
||||
"a_try_before": "Sì. L'app completa può essere scaricata gratuitamente ed eseguita localmente per la valutazione ai sensi dell'FSL. Quando sei pronto per discutere di un'implementazione commerciale, inviaci un'e-mail e lavoreremo insieme sui dettagli.",
|
||||
"q_watermark": "E la filigrana?",
|
||||
"a_watermark": "La filigrana invisibile AudioSeal è incorporata per impostazione predefinita. I licenziatari commerciali possono disabilitarlo in Impostazioni → Privacy. L'uso gratuito/personale include sempre la filigrana.",
|
||||
"q_apache": "Il sorgente diventerà mai Apache 2.0?",
|
||||
"a_apache": "Sì. Ogni versione viene convertita automaticamente nella licenza Apache, versione 2.0 nel secondo anniversario della sua pubblicazione. Ciò significa che il rilascio di oggi è Apache 2.0 tra due anni, non è richiesta alcuna azione da parte nostra: l'FSL lo garantisce irrevocabilmente."
|
||||
},
|
||||
"gallery_extra": {
|
||||
"save_prompt": "Inserisci un nome per questo profilo vocale:",
|
||||
"saved_as_profile": "Voce salvata come profilo!",
|
||||
"save_profile_failed": "Impossibile salvare il profilo",
|
||||
"download_failed": "Download non riuscito: {{message}}",
|
||||
"trim_load_failed": "Impossibile caricare l'audio per il taglio: {{message}}",
|
||||
"upload_crop_failed": "Impossibile caricare la voce ritagliata: {{message}}"
|
||||
},
|
||||
"dub_workflow": {
|
||||
"preparing_audio": "Preparazione dell'audio…",
|
||||
"preparing_video": "Preparazione del video...",
|
||||
"extracting_audio_scenes": "Estrazione di audio e scene…",
|
||||
"transcribing_audio": "Trascrizione dell'audio…",
|
||||
"transcription_complete": "Trascrizione completata",
|
||||
"upload_cancelled": "Caricamento annullato",
|
||||
"upload_failed": "Caricamento non riuscito: {{message}}",
|
||||
"downloading_video": "Download del video…",
|
||||
"ingested": "Importato {{url}}",
|
||||
"ingest_cancelled": "Importazione annullata",
|
||||
"ingest_failed": "Inserimento URL non riuscito: {{message}}",
|
||||
"retry_cancelled": "Nuovo tentativo annullato",
|
||||
"transcription_failed": "Trascrizione non riuscita: {{message}}",
|
||||
"import_srt_no_job": "Carica o importa prima un video: non esiste un lavoro a cui allegare i sottotitoli.",
|
||||
"imported_cues": "Cue {{count}} importati da {{file}}",
|
||||
"skipped_malformed": "{{count}} saltato (formato non valido)",
|
||||
"dropped_overlap": "{{count}} eliminato (sovrapposizione)",
|
||||
"clamped_to_duration": "{{count}} fissato alla lunghezza del supporto",
|
||||
"srt_import_failed": "L'importazione SRT non è riuscita",
|
||||
"cleaned_one": "Frammento {{count}} pulito",
|
||||
"cleaned_other": "Frammenti {{count}} puliti",
|
||||
"segments_clean": "Segmenti già puliti",
|
||||
"cleanup_failed": "Pulizia non riuscita: {{message}}",
|
||||
"cinematic_no_llm": "La qualità cinematografica richiede un LLM: imposta TRANSLATE_BASE_URL + TRANSLATE_API_KEY (Ollama funziona localmente). Ritornando a Fast.",
|
||||
"translate_errors": "Segmento/i {{errorCount}}/{{totalCount}} non riuscito: {{firstError}}",
|
||||
"translated_segments": "Segmento/i {{count}} tradotto/i → {{lang}}",
|
||||
"translated_cinematic_suffix": "(Cinematologico)",
|
||||
"translation_failed": "Traduzione non riuscita: {{message}}",
|
||||
"regenerating": "Rigenerazione del segmento {{count}} in corso…",
|
||||
"generating_dub": "Generazione doppiaggio…",
|
||||
"generating_progress": "Generazione doppiaggio… {{current}}/{{total}}",
|
||||
"generation_aborted": "Generazione interrotta.",
|
||||
"dubbing_aborted": "Doppiaggio interrotto",
|
||||
"generation_stream_ended": "Il flusso di generazione è terminato prima del completamento",
|
||||
"dub_complete": "Duplicazione completata",
|
||||
"stop_failed": "Impossibile fermarsi",
|
||||
"save_first": "Fai prima clic su \"Carica e trascrivi\" in modo che il video venga elaborato sul server prima di essere salvato.",
|
||||
"project_saved": "Progetto salvato",
|
||||
"project_created": "Progetto creato",
|
||||
"save_failed": "Salvataggio non riuscito: {{message}}",
|
||||
"opened_project": "Aperto: {{name}}",
|
||||
"delete_project_confirm": "Eliminare questo progetto? Questa operazione non può essere annullata.",
|
||||
"project_deleted": "Progetto eliminato",
|
||||
"delete_history_confirm": "Eliminare questo elemento della cronologia?",
|
||||
"history_deleted": "Elemento della cronologia eliminato",
|
||||
"restored_state": "Stato della generazione precedente ripristinato",
|
||||
"upgrading_preview": "Aggiornamento dei segmenti dalla qualità di anteprima {{count}} alla qualità totale…"
|
||||
},
|
||||
"tts_errors": {
|
||||
"enter_text": "Inserisci il testo",
|
||||
"upload_or_select": "Carica un audio o seleziona un profilo vocale",
|
||||
"trim_hint": "L'audio è {{duration}}s: taglia a ≤{{max}}s per una migliore clonazione",
|
||||
"timeout": "Generazione scaduta: il modello potrebbe essere ancora in fase di download. Controlla Impostazioni → Registri, quindi riprova.",
|
||||
"error_prefix": "Errore: {{message}}",
|
||||
"ignored_unsupported": "Istruzione non supportata ignorata: {{items}}",
|
||||
"ignored_duplicate": "Ignorato (categoria già impostata): {{items}}"
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Condivisione e accesso remoto",
|
||||
"help": "Esponi questa istanza OmniVoice in esecuzione alle altre macchine senza riavviarla. Solo loopback è l'impostazione predefinita: nulla viene condiviso finché non lo attivi qui.",
|
||||
"local_network": "Rete locale",
|
||||
"local_help": "Condividi sul tuo Wi-Fi/Ethernet con un PIN di accesso monouso. Altri dispositivi scansionano il codice QR o aprono il collegamento.",
|
||||
"ports_title": "Porti",
|
||||
"ports_help": "Questi vengono impostati tramite variabili di ambiente lette all'avvio. Modificare il backend o la porta dell'interfaccia utente impostando la variabile e riavviando OmniVoice.",
|
||||
"backend_port": "Porta di back-end",
|
||||
"ui_port": "Porta dell'interfaccia utente",
|
||||
"lan_share_port": "Porta di condivisione LAN",
|
||||
"port_error": "Inserisci una porta compresa tra 1024 e 65535",
|
||||
"port_saved": "Porta di condivisione LAN salvata: si applica la prossima volta che abiliti la condivisione",
|
||||
"port_save_failed": "Impossibile salvare la porta: {{message}}",
|
||||
"saving": "Salvataggio…",
|
||||
"ports_note": "Le porte back-end e dell'interfaccia utente si applicano al riavvio. La porta di condivisione LAN verrà applicata la prossima volta che abiliti la condivisione.",
|
||||
"tailscale_title": "Tailscale (accesso remoto privato)",
|
||||
"tailscale_checking": "Verifica del tailscale...",
|
||||
"tailscale_absent": "Tailscale non rilevato. Installalo per raggiungere OmniVoice in modo sicuro da qualsiasi punto della tua tailnet privata.",
|
||||
"tailscale_install": "Installa Tailscale",
|
||||
"tailscale_running": "Tailscale è in esecuzione. Servi OmniVoice sulla tua tailnet privata.",
|
||||
"tailscale_not_logged_in": "Tailscale è installato ma non è stato effettuato l'accesso. Avvia e accedi prima a Tailscale.",
|
||||
"tailscale_enabled": "Servizio Tailscale abilitato",
|
||||
"tailscale_enable_failed": "Impossibile abilitare Tailscale",
|
||||
"tailscale_enable_error": "Impossibile abilitare Tailscale: {{message}}",
|
||||
"tailscale_disabled": "Servizio Tailscale disabilitato",
|
||||
"tailscale_disable_failed": "Impossibile disattivare Tailscale",
|
||||
"tailscale_disable_error": "Impossibile disattivare Tailscale: {{message}}",
|
||||
"tailscale_enabling": "Abilitazione...",
|
||||
"tailscale_enable_btn": "Abilita il servizio Tailscale",
|
||||
"tailscale_disabling": "Disabilitazione…",
|
||||
"tailscale_disable_btn": "Interrompi il servizio di Tailscale",
|
||||
"tailscale_copy": "Copia collegamento",
|
||||
"tailscale_open": "Apri nel browser",
|
||||
"copied": "Copiato",
|
||||
"tailscale_qr_alt": "Codice QR per l'URL Tailscale"
|
||||
},
|
||||
"reportBug": {
|
||||
"label": "Segnala un bug",
|
||||
"title": "Apre una pagina dei problemi di GitHub precompilata nel browser. Non viene inviato nulla finché non si fa clic su Invia."
|
||||
},
|
||||
"app": {
|
||||
"loading": "Caricamento…",
|
||||
"trimmed_loaded": "Audio tagliato caricato",
|
||||
"toast_exported": "Esportato: {{name}}",
|
||||
"toast_export_failed": "Esportazione non riuscita: {{message}}",
|
||||
"toast_open_folder_failed": "Impossibile aprire la cartella: {{message}}",
|
||||
"toast_saving": "Salvataggio {{name}}...",
|
||||
"toast_saved": "Salvato: {{path}}",
|
||||
"toast_save_error": "Errore di salvataggio: {{message}}",
|
||||
"toast_processing": "Elaborazione {{name}}...",
|
||||
"toast_downloaded": "Scaricato {{name}}",
|
||||
"toast_download_error": "Errore di download: {{message}}",
|
||||
"toast_upload_first": "Fai prima clic su \"Carica e trascrivi\" in modo che il video venga elaborato sul server prima di essere salvato.",
|
||||
"toast_save_failed": "Salvataggio non riuscito: {{message}}",
|
||||
"toast_opened": "Aperto: {{name}}",
|
||||
"toast_project_deleted": "Progetto eliminato",
|
||||
"toast_restored_state": "Stato della generazione precedente ripristinato",
|
||||
"toast_history_deleted": "Elemento della cronologia eliminato",
|
||||
"toast_flushed": "Scaricato — RAM {{ram}}G · VRAM {{vram}}G{{unloaded}}",
|
||||
"toast_model_unloaded": "· modello scarico",
|
||||
"toast_flush_failed": "Eliminazione non riuscita: {{message}}",
|
||||
"toast_project_saved": "Progetto salvato",
|
||||
"toast_project_created": "Progetto creato"
|
||||
},
|
||||
"update": {
|
||||
"available": "Aggiornamento {{version}} disponibile",
|
||||
"install": "Installa e riavvia",
|
||||
"install_hint": "Scarica l'aggiornamento e riavvia nella nuova versione",
|
||||
"downloading": "Aggiornamento… {{pct}}%",
|
||||
"restart": "Riavvia per aggiornare",
|
||||
"busy": "Completa prima il tuo doppiaggio, poi installa l'aggiornamento.",
|
||||
"whats_new": "Cosa c'è di nuovo",
|
||||
"failed": "Aggiornamento non riuscito",
|
||||
"retry": "Riprova",
|
||||
"dismiss": "Ignora"
|
||||
},
|
||||
"archetypes": {
|
||||
"featured": "In primo piano",
|
||||
@@ -945,5 +1577,25 @@
|
||||
"facet_accent": "Accento",
|
||||
"facet_lang": "Lingua",
|
||||
"facet_whisper": "Sussurrare"
|
||||
},
|
||||
"support": {
|
||||
"tab_support": "Supporto",
|
||||
"tab_license": "Licenza commerciale",
|
||||
"toggle_label": "Supporto o licenza commerciale",
|
||||
"other_ways": "Altri modi per aiutare",
|
||||
"star_github": "Stella su GitHub",
|
||||
"join_discord": "Unisciti a Discord"
|
||||
},
|
||||
"updates": {
|
||||
"tab": "Aggiornamenti",
|
||||
"up_to_date": "Aggiornato · v{{version}}",
|
||||
"check_now": "Controlla ora",
|
||||
"releases": "Rilasci",
|
||||
"current": "corrente",
|
||||
"prerelease": "anteprima",
|
||||
"loading": "Caricamento versioni…",
|
||||
"none": "Nessuna versione trovata",
|
||||
"load_error": "Impossibile caricare le versioni (offline?)",
|
||||
"retry_load": "Riprova"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,4 @@
|
||||
{
|
||||
"update": {
|
||||
"available": "アップデート {{version}} が利用可能です",
|
||||
"install": "インストールして再起動",
|
||||
"install_hint": "アップデートをダウンロードして新しいバージョンで再起動します",
|
||||
"downloading": "更新中… {{pct}}%",
|
||||
"restart": "再起動してアップデート",
|
||||
"busy": "先に吹き替えを完了してから、アップデートをインストールしてください。"
|
||||
},
|
||||
"nav": {
|
||||
"clone": "複製",
|
||||
"design": "デザイン",
|
||||
@@ -16,7 +8,10 @@
|
||||
"launchpad": "ランチパッド",
|
||||
"gallery": "ギャラリー",
|
||||
"transcripts": "成績証明書",
|
||||
"omnidrive": "オムニドライブ"
|
||||
"omnidrive": "オムニドライブ",
|
||||
"move_rail_right": "レールを右に移動します",
|
||||
"move_rail_left": "レールを左に移動します",
|
||||
"flip_rail": "レール側を反転"
|
||||
},
|
||||
"settings": {
|
||||
"general": "一般",
|
||||
@@ -45,7 +40,40 @@
|
||||
"ffmpeg_missing": "見つかりません",
|
||||
"ffmpeg_current": "現在のパス",
|
||||
"ffmpeg_desc": "自動検出が失敗した場合は、カスタム ffmpeg パスを設定します。",
|
||||
"ffmpeg_saved": "FFmpeg パス セット — 適用するにはバックエンドを再起動します。"
|
||||
"ffmpeg_saved": "FFmpeg パス セット — 適用するにはバックエンドを再起動します。",
|
||||
"diagnostics_copied": "診断がコピーされました — 問題レポートに貼り付けます。",
|
||||
"updater_desktop": "アップデーターはデスクトップ アプリでのみ実行されます。",
|
||||
"latest_version": "最新バージョンを使用しています。",
|
||||
"logs_load_failed": "ログのロードに失敗しました: {{message}}",
|
||||
"clear_frontend_confirm": "メモリ内のフロントエンド ログ バッファをクリアしますか?",
|
||||
"clear_frontend_title": "ログをクリアする",
|
||||
"frontend_logs_cleared": "フロントエンドログがクリアされました",
|
||||
"clear_tauri_confirm": "Tauri 側のログ ファイルを切り詰めますか? OS は新しいエントリを書き込み続けます。",
|
||||
"clear_tauri_title": "Tauri ログをクリアする",
|
||||
"nothing_to_clear": "クリアするものは何もありません。ディスク上にはまだ Tauri ログ ファイルがありません。",
|
||||
"cleared_tauri_one": "{{count}} Tauri ログ ファイルをクリアしました",
|
||||
"cleared_tauri_other": "{{count}} Tauri ログ ファイルをクリアしました",
|
||||
"clear_tauri_failed": "Tauri ログをクリアできませんでした: {{message}}",
|
||||
"clear_backend_confirm": "バックエンド ランタイムとクラッシュ ログをクリアしますか?これを元に戻すことはできません。",
|
||||
"clear_backend_title": "ログをクリアする",
|
||||
"backend_logs_cleared": "バックエンドログがクリアされました",
|
||||
"clear_backend_failed": "ログのクリアに失敗しました",
|
||||
"copy_failed": "コピーに失敗しました: {{message}}",
|
||||
"update_check_failed": "更新チェックに失敗しました: {{message}}",
|
||||
"save_failed": "保存に失敗しました: {{message}}",
|
||||
"clear_failed": "クリアに失敗しました: {{message}}",
|
||||
"engine_switched": "{{family}} → {{engine}}",
|
||||
"channel_set_failed": "チャネルの設定に失敗しました: {{message}}",
|
||||
"updater_downloading": "{{version}} をダウンロード中…",
|
||||
"updater_installed": "インストール済み — 再起動中。",
|
||||
"updater_available_title": "利用可能なアップデート",
|
||||
"updater_available_body": "バージョン {{version}} が利用可能です。\n\n{{notes}}\n\n今すぐダウンロードしてインストールしますか?",
|
||||
"updater_notes_fallback": "GitHub のリリース ノートを参照してください。",
|
||||
"shortcut_load_failed": "ショートカットを読み込めませんでした: {{message}}",
|
||||
"shortcut_set": "ディクテーションのショートカットが {{shortcut}} に設定されました",
|
||||
"shortcut_register_failed": "登録できませんでした: {{message}}",
|
||||
"shortcut_reset": "デフォルトにリセットする",
|
||||
"shortcut_reset_failed": "リセットに失敗しました: {{message}}"
|
||||
},
|
||||
"bootstrap": {
|
||||
"title": "OmniVoice Studio",
|
||||
@@ -70,7 +98,13 @@
|
||||
"suggest_lang": "日本語に切り替えますか?",
|
||||
"select_lang": "言語:",
|
||||
"lines_one": "{{count}}行",
|
||||
"lines_other": "{{count}} 行"
|
||||
"lines_other": "{{count}} 行",
|
||||
"region_global": "グローバル(ダイレクト)",
|
||||
"region_china": "中国(鏡)",
|
||||
"region_russia": "ロシア(鏡)",
|
||||
"region_restricted": "制限付き(ミラー)",
|
||||
"unknown_error": "不明なエラー",
|
||||
"retrying": "再試行中…"
|
||||
},
|
||||
"stories": {
|
||||
"title": "ストーリーエディター",
|
||||
@@ -159,7 +193,17 @@
|
||||
"reload": "強制リロード UI",
|
||||
"backend": "バックエンド",
|
||||
"frontend": "フロントエンド",
|
||||
"tauri": "タウリ"
|
||||
"tauri": "タウリ",
|
||||
"cancelOp": "操作をキャンセルする",
|
||||
"dismiss": "解雇する",
|
||||
"dismissStatus": "ステータスを却下",
|
||||
"search": "検索…",
|
||||
"no_matches": "一致しません",
|
||||
"recent_and_popular": "最近の人気の",
|
||||
"popular_label": "人気のある",
|
||||
"showing_of": "{{total}} の {{shown}} を表示しています。検索するには入力してください…",
|
||||
"yes": "はい",
|
||||
"no": "いいえ"
|
||||
},
|
||||
"launchpad": {
|
||||
"greeting": "こんにちは。",
|
||||
@@ -181,7 +225,16 @@
|
||||
"locked": "ロックされています",
|
||||
"open": "開く",
|
||||
"try_it": "試してみてください",
|
||||
"audio_only": "音声のみ"
|
||||
"audio_only": "音声のみ",
|
||||
"stories_title": "ストーリー",
|
||||
"stories_desc": "マルチボイスオーディオブック — キャラクターをキャストし、スクリプトを入力し、章ごとにエクスポートします。",
|
||||
"gallery_title": "ボイスギャラリー",
|
||||
"gallery_desc": "アクセント、年齢、スタイルごとに既製のデザインされた音声を参照できます。セットアップは必要ありません。",
|
||||
"transcripts_title": "成績証明書",
|
||||
"transcripts_desc": "オーディオやビデオを 646 言語で編集可能、検索可能なテキストに変換します。",
|
||||
"recent_files": "最近のファイル",
|
||||
"view_all_files": "すべてのファイルを表示する",
|
||||
"file": "ファイル"
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "プロンプト",
|
||||
@@ -272,11 +325,6 @@
|
||||
"outputs": "出力",
|
||||
"crash_log": "クラッシュログ",
|
||||
"update_endpoint": "エンドポイントの更新",
|
||||
"update_channel": "アップデートチャンネル",
|
||||
"channel_stable": "安定版",
|
||||
"channel_preview": "プレビュー",
|
||||
"channel_set": "アップデートチャンネルを {{channel}} に設定しました",
|
||||
"channel_preview_hint": "プレビューは最新の main ビルドを追跡します。新機能をいち早く利用できますが、テストは少なめです。安定版が先行している場合は安定版にフォールバックします。",
|
||||
"yes": "はい",
|
||||
"no": "いいえ",
|
||||
"web_preview": "ウェブプレビュー",
|
||||
@@ -285,7 +333,12 @@
|
||||
"copy_diagnostics": "コピー診断",
|
||||
"github": "GitHub 上のオムニボイス",
|
||||
"model_card": "モデルカード",
|
||||
"commercial_license": "商用ライセンス"
|
||||
"commercial_license": "商用ライセンス",
|
||||
"update_channel": "アップデートチャンネル",
|
||||
"channel_stable": "安定版",
|
||||
"channel_preview": "プレビュー",
|
||||
"channel_set": "アップデートチャンネルを {{channel}} に設定しました",
|
||||
"channel_preview_hint": "プレビューは最新の main ビルドを追跡します。新機能をいち早く利用できますが、テストは少なめです。安定版が先行している場合は安定版にフォールバックします。"
|
||||
},
|
||||
"privacy": {
|
||||
"desc": "すべては<1>このマシン</1> 上で実行されます。オンライン翻訳者 (Google、DeepL など) を明示的に使用するか、HuggingFace にプッシュしない限り、オーディオ、ビデオ、およびトランスクリプトがコンピューターから流出することはありません。",
|
||||
@@ -341,7 +394,30 @@
|
||||
"unavailable": "利用不可",
|
||||
"use": "使用する",
|
||||
"loading": "エンジンをロード中…",
|
||||
"refresh": "リフレッシュ"
|
||||
"refresh": "リフレッシュ",
|
||||
"matrixTitle": "エンジン互換性マトリックス",
|
||||
"loadFailed": "エンジンのロードに失敗しました: {{message}}",
|
||||
"couldNotLoad": "エンジンをロードできませんでした: {{message}}",
|
||||
"retry": "再試行",
|
||||
"activeEngine": "アクティブ {{family}}: {{engine}}",
|
||||
"engineCompatLabel": "{{family}} エンジンの互換性",
|
||||
"active": "アクティブな",
|
||||
"whyUnavailable": "なぜ利用できないのでしょうか?",
|
||||
"lastError": "最後のエラー: {{error}}",
|
||||
"installedAndReady": "インストールされて準備完了",
|
||||
"notInstalled": "インストールされていません",
|
||||
"available": "利用可能",
|
||||
"subprocessTitle": "独自のサブプロセス + venv で実行",
|
||||
"inProcessTitle": "OmniVoice Python プロセスで実行されます",
|
||||
"testEngine": "テストエンジン",
|
||||
"testing": "テスト中…",
|
||||
"recheck": "再確認",
|
||||
"rechecking": "再確認中…",
|
||||
"latencyMs": "{{ms}} ミリ秒",
|
||||
"failed": "失敗しました",
|
||||
"acceptLicense": "ライセンスに同意する",
|
||||
"noBackends": "バックエンドが登録されていません。",
|
||||
"switch_failed": "エンジンの切り替えに失敗しました"
|
||||
},
|
||||
"capture": {
|
||||
"desc": "グローバル ホットキーはデスクトップ アプリでのみ機能します。 Web UI は、ウィンドウにフォーカスがある間、ページ内 <1>Ctrl+Shift+Space</1> ショートカットを使用します。",
|
||||
@@ -353,13 +429,55 @@
|
||||
"record_shortcut": "録音ショートカット",
|
||||
"recording": "録音中…",
|
||||
"save": "保存",
|
||||
"reset_default": "デフォルトにリセットする"
|
||||
"reset_default": "デフォルトにリセットする",
|
||||
"listening_label": "聞いています…",
|
||||
"transcribing_label": "文字起こし中…",
|
||||
"pasted": "貼り付けた",
|
||||
"no_speech": "音声が検出されませんでした",
|
||||
"mic_denied": "マイクアクセスが拒否されました",
|
||||
"mic_denied_toast": "マイクへのアクセスが拒否されました。 {{hint}}",
|
||||
"mic_hint_mac": "macOS: [システム設定] → [プライバシーとセキュリティ] → [マイク] を開き、OmniVoice を有効にします。",
|
||||
"mic_hint_windows": "Windows: [設定] → [プライバシーとセキュリティ] → [マイク] を開き、OmniVoice を許可します。",
|
||||
"mic_hint_linux": "Linux: ユーザーがオーディオ グループに属していること、および WebView にマイク アクセスがあることを確認してください。",
|
||||
"transcription_failed": "転写に失敗しました: {{message}}"
|
||||
},
|
||||
"logs": {
|
||||
"no_tauri_log": "ディスク上にまだ Tauri ログがありません — デスクトップ ビルド経由で起動してログを作成します",
|
||||
"empty_frontend": "フロントエンド コンソール エントリはまだキャプチャされていません。アプリを操作します。すべての console.* がここに表示されます。",
|
||||
"empty_tauri": "Tauri ログは利用できません。デスクトップ シェルでのみ実行されます。",
|
||||
"empty_backend": "実行時ログが空です。バックエンドがログを記録すると、アクティビティがここに表示されます。"
|
||||
"empty_backend": "実行時ログが空です。バックエンドがログを記録すると、アクティビティがここに表示されます。",
|
||||
"title": "ログ",
|
||||
"source_backend": "バックエンド",
|
||||
"source_frontend": "フロントエンド",
|
||||
"source_tauri": "タウリ",
|
||||
"expand": "ログを展開する",
|
||||
"collapse": "ログを折りたたむ",
|
||||
"expand_aria": "ログパネルを展開します",
|
||||
"collapse_aria": "ログパネルを折りたたむ",
|
||||
"drag_resize": "ドラッグしてサイズを変更します",
|
||||
"refresh": "リフレッシュ",
|
||||
"refresh_aria": "ログを更新する",
|
||||
"copy_visible": "表示されているログをコピーする",
|
||||
"copy_visible_aria": "表示されているログをコピーする",
|
||||
"clear": "クリア",
|
||||
"clear_aria": "ログをクリアする",
|
||||
"report_issue": "問題を報告する (コピー診断)",
|
||||
"report_issue_aria": "問題を報告する",
|
||||
"close": "閉じる",
|
||||
"close_aria": "ログパネルを閉じる",
|
||||
"join_discord": "Discordに参加してください",
|
||||
"join_discord_aria": "Discord コミュニティに参加してください",
|
||||
"support_project": "このプロジェクトを支援する",
|
||||
"support_project_aria": "このプロジェクトを支援する",
|
||||
"empty_frontend_short": "フロントエンドのコンソール出力はまだありません。",
|
||||
"empty_lines": "線はありません。",
|
||||
"all_clear": "✅ すべてクリア – 問題は検出されませんでした",
|
||||
"log_cleared": "{{source}} ログがクリアされました",
|
||||
"clear_failed": "クリアに失敗しました: {{message}}",
|
||||
"log_copied": "{{source}} ログをコピーしました",
|
||||
"copy_failed": "コピーに失敗しました: {{message}}",
|
||||
"report_copied": "診断レポートがコピーされました — GitHub の問題に貼り付けます。",
|
||||
"report_failed": "レポートが失敗しました: {{message}}"
|
||||
},
|
||||
"voice_profile": {
|
||||
"test_text": "こんにちは — これはこの音声のテストです。",
|
||||
@@ -527,7 +645,17 @@
|
||||
"install_already": "{{engine}} はすでにインストールされています",
|
||||
"install_ok": "{{engine}} がインストールされました",
|
||||
"install_failed": "インストールに失敗しました: {{message}}",
|
||||
"prep_elapsed": "{{time}} 経過しました"
|
||||
"prep_elapsed": "{{time}} 経過しました",
|
||||
"add_language": "言語を追加する",
|
||||
"search_languages": "言語を検索…",
|
||||
"languages_selected_one": "{{count}} 言語が選択されました",
|
||||
"languages_selected_other": "{{count}} 言語が選択されました",
|
||||
"more_to_narrow": "+{{count}} 詳細 — 入力して絞り込む",
|
||||
"no_matches": "一致しません",
|
||||
"diagnostic_copied": "診断がコピーされました",
|
||||
"copy_failed": "コピーに失敗しました",
|
||||
"open_docs": "ドキュメントを開く",
|
||||
"copy_diagnostic": "コピー診断"
|
||||
},
|
||||
"glossary": {
|
||||
"title": "用語集",
|
||||
@@ -602,7 +730,17 @@
|
||||
"more_actions_title": "さらなるアクション",
|
||||
"speaker_pick": "選んでください…",
|
||||
"speaker_title_detected": "スピーカー — 検出されたスピーカーから選択するか、カスタム名を入力します",
|
||||
"speaker_title_custom": "スピーカー — 名前を入力します (ダイアライゼーション クローンは検出されません)。"
|
||||
"speaker_title_custom": "スピーカー — 名前を入力します (ダイアライゼーション クローンは検出されません)。",
|
||||
"time_edit_title": "クリックして開始時刻 (分:ss.s) を編集します。 Enter を押してコミットし、Esc を押してキャンセルします。",
|
||||
"fit_fits": "適合",
|
||||
"fit_fits_title": "ナチュラルレートのオーディオがスロット内に収まります。",
|
||||
"fit_overflows": "オーバーフロー +{{seconds}}s",
|
||||
"fit_overflows_title": "翻訳されたテキストが元のスロットより {{seconds}}s 長くなりました。音声は大幅にトリミングされました。テキストを短くするか、タイミングを「ビデオをストレッチ」に切り替えてください。",
|
||||
"fit_stretched": "ビデオ {{ratio}}×",
|
||||
"fit_stretched_title": "ストレッチ ビデオ モード: このセグメントのビデオは、自然なダブ オーディオに合わせて {{ratio}}× まで遅くされました。",
|
||||
"fit_compressed_title": "TTS オーディオはスロットの {{pct}}% であり、高度に圧縮されています。",
|
||||
"fit_audio_title": "オーディオはスロット内に収まります。",
|
||||
"fit_ratio_title": "TTS オーディオはスロットの {{pct}}% です。"
|
||||
},
|
||||
"voice": {
|
||||
"personality": "性格",
|
||||
@@ -697,10 +835,24 @@
|
||||
"status_running": "走っている",
|
||||
"status_done": "完了しました",
|
||||
"status_failed": "失敗しました",
|
||||
"status_cancelled": "キャンセルされました"
|
||||
"status_cancelled": "キャンセルされました",
|
||||
"add_to_queue_title": "ビデオをキューに追加",
|
||||
"drop_hint_text": "ここにビデオ ファイルをドロップするか、クリックして参照します",
|
||||
"drop_formats": "MP4・MOV・MKV・WEBM",
|
||||
"files_kicker": "ファイル ({{count}})",
|
||||
"file_size_mb": "{{size}} MB",
|
||||
"target_languages": "対象言語",
|
||||
"voice_kicker": "声",
|
||||
"default_option": "デフォルト",
|
||||
"clone_profiles": "プロファイルのクローンを作成する",
|
||||
"presets": "プリセット",
|
||||
"preserve_bg": "バックグラウンドオーディオの保存 (音楽/FX)",
|
||||
"estimate": "{{videos}} ビデオ × {{langs}} 言語 = {{jobs}} ジョブ",
|
||||
"select_files_langs": "ファイルと言語を選択してください",
|
||||
"add_to_queue": "キューに追加"
|
||||
},
|
||||
"gallery": {
|
||||
"title": "ギャラリー",
|
||||
"title": "OmniVoice ギャラリー",
|
||||
"search_placeholder": "YouTube を検索…",
|
||||
"all_voices": "すべての声 ({{count}})",
|
||||
"no_voices": "まだ声はありません",
|
||||
@@ -715,6 +867,14 @@
|
||||
"youtube_results": "YouTube の結果 ({{count}})",
|
||||
"clone_profile": "プロファイルのクローンを作成する",
|
||||
"crop_audio": "オーディオをトリミングする",
|
||||
"cat_disney": "ディズニー",
|
||||
"cat_anime": "アニメ",
|
||||
"cat_marvel": "マーベル/DC",
|
||||
"cat_celebs": "有名人",
|
||||
"cat_politicians": "政治家",
|
||||
"cat_news": "ニュースアンカー",
|
||||
"cat_gaming": "ゲーム",
|
||||
"cat_books": "本/映画",
|
||||
"subtitle": "何百もの既製のデザインされた音声 - 1 つを選んですぐに使用できます。",
|
||||
"zone_archetypes": "原型",
|
||||
"zone_imports": "私の輸入品",
|
||||
@@ -819,7 +979,6 @@
|
||||
"badge": "商用ライセンス",
|
||||
"hero_title": "本番環境で AI 音声を出荷",
|
||||
"hero_desc": "OmniVoice Studio は、Functional Source License (FSL) に基づいてソース提供されています。ほとんどのユーザーは、商業契約なしで評価、プロトタイプを作成し、さらには社内で導入することができます。商用ライセンスが必要になるのは、競合する製品やサービスを構築している場合、またはユースケースが FSL の境界外にある場合のみです。",
|
||||
"hero_note": "競合する製品やサービスを構築したり、大規模に展開したりする場合 (従量課金制 API の提供など)、商用ライセンスが必要です。価格帯は近日公開予定です。それまでにお問い合わせください。",
|
||||
"why_title": "企業が OmniVoice を選ぶ理由",
|
||||
"pricing_title": "価格設定",
|
||||
"faq_title": "よくある質問",
|
||||
@@ -839,7 +998,8 @@
|
||||
"benefit_source": "ソースが利用可能なコア",
|
||||
"benefit_source_desc": "スタックを完全に可視化します。ライセンス条項内で監査、フォーク、適応を行います。",
|
||||
"benefit_lang": "646の言語",
|
||||
"benefit_lang_desc": "人間レベルの品質で 646 言語の文字起こし、翻訳、吹き替えを行います。"
|
||||
"benefit_lang_desc": "人間レベルの品質で 646 言語の文字起こし、翻訳、吹き替えを行います。",
|
||||
"hero_note": "FSL では、内部使用は大規模であっても無料です。商用ライセンスは、OmniVoice を競合製品またはサービス (ホスト型 API または従量制 API、再販またはホワイトラベル アプリ) として他者に提供する場合にのみ必要です。価格帯は近日中に公開される予定です。それまでにお問い合わせください。"
|
||||
},
|
||||
"exportModal": {
|
||||
"export": "エクスポート",
|
||||
@@ -928,7 +1088,479 @@
|
||||
"dubbing_title": "吹き替えの様子をご覧ください",
|
||||
"dubbing_sync": "同期再生",
|
||||
"dubbing_picker": "別の言語を試してください:",
|
||||
"dubbing_cta": "これを自分のビデオで実行します→"
|
||||
"dubbing_cta": "これを自分のビデオで実行します→",
|
||||
"dubbing_loading": "ダビングデモを読み込み中…",
|
||||
"dubbing_dismiss": "ダビングデモを閉じる",
|
||||
"original_tag": "オリジナル",
|
||||
"dubbed_tag": "吹き替えられた",
|
||||
"script_conversational": "会話型",
|
||||
"script_technical": "技術用語",
|
||||
"script_french": "英語以外(フランス語)",
|
||||
"aria_pause": "一時停止 {{label}}",
|
||||
"aria_hear": "{{label}} を聞いてください",
|
||||
"aria_replay": "文字起こしを通じて {{label}} を再生する"
|
||||
},
|
||||
"direction": {
|
||||
"title": "セグメント #{{id}} の方向",
|
||||
"desc": "このラインがどのように感じられるべきかをパイプラインに伝えます。 Plain English は機能します。システムはあなたの単語を安定した分類法 (エネルギー / 感情 / ペース / 親密さ / 格式) にマッピングし、その分類法を映画翻訳、TTS、およびスロットフィットに通します。",
|
||||
"label": "方向",
|
||||
"lineHint": "行: 「{{text}}」",
|
||||
"placeholder": "例:緊急で驚いた / 温かく、希望に満ちた / ささやき、親密な",
|
||||
"previewParse": "解析のプレビュー",
|
||||
"previewFailed": "プレビューが失敗しました: {{message}}",
|
||||
"clear": "クリア",
|
||||
"cancel": "キャンセル",
|
||||
"saveDirection": "保存方向",
|
||||
"ttsInstruct": "TTS は次のように指示します。",
|
||||
"nothingParsed": "— (何も解析されませんでした)",
|
||||
"translateHint": "翻訳のヒント:",
|
||||
"rateBias": "レートバイアス:",
|
||||
"speedsUp": "スピードアップする",
|
||||
"slowsDown": "遅くなる",
|
||||
"taxonomyTokens": "分類トークン"
|
||||
},
|
||||
"errors": {
|
||||
"title": "このタブは問題に遭遇しました。",
|
||||
"desc": "心配しないでください。アプリの残りの部分は引き続き動作します。タブを切り替えるか、以下でもう一度お試しください。",
|
||||
"tryAgain": "もう一度試してください",
|
||||
"openDocs": "このエラーのドキュメントを開く"
|
||||
},
|
||||
"keyboard": {
|
||||
"title": "キーボードショートカット",
|
||||
"footer": "いつでも <1>?</1> を押してこれを開きます。",
|
||||
"or": "または",
|
||||
"nav": "ナビゲーション",
|
||||
"nav_cheatsheet": "このチートシートを表示する",
|
||||
"nav_closeModal": "モーダルを閉じる/キャンセル",
|
||||
"nav_save": "プロジェクトの保存 / トリムのコミット",
|
||||
"segmentEditor": "セグメントエディター",
|
||||
"seg_split": "カーソル位置でセグメントを分割",
|
||||
"seg_merge": "次のセグメントと結合",
|
||||
"seg_undo": "元に戻す",
|
||||
"seg_redo": "やり直し",
|
||||
"seg_click": "主なアクション",
|
||||
"seg_shiftClick": "範囲選択",
|
||||
"trimmer": "オーディオトリマー",
|
||||
"trim_playPause": "プレビュー再生/一時停止",
|
||||
"trim_nudgeStart": "開始ハンドルを微調整する",
|
||||
"trim_nudgeEnd": "ナッジエンドハンドル",
|
||||
"trim_fineNudge": "細かいナッジ",
|
||||
"trim_coarseNudge": "粗いナッジ",
|
||||
"trim_zoomIn": "ズームイン/アウト",
|
||||
"trim_fitAll": "すべてに適合 / 選択を適合",
|
||||
"trim_confirm": "トリムを確認する",
|
||||
"dub": "ダブ",
|
||||
"dub_generate": "ダブの生成",
|
||||
"dub_sidebar": "サイドバーの切り替え"
|
||||
},
|
||||
"network": {
|
||||
"sharing_on_title": "共有 - 詳細についてはクリックしてください",
|
||||
"share_on_network": "ネットワーク上で共有する",
|
||||
"switching": "切り替え中…",
|
||||
"network": "ネットワーク",
|
||||
"local": "ローカル",
|
||||
"share_confirm_title": "ネットワーク上で共有しますか?",
|
||||
"share_confirm_hint": "Wi-Fi/イーサネット上の他のデバイスは、OmniVoice がオンになると、表示されるアクセス PIN を使用して OmniVoice にアクセスできるようになります。",
|
||||
"enabling": "有効化中…",
|
||||
"enable": "有効にする",
|
||||
"shared_title": "ネットワーク上で共有されています",
|
||||
"no_interface": "到達可能なネットワーク インターフェイスがありません - Wi-Fi/イーサネットに接続してください。",
|
||||
"copy_link": "リンクをコピー",
|
||||
"open_in_browser": "ブラウザで開く",
|
||||
"qr_alt": "{{ip}} の QR",
|
||||
"pin": "PIN:",
|
||||
"stop_sharing": "共有を停止する",
|
||||
"copied": "コピーされました",
|
||||
"enable_error": "共有を有効にできませんでした: {{message}}",
|
||||
"disable_error": "無効にできませんでした: {{message}}"
|
||||
},
|
||||
"readiness": {
|
||||
"checking_system": "システムを確認中…",
|
||||
"all_ready": "すべてのシステムが準備完了",
|
||||
"system_readiness": "システムの準備状況",
|
||||
"asr_model": "ASRモデル",
|
||||
"loaded_ready": "ロードされて準備完了",
|
||||
"loading_first_run": "読み込み中… (初回実行時は 1 ~ 2 分かかる場合があります)",
|
||||
"failed_to_load": "ロードに失敗しました",
|
||||
"not_loaded_yet": "まだロードされていません - 最初の文字起こし時にロードされます",
|
||||
"error_check_logs": "エラー: {{error}}。ログを確認して再起動してみてください。",
|
||||
"check_logs_restart": "モデルの読み込みエラーがないかログを確認します。再起動してみてください。",
|
||||
"llm_cinematic": "LLM (映画)",
|
||||
"llm_configure": "TRANSLATE_BASE_URL を映画のような翻訳品質に設定する",
|
||||
"llm_set_env": "TRANSLATE_BASE_URL および TRANSLATE_API_KEY 環境変数を設定します。 Ollama、OpenAI、LM Studioなどと連携します。",
|
||||
"llm_optional": "オプション — TRANSLATE_BASE_URL を映画のような品質に設定します"
|
||||
},
|
||||
"license": {
|
||||
"title": "Supertonic-3 — ライセンスの受諾",
|
||||
"intro": "Supertonic-3 は 2 つの異なるライセンスの下で出荷されます。エンジンを有効にする前に両方を確認してください。",
|
||||
"sdk_heading": "SDK コード · MIT",
|
||||
"sdk_desc": "Python 推論 SDK (スーパートニック) は MIT ライセンスを取得しています。商用を含む使用は許可されます。",
|
||||
"read_mit": "MIT ライセンスを読む→",
|
||||
"model_heading": "モデルの重み · OpenRAIL-M",
|
||||
"model_desc": "Supertonic-3 モデルの重みは、OpenRAIL-M ライセンスに基づいてリリースされています。このライセンスは、悪意のない目的での使用を制限します。使用ベースの制限の完全なセットについては、リンクされたライセンスを参照してください。",
|
||||
"read_openrail": "OpenRAIL-M ライセンスを読む→",
|
||||
"footer": "[承諾] をクリックすると、承諾が OmniVoice のローカル設定に記録され、エンジンが有効になります。あなたの同意はこのマシンにのみ保存され、Supertone Inc. や第三者には何も報告されません。",
|
||||
"saving": "保存中…",
|
||||
"accept": "受け入れる",
|
||||
"accepted_toast": "Supertonic-3 ライセンスが受け入れられました。",
|
||||
"accept_error": "ライセンスの受諾を記録できませんでした: {{message}}"
|
||||
},
|
||||
"voicePreview": {
|
||||
"title": "音声プレビュー",
|
||||
"close": "プレビューを閉じる",
|
||||
"default_text": "こんにちは!これは、私がこの声でどのように聞こえるかのプレビューです。",
|
||||
"default_voice": "デフォルトボイス",
|
||||
"clone_profiles": "プロファイルのクローンを作成する",
|
||||
"designed_voices": "デザインされた声",
|
||||
"presets": "プリセット",
|
||||
"placeholder": "聞きたいことを入力してください…",
|
||||
"stop": "停止",
|
||||
"regenerate": "再生する",
|
||||
"preview": "プレビュー",
|
||||
"hint": "8ステップ・高速プレビュー"
|
||||
},
|
||||
"header": {
|
||||
"kicker_studio": "スタジオ",
|
||||
"kicker_library": "図書館",
|
||||
"kicker_preferences": "設定",
|
||||
"kicker_licensing": "ライセンス",
|
||||
"label_launchpad": "ランチパッド",
|
||||
"label_clone": "音声クローン",
|
||||
"label_design": "音声デザイン",
|
||||
"label_dub": "ダビング",
|
||||
"label_projects": "オムニドライブ",
|
||||
"label_gallery": "ギャラリー",
|
||||
"label_transcriptions": "転写",
|
||||
"label_settings": "設定",
|
||||
"label_enterprise": "商用ライセンス",
|
||||
"status_ready": "準備完了",
|
||||
"status_loading": "読み込み中…",
|
||||
"status_idle": "アイドル状態",
|
||||
"memory_management": "メモリ管理",
|
||||
"flush": "フラッシュ",
|
||||
"loaded_models": "ロードされたモデル",
|
||||
"no_models": "モデルがロードされていません",
|
||||
"unload": "アンロード",
|
||||
"flush_caches": "キャッシュをフラッシュする",
|
||||
"unload_all_flush": "すべてアンロード + フラッシュ"
|
||||
},
|
||||
"sidebar": {
|
||||
"tab_drive": "ドライブ",
|
||||
"tab_history": "歴史",
|
||||
"tab_exports": "輸出",
|
||||
"save_project": "ダブプロジェクトの保存",
|
||||
"save_new_project": "新しいダブプロジェクトとして保存",
|
||||
"dub_projects": "ダブプロジェクト",
|
||||
"voice_clones": "音声クローン",
|
||||
"designed_voices": "デザインされた声",
|
||||
"no_dub_projects": "保存されたダビングプロジェクトがありません",
|
||||
"no_dub_hint": "ビデオをアップロードし、「保存」をクリックして作業を保存します。",
|
||||
"no_clones": "音声クローンはまだありません",
|
||||
"no_clones_hint": "音声を録音またはアップロードし、「音声プロファイルとして保存」をクリックします。",
|
||||
"no_designs": "デザインされた音声はまだありません",
|
||||
"no_designs_hint": "音声を生成し、履歴から保存します。",
|
||||
"history_subtitle": "生成履歴 · SQLite に保存",
|
||||
"no_history": "世代履歴なし",
|
||||
"no_history_hint": "オーディオを合成するか、ビデオをダビングします。結果がここに表示されます。",
|
||||
"clear_history": "履歴をクリアする",
|
||||
"clear_confirm": "すべての {{count}} 履歴項目をクリアしますか?これを元に戻すことはできません。",
|
||||
"history_cleared": "履歴がクリアされました",
|
||||
"recent_exports": "最近のエクスポート",
|
||||
"no_exports": "ダウンロードされた出力はありません",
|
||||
"no_exports_hint": "Tauri 経由でファイルをエクスポートすると、ここで追跡されていることが確認できます。",
|
||||
"show_in_folder": "フォルダ内に表示",
|
||||
"open": "開く",
|
||||
"select": "選択",
|
||||
"try_voice": "試してみる",
|
||||
"consistent": "一貫した",
|
||||
"locked": "ロックされています",
|
||||
"clone_label": "クローン",
|
||||
"design_label": "デザイン",
|
||||
"dub_label": "ダブ",
|
||||
"save_label": "保存",
|
||||
"lock_identity": "音声 ID をロックする",
|
||||
"in_folder": "{{folder}} に"
|
||||
},
|
||||
"trimmer": {
|
||||
"title": "リファレンスオーディオをトリミングする",
|
||||
"decoding": "音声をデコード中…",
|
||||
"meta_length": "長さ {{duration}} · {{sampleRate}} Hz",
|
||||
"meta_rendering": "レンダリング波形 {{percent}}%",
|
||||
"keyboard_hint": "スクロール = ズーム · シフト + スクロール = パン · alt+ドラッグ = パン · ⏐ ⟵ ⟶ ⏐ キーでハンドルを調整",
|
||||
"zoom_in": "ズームイン (+)",
|
||||
"zoom_out": "ズームアウト (-)",
|
||||
"fit_all": "すべてに適合 (ホーム)",
|
||||
"fit_selection": "フィット選択(終了)",
|
||||
"fit_sel_btn": "フィットセル",
|
||||
"view_range": "{{start}} → {{end}} ({{duration}}) を表示",
|
||||
"start_label": "スタート",
|
||||
"end_label": "終了",
|
||||
"length_label": "長さ",
|
||||
"too_long": ">{{max}}s",
|
||||
"too_short": "短すぎる",
|
||||
"length_ok": "わかりました",
|
||||
"loop_preview": "ループプレビュー",
|
||||
"pause": "一時停止",
|
||||
"preview_selection": "選択範囲をプレビューする",
|
||||
"play_hint": "再生するスペース · 確定するには Enter · キャンセルするには Esc",
|
||||
"cancel": "キャンセル",
|
||||
"use_trimmed": "トリミングして使用する",
|
||||
"decode_failed": "デコードに失敗しました: {{message}}",
|
||||
"playback_failed": "再生に失敗しました: {{message}}",
|
||||
"audio_load_failed": "オーディオのロードに失敗しました",
|
||||
"unit_seconds": "s"
|
||||
},
|
||||
"casting": {
|
||||
"title": "スピーカーの鋳造",
|
||||
"auto_assign_title": "抽出されたスピーカークローンから音声を自動割り当て",
|
||||
"auto_cast": "オートキャスト",
|
||||
"all_cast": "オールキャスト",
|
||||
"segments_count": "{{count}} セグメント",
|
||||
"assign_voice": "音声を割り当て…",
|
||||
"from_video": "🎤 動画より ({{name}})",
|
||||
"no_profiles": "音声プロファイルはまだ保存されていません。",
|
||||
"preview_voice": "プレビュー音声"
|
||||
},
|
||||
"checkpoint": {
|
||||
"asr_title": "トランスクリプトの準備完了",
|
||||
"asr_cta": "翻訳する",
|
||||
"asr_hint": "ASR エラーを今すぐ修正します。厳密なディクショニングにより、後で TTS の試行を節約できます。",
|
||||
"translate_title": "翻訳準備完了",
|
||||
"translate_cta": "ダブの生成",
|
||||
"translate_hint": "対象のテキストをざっと読んでください。長すぎる回線の速度が向上します。直接編集することもできます。",
|
||||
"done_title": "ダビング完了",
|
||||
"done_hint": "タイミングと同期率を確認します。任意の行を微調整し、「Regen Changed」を押すと、部分的に素早くやり直すことができます。",
|
||||
"segment_one": "{{count}} セグメント",
|
||||
"segment_other": "{{count}} セグメント",
|
||||
"dismiss_title": "閉じる — リロードするまでこのステージでは再表示されません"
|
||||
},
|
||||
"compare": {
|
||||
"title": "A/B音声比較",
|
||||
"close": "厳密な比較",
|
||||
"desc": "2 つの声を並べて比較して、キャスティングを決定します。アプリはインタラクティブなままです。",
|
||||
"test_phrase": "テストフレーズ",
|
||||
"voice_a": "ボイスA",
|
||||
"voice_b": "ボイスB",
|
||||
"select_voice": "— 音声を選択 —",
|
||||
"preset_suffix": "(プリセット)",
|
||||
"no_audio": "まだ音声がありません",
|
||||
"close_btn": "閉じる",
|
||||
"comparing": "比較中…",
|
||||
"compare_btn": "比較する",
|
||||
"preparing_voice": "音声を準備しています...",
|
||||
"generating_voice_a": "音声を生成中...",
|
||||
"generating_voice_b": "ボイスBを生成中...",
|
||||
"comparison_complete": "比較完了!",
|
||||
"play_failed": "プレイに失敗しました: {{message}}"
|
||||
},
|
||||
"models": {
|
||||
"hf_token_set_toast": "HuggingFace トークン セット - より高速なダウンロードが可能",
|
||||
"hf_token_save_failed": "トークンの保存に失敗しました",
|
||||
"install_started": "インストールが開始されました - 進行状況",
|
||||
"delete_confirm": "{{repoId}} を削除しますか?後で再インストールできます。",
|
||||
"delete_confirm_title": "モデルの削除",
|
||||
"deleted": "{{repoId}} を削除しました",
|
||||
"reinstall_confirm": "{{repoId}} を再インストールしますか?これにより、現在のコピーが削除され、再度ダウンロードされます。",
|
||||
"reinstall_confirm_title": "モデルを再インストールする",
|
||||
"reinstalling": "再インストール中",
|
||||
"recommended_installed": "推奨モデルはすでにインストールされています。",
|
||||
"started_downloading_one": "{{count}} モデルのダウンロードを開始しました",
|
||||
"started_downloading_other": "{{count}} モデルのダウンロードを開始しました",
|
||||
"install_failed": "インストールに失敗しました: {{message}}",
|
||||
"removing_cached": "キャッシュされたリビジョンを削除しています…",
|
||||
"resolving_metadata": "リポジトリのメタデータの解決",
|
||||
"retry_attempt": "再試行 {{attempt}} — {{error}}",
|
||||
"connecting_hf": "ハグフェイスに接続中…",
|
||||
"resolving_files_one": "{{count}} ファイルを解決しています…",
|
||||
"resolving_files_other": "{{count}} ファイルを解決しています…",
|
||||
"resolving_files_active": "{{count}} ファイルを解決中… · {{file}}",
|
||||
"measuring": "測定中…",
|
||||
"files_progress": "{{done}}/{{total}} ファイル",
|
||||
"install_error": "インストールに失敗しました: {{error}}",
|
||||
"view_on_hf": "ハギングフェイスで見る",
|
||||
"install_btn": "インストール",
|
||||
"reinstall_btn": "再インストール",
|
||||
"downloading": "ダウンロード",
|
||||
"deleting": "削除する",
|
||||
"working": "働いている",
|
||||
"installed": "インストールされています",
|
||||
"not_installed": "インストールされていません",
|
||||
"required_tag": "必須",
|
||||
"delete_btn": "削除",
|
||||
"hf_token_btn": "HFトークン",
|
||||
"hf_set_title": "ダウンロードを高速化するために HuggingFace トークンを設定する",
|
||||
"get_token": "トークンを取得 →",
|
||||
"reco_installed_for": "{{device}} にインストールされている推奨バンドル",
|
||||
"reco_for": "{{device}} に推奨",
|
||||
"starting": "開始中…",
|
||||
"required_size": "必要 ~{{size}} GB",
|
||||
"all_size": "すべて ~{{size}} GB",
|
||||
"req_tag": "要求",
|
||||
"search_placeholder": "モデルを検索…",
|
||||
"search_label": "モデルを検索する",
|
||||
"no_matches": "フィルターに一致するモデルはありません。",
|
||||
"sort_by": "{{column}} で並べ替え",
|
||||
"column_model": "モデル",
|
||||
"column_role": "役割",
|
||||
"column_size": "サイズ",
|
||||
"column_status": "ステータス",
|
||||
"ready_badge": "準備完了",
|
||||
"loading_badge": "読み込み中…",
|
||||
"idle_badge": "アイドル状態",
|
||||
"started_downloading_required_one": "{{count}} 必須モデルのダウンロードを開始しました",
|
||||
"started_downloading_required_other": "{{count}} 必須モデルのダウンロードを開始しました"
|
||||
},
|
||||
"enterprise_faq": {
|
||||
"q_internal_tools": "内部ツールにはライセンスが必要ですか?",
|
||||
"a_internal_tools": "従業員および請負業者による内部使用は、FSL の下で許可された目的であり、ライセンスは必要ありません。 OmniVoice を競合製品またはサービス (再販、ホスト型 SaaS、ホワイトラベル) の一部として他者が利用できるようにする場合は、商用ライセンスが必要です。",
|
||||
"q_try_before": "コミットする前に試してみることはできますか?",
|
||||
"a_try_before": "はい。完全なアプリは、FSL に基づいて評価のために無料でダウンロードしてローカルで実行できます。商用展開について話し合う準備ができたら、メールでご連絡ください。詳細については一緒に検討させていただきます。",
|
||||
"q_watermark": "透かしについてはどうですか?",
|
||||
"a_watermark": "デフォルトでは、目に見えない AudioSeal ウォーターマークが埋め込まれています。商用ライセンシーは、「設定」→「プライバシー」で無効にすることができます。無料/個人使用には必ずウォーターマークが含まれます。",
|
||||
"q_apache": "ソースが Apache 2.0 になることはありますか?",
|
||||
"a_apache": "はい。各リリースは、公開から 2 年目に自動的に Apache License バージョン 2.0 に変換されます。つまり、今日のリリースは 2 年後の Apache 2.0 であり、私たちからのアクションは必要ありません。FSL はそれを取り消し不能に保証します。"
|
||||
},
|
||||
"gallery_extra": {
|
||||
"save_prompt": "この音声プロファイルの名前を入力してください:",
|
||||
"saved_as_profile": "音声をプロフィールとして保存しました!",
|
||||
"save_profile_failed": "プロファイルの保存に失敗しました",
|
||||
"download_failed": "ダウンロードに失敗しました: {{message}}",
|
||||
"trim_load_failed": "トリミング用のオーディオのロードに失敗しました: {{message}}",
|
||||
"upload_crop_failed": "トリミングされた音声のアップロードに失敗しました: {{message}}"
|
||||
},
|
||||
"dub_workflow": {
|
||||
"preparing_audio": "オーディオを準備しています…",
|
||||
"preparing_video": "ビデオを準備しています…",
|
||||
"extracting_audio_scenes": "オーディオとシーンを抽出しています…",
|
||||
"transcribing_audio": "音声を文字に起こしています…",
|
||||
"transcription_complete": "文字起こしが完了しました",
|
||||
"upload_cancelled": "アップロードがキャンセルされました",
|
||||
"upload_failed": "アップロードに失敗しました: {{message}}",
|
||||
"downloading_video": "ビデオをダウンロード中…",
|
||||
"ingested": "{{url}} を摂取しました",
|
||||
"ingest_cancelled": "取り込みがキャンセルされました",
|
||||
"ingest_failed": "URL の取り込みに失敗しました: {{message}}",
|
||||
"retry_cancelled": "再試行がキャンセルされました",
|
||||
"transcription_failed": "転写に失敗しました: {{message}}",
|
||||
"import_srt_no_job": "まずビデオをアップロードまたは取り込みます。字幕を付ける作業はありません。",
|
||||
"imported_cues": "{{file}} から {{count}} キューをインポートしました",
|
||||
"skipped_malformed": "{{count}} がスキップされました (不正な形式)",
|
||||
"dropped_overlap": "{{count}} が削除されました (重複)",
|
||||
"clamped_to_duration": "{{count}} メディアの長さにクランプ",
|
||||
"srt_import_failed": "SRT インポートに失敗しました",
|
||||
"cleaned_one": "{{count}} フラグメントをクリーンアップしました",
|
||||
"cleaned_other": "{{count}} フラグメントをクリーンアップしました",
|
||||
"segments_clean": "セグメントはすでにクリーンになっています",
|
||||
"cleanup_failed": "クリーンアップに失敗しました: {{message}}",
|
||||
"cinematic_no_llm": "映画のような品質には LLM が必要です — TRANSLATE_BASE_URL + TRANSLATE_API_KEY を設定します (Ollama はローカルで動作します)。 Fast に戻ります。",
|
||||
"translate_errors": "{{errorCount}}/{{totalCount}} セグメントが失敗しました: {{firstError}}",
|
||||
"translated_segments": "{{count}} セグメント → {{lang}} を翻訳しました",
|
||||
"translated_cinematic_suffix": "(映画的)",
|
||||
"translation_failed": "翻訳に失敗しました: {{message}}",
|
||||
"regenerating": "{{count}} セグメントを再生成中…",
|
||||
"generating_dub": "ダビングを生成中…",
|
||||
"generating_progress": "ダビングを生成中… {{current}}/{{total}}",
|
||||
"generation_aborted": "生成が中止されました。",
|
||||
"dubbing_aborted": "ダビングが中止されました",
|
||||
"generation_stream_ended": "生成ストリームが完了前に終了しました",
|
||||
"dub_complete": "ダビング完了",
|
||||
"stop_failed": "停止に失敗しました",
|
||||
"save_first": "まず「アップロードと文字起こし」をクリックして、ビデオが保存前にサーバー上で処理されるようにしてください。",
|
||||
"project_saved": "プロジェクトが保存されました",
|
||||
"project_created": "プロジェクトが作成されました",
|
||||
"save_failed": "保存に失敗しました: {{message}}",
|
||||
"opened_project": "オープン済み: {{name}}",
|
||||
"delete_project_confirm": "このプロジェクトを削除しますか?これを元に戻すことはできません。",
|
||||
"project_deleted": "プロジェクトが削除されました",
|
||||
"delete_history_confirm": "この履歴項目を削除しますか?",
|
||||
"history_deleted": "履歴項目が削除されました",
|
||||
"restored_state": "前世代の状態を復元",
|
||||
"upgrading_preview": "{{count}} プレビュー品質のセグメントをフル品質にアップグレードしています…"
|
||||
},
|
||||
"tts_errors": {
|
||||
"enter_text": "テキストを入力してください",
|
||||
"upload_or_select": "音声をアップロードするか、音声プロファイルを選択してください",
|
||||
"trim_hint": "オーディオは {{duration}}s — 最適なクローン作成のために ≤{{max}}s にトリミングします",
|
||||
"timeout": "生成がタイムアウトしました — モデルはまだダウンロード中の可能性があります。 「設定」→「ログ」を確認して、もう一度お試しください。",
|
||||
"error_prefix": "エラー: {{message}}",
|
||||
"ignored_unsupported": "サポートされていない命令は無視されました: {{items}}",
|
||||
"ignored_duplicate": "無視 (カテゴリはすでに設定されています): {{items}}"
|
||||
},
|
||||
"sharing": {
|
||||
"title": "共有とリモートアクセス",
|
||||
"help": "この実行中の OmniVoice インスタンスを再起動せずに他のマシンに公開します。デフォルトはループバックのみです。ここでオンにするまでは何も共有されません。",
|
||||
"local_network": "ローカルネットワーク",
|
||||
"local_help": "ワンタイムアクセスPINを使用してWi-Fi/イーサネット上で共有します。他のデバイスは QR コードをスキャンするか、リンクを開きます。",
|
||||
"ports_title": "ポート",
|
||||
"ports_help": "これらは、起動時に読み取られる環境変数によって設定されます。変数を設定し、OmniVoice を再起動して、バックエンドまたは UI ポートを変更します。",
|
||||
"backend_port": "バックエンドポート",
|
||||
"ui_port": "UIポート",
|
||||
"lan_share_port": "LAN共有ポート",
|
||||
"port_error": "1024 ~ 65535 のポートを入力してください",
|
||||
"port_saved": "LAN 共有ポートが保存されました - 次回共有を有効にしたときに適用されます",
|
||||
"port_save_failed": "ポートを保存できませんでした: {{message}}",
|
||||
"saving": "保存中…",
|
||||
"ports_note": "バックエンドおよび UI ポートは再起動時に適用されます。 LAN 共有ポートは、次回共有を有効にするときに適用されます。",
|
||||
"tailscale_title": "Tailscale (プライベート リモート アクセス)",
|
||||
"tailscale_checking": "テールスケールを確認しています…",
|
||||
"tailscale_absent": "テールスケールが検出されませんでした。これをインストールすると、プライベート テールネット上のどこからでも OmniVoice に安全にアクセスできます。",
|
||||
"tailscale_install": "テールスケールのインストール",
|
||||
"tailscale_running": "テールスケールが実行中です。プライベート テールネット経由で OmniVoice を提供します。",
|
||||
"tailscale_not_logged_in": "Tailscale はインストールされていますが、ログインしていません。まず、Tailscale を起動してサインインします。",
|
||||
"tailscale_enabled": "テールスケール サーブが有効になっています",
|
||||
"tailscale_enable_failed": "テールスケールを有効にできませんでした",
|
||||
"tailscale_enable_error": "テールスケールを有効にできませんでした: {{message}}",
|
||||
"tailscale_disabled": "テールスケール サーブが無効になっています",
|
||||
"tailscale_disable_failed": "テールスケールを無効にできませんでした",
|
||||
"tailscale_disable_error": "Tailscale を無効にできませんでした: {{message}}",
|
||||
"tailscale_enabling": "有効化中…",
|
||||
"tailscale_enable_btn": "テールスケール サーブを有効にする",
|
||||
"tailscale_disabling": "無効化中…",
|
||||
"tailscale_disable_btn": "テイルスケールのサーブを止める",
|
||||
"tailscale_copy": "リンクをコピー",
|
||||
"tailscale_open": "ブラウザで開く",
|
||||
"copied": "コピーされました",
|
||||
"tailscale_qr_alt": "Tailscale URL の QR コード"
|
||||
},
|
||||
"reportBug": {
|
||||
"label": "バグを報告する",
|
||||
"title": "ブラウザで事前に入力された GitHub の問題ページを開きます。 「送信」をクリックするまでは何も送信されません。"
|
||||
},
|
||||
"app": {
|
||||
"loading": "読み込み中…",
|
||||
"trimmed_loaded": "トリミングされたオーディオがロードされました",
|
||||
"toast_exported": "エクスポート済み: {{name}}",
|
||||
"toast_export_failed": "エクスポートに失敗しました: {{message}}",
|
||||
"toast_open_folder_failed": "フォルダーを開けませんでした: {{message}}",
|
||||
"toast_saving": "{{name}} を保存しています...",
|
||||
"toast_saved": "保存済み: {{path}}",
|
||||
"toast_save_error": "保存エラー: {{message}}",
|
||||
"toast_processing": "{{name}} を処理しています...",
|
||||
"toast_downloaded": "{{name}} をダウンロードしました",
|
||||
"toast_download_error": "ダウンロード エラー: {{message}}",
|
||||
"toast_upload_first": "まず「アップロードと文字起こし」をクリックして、ビデオが保存前にサーバー上で処理されるようにしてください。",
|
||||
"toast_save_failed": "保存に失敗しました: {{message}}",
|
||||
"toast_opened": "オープン済み: {{name}}",
|
||||
"toast_project_deleted": "プロジェクトが削除されました",
|
||||
"toast_restored_state": "前世代の状態を復元",
|
||||
"toast_history_deleted": "履歴項目が削除されました",
|
||||
"toast_flushed": "フラッシュ — RAM {{ram}}G · VRAM {{vram}}G{{unloaded}}",
|
||||
"toast_model_unloaded": "· モデルがアンロードされました",
|
||||
"toast_flush_failed": "フラッシュに失敗しました: {{message}}",
|
||||
"toast_project_saved": "プロジェクトが保存されました",
|
||||
"toast_project_created": "プロジェクトが作成されました"
|
||||
},
|
||||
"update": {
|
||||
"available": "アップデート {{version}} が利用可能です",
|
||||
"install": "インストールして再起動",
|
||||
"install_hint": "アップデートをダウンロードして新しいバージョンで再起動します",
|
||||
"downloading": "更新中… {{pct}}%",
|
||||
"restart": "再起動してアップデート",
|
||||
"busy": "先に吹き替えを完了してから、アップデートをインストールしてください。",
|
||||
"whats_new": "新機能",
|
||||
"failed": "アップデートに失敗しました",
|
||||
"retry": "再試行",
|
||||
"dismiss": "解雇する"
|
||||
},
|
||||
"archetypes": {
|
||||
"featured": "注目の",
|
||||
@@ -946,5 +1578,25 @@
|
||||
"facet_accent": "アクセント",
|
||||
"facet_lang": "言語",
|
||||
"facet_whisper": "ささやき"
|
||||
},
|
||||
"support": {
|
||||
"tab_support": "サポート",
|
||||
"tab_license": "商用ライセンス",
|
||||
"toggle_label": "サポートまたは商用ライセンス",
|
||||
"other_ways": "その他の支援方法",
|
||||
"star_github": "GitHub でスターを付ける",
|
||||
"join_discord": "Discordに参加する"
|
||||
},
|
||||
"updates": {
|
||||
"tab": "アップデート",
|
||||
"up_to_date": "最新 · v{{version}}",
|
||||
"check_now": "今すぐチェック",
|
||||
"releases": "リリース",
|
||||
"current": "現在",
|
||||
"prerelease": "プレビュー",
|
||||
"loading": "リリースを読み込み中…",
|
||||
"none": "リリースが見つかりませんでした",
|
||||
"load_error": "リリースをロードできませんでした (オフライン?)",
|
||||
"retry_load": "再試行"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,4 @@
|
||||
{
|
||||
"update": {
|
||||
"available": "업데이트 {{version}} 사용 가능",
|
||||
"install": "설치 후 다시 시작",
|
||||
"install_hint": "업데이트를 다운로드하고 새 버전으로 다시 시작합니다",
|
||||
"downloading": "업데이트 중… {{pct}}%",
|
||||
"restart": "다시 시작하여 업데이트",
|
||||
"busy": "먼저 더빙을 완료한 후 업데이트를 설치하세요."
|
||||
},
|
||||
"nav": {
|
||||
"clone": "복제",
|
||||
"design": "디자인",
|
||||
@@ -16,7 +8,10 @@
|
||||
"launchpad": "런치패드",
|
||||
"gallery": "갤러리",
|
||||
"transcripts": "성적 증명서",
|
||||
"omnidrive": "옴니드라이브"
|
||||
"omnidrive": "옴니드라이브",
|
||||
"move_rail_right": "레일을 오른쪽으로 이동",
|
||||
"move_rail_left": "레일을 왼쪽으로 이동",
|
||||
"flip_rail": "레일 측면 뒤집기"
|
||||
},
|
||||
"settings": {
|
||||
"general": "일반",
|
||||
@@ -45,7 +40,40 @@
|
||||
"ffmpeg_missing": "찾을 수 없음",
|
||||
"ffmpeg_current": "현재 경로",
|
||||
"ffmpeg_desc": "자동 감지에 실패하면 사용자 정의 ffmpeg 경로를 설정하십시오.",
|
||||
"ffmpeg_saved": "FFmpeg 경로 설정 - 적용하려면 백엔드를 다시 시작하세요."
|
||||
"ffmpeg_saved": "FFmpeg 경로 설정 - 적용하려면 백엔드를 다시 시작하세요.",
|
||||
"diagnostics_copied": "진단이 복사되었습니다. 문제 보고서에 붙여넣으세요.",
|
||||
"updater_desktop": "업데이터는 데스크톱 앱에서만 실행됩니다.",
|
||||
"latest_version": "최신 버전을 사용 중입니다.",
|
||||
"logs_load_failed": "로그를 로드하지 못했습니다: {{message}}",
|
||||
"clear_frontend_confirm": "메모리 내 프런트엔드 로그 버퍼를 삭제하시겠습니까?",
|
||||
"clear_frontend_title": "로그 지우기",
|
||||
"frontend_logs_cleared": "프런트엔드 로그가 삭제되었습니다.",
|
||||
"clear_tauri_confirm": "Tauri 측 로그 파일을 자르시겠습니까? OS는 계속해서 새 항목을 작성합니다.",
|
||||
"clear_tauri_title": "타우리 로그 지우기",
|
||||
"nothing_to_clear": "지울 내용이 없습니다. 아직 디스크에 Tauri 로그 파일이 없습니다.",
|
||||
"cleared_tauri_one": "{{count}} Tauri 로그 파일을 삭제했습니다.",
|
||||
"cleared_tauri_other": "{{count}} Tauri 로그 파일을 삭제했습니다.",
|
||||
"clear_tauri_failed": "Tauri 로그를 지우지 못했습니다: {{message}}",
|
||||
"clear_backend_confirm": "백엔드 런타임 + 충돌 로그를 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.",
|
||||
"clear_backend_title": "로그 지우기",
|
||||
"backend_logs_cleared": "백엔드 로그가 삭제되었습니다.",
|
||||
"clear_backend_failed": "로그를 지우지 못했습니다.",
|
||||
"copy_failed": "복사 실패: {{message}}",
|
||||
"update_check_failed": "업데이트 확인 실패: {{message}}",
|
||||
"save_failed": "저장 실패: {{message}}",
|
||||
"clear_failed": "지우기 실패: {{message}}",
|
||||
"engine_switched": "{{family}} → {{engine}}",
|
||||
"channel_set_failed": "채널 설정 실패: {{message}}",
|
||||
"updater_downloading": "{{version}} 다운로드 중…",
|
||||
"updater_installed": "설치됨 - 다시 시작하는 중입니다.",
|
||||
"updater_available_title": "업데이트 가능",
|
||||
"updater_available_body": "버전 {{version}}을(를) 사용할 수 있습니다.\n\n{{notes}}\n\n지금 다운로드하여 설치하시겠습니까?",
|
||||
"updater_notes_fallback": "GitHub의 릴리스 노트를 참조하세요.",
|
||||
"shortcut_load_failed": "바로가기를 로드할 수 없습니다: {{message}}",
|
||||
"shortcut_set": "받아쓰기 단축키가 {{shortcut}}로 설정되었습니다.",
|
||||
"shortcut_register_failed": "등록할 수 없습니다: {{message}}",
|
||||
"shortcut_reset": "기본값으로 재설정",
|
||||
"shortcut_reset_failed": "재설정 실패: {{message}}"
|
||||
},
|
||||
"bootstrap": {
|
||||
"title": "OmniVoice Studio",
|
||||
@@ -69,7 +97,13 @@
|
||||
"waiting_output": "출력 대기 중…",
|
||||
"auto_detect": "자동 감지",
|
||||
"suggest_lang": "한국어로 변경하시겠습니까?",
|
||||
"select_lang": "언어:"
|
||||
"select_lang": "언어:",
|
||||
"region_global": "글로벌(직접)",
|
||||
"region_china": "중국 (거울)",
|
||||
"region_russia": "러시아 (거울)",
|
||||
"region_restricted": "제한됨(미러)",
|
||||
"unknown_error": "알 수 없는 오류",
|
||||
"retrying": "재시도 중…"
|
||||
},
|
||||
"stories": {
|
||||
"title": "스토리 편집자",
|
||||
@@ -158,7 +192,17 @@
|
||||
"reload": "강제 새로고침 UI",
|
||||
"backend": "백엔드",
|
||||
"frontend": "프런트엔드",
|
||||
"tauri": "타우리"
|
||||
"tauri": "타우리",
|
||||
"cancelOp": "작업 취소",
|
||||
"dismiss": "닫기",
|
||||
"dismissStatus": "상태 닫기",
|
||||
"search": "검색…",
|
||||
"no_matches": "일치하는 항목 없음",
|
||||
"recent_and_popular": "최근 및 인기",
|
||||
"popular_label": "인기",
|
||||
"showing_of": "{{total}} 중 {{shown}} 표시 중 검색하려면 입력하세요…",
|
||||
"yes": "예",
|
||||
"no": "아니요"
|
||||
},
|
||||
"launchpad": {
|
||||
"greeting": "안녕하세요",
|
||||
@@ -180,7 +224,16 @@
|
||||
"locked": "잠김",
|
||||
"open": "열기",
|
||||
"try_it": "사용해 보세요",
|
||||
"audio_only": "오디오만"
|
||||
"audio_only": "오디오만",
|
||||
"stories_title": "스토리",
|
||||
"stories_desc": "다중 음성 오디오북 — 캐릭터 캐스팅, 스크립트 삽입, 장별로 내보내기.",
|
||||
"gallery_title": "보이스 갤러리",
|
||||
"gallery_desc": "악센트, 연령, 스타일별로 미리 디자인된 음성을 찾아보세요. 설정이 필요 없습니다.",
|
||||
"transcripts_title": "성적 증명서",
|
||||
"transcripts_desc": "646개 언어로 오디오나 비디오를 편집 및 검색 가능한 텍스트로 변환하세요.",
|
||||
"recent_files": "최근 파일",
|
||||
"view_all_files": "모든 파일 보기",
|
||||
"file": "파일"
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "프롬프트",
|
||||
@@ -271,11 +324,6 @@
|
||||
"outputs": "출력",
|
||||
"crash_log": "충돌 로그",
|
||||
"update_endpoint": "엔드포인트 업데이트",
|
||||
"update_channel": "업데이트 채널",
|
||||
"channel_stable": "안정 버전",
|
||||
"channel_preview": "미리 보기",
|
||||
"channel_set": "업데이트 채널이 {{channel}}(으)로 설정됨",
|
||||
"channel_preview_hint": "미리 보기는 최신 main 빌드를 따릅니다. 새로운 기능을 빨리 사용할 수 있지만 테스트는 적습니다. 안정 버전이 더 최신이면 안정 버전으로 대체됩니다.",
|
||||
"yes": "응",
|
||||
"no": "아니",
|
||||
"web_preview": "웹 미리보기",
|
||||
@@ -284,7 +332,12 @@
|
||||
"copy_diagnostics": "진단 복사",
|
||||
"github": "GitHub의 OmniVoice",
|
||||
"model_card": "모델 카드",
|
||||
"commercial_license": "상업용 라이센스"
|
||||
"commercial_license": "상업용 라이센스",
|
||||
"update_channel": "업데이트 채널",
|
||||
"channel_stable": "안정 버전",
|
||||
"channel_preview": "미리 보기",
|
||||
"channel_set": "업데이트 채널이 {{channel}}(으)로 설정됨",
|
||||
"channel_preview_hint": "미리 보기는 최신 main 빌드를 따릅니다. 새로운 기능을 빨리 사용할 수 있지만 테스트는 적습니다. 안정 버전이 더 최신이면 안정 버전으로 대체됩니다."
|
||||
},
|
||||
"privacy": {
|
||||
"desc": "모든 것이 <1>이 머신</1>에서 실행됩니다. 온라인 번역기(Google, DeepL 등)를 명시적으로 사용하거나 HuggingFace에 푸시하지 않는 한 귀하의 오디오, 비디오 및 스크립트는 컴퓨터를 떠나지 않습니다.",
|
||||
@@ -340,7 +393,30 @@
|
||||
"unavailable": "이용할 수 없음",
|
||||
"use": "사용",
|
||||
"loading": "엔진 로드 중…",
|
||||
"refresh": "새로고침"
|
||||
"refresh": "새로고침",
|
||||
"matrixTitle": "엔진 호환성 매트릭스",
|
||||
"loadFailed": "엔진을 로드하지 못했습니다: {{message}}",
|
||||
"couldNotLoad": "엔진을 로드할 수 없습니다: {{message}}",
|
||||
"retry": "재시도",
|
||||
"activeEngine": "활성 {{family}}: {{engine}}",
|
||||
"engineCompatLabel": "{{family}} 엔진 호환성",
|
||||
"active": "활성",
|
||||
"whyUnavailable": "왜 사용할 수 없나요?",
|
||||
"lastError": "마지막 오류: {{error}}",
|
||||
"installedAndReady": "설치 및 준비 완료",
|
||||
"notInstalled": "설치되지 않음",
|
||||
"available": "가능",
|
||||
"subprocessTitle": "자체 하위 프로세스 + venv에서 실행됩니다.",
|
||||
"inProcessTitle": "OmniVoice Python 프로세스에서 실행됩니다.",
|
||||
"testEngine": "테스트 엔진",
|
||||
"testing": "테스트 중…",
|
||||
"recheck": "재확인",
|
||||
"rechecking": "재확인 중…",
|
||||
"latencyMs": "{{ms}}ms",
|
||||
"failed": "실패했다",
|
||||
"acceptLicense": "라이선스 수락",
|
||||
"noBackends": "등록된 백엔드가 없습니다.",
|
||||
"switch_failed": "엔진을 전환하지 못했습니다."
|
||||
},
|
||||
"capture": {
|
||||
"desc": "전역 단축키는 데스크톱 앱에서만 작동합니다. 웹 UI는 창에 포커스가 있는 동안 페이지 내 <1>Ctrl+Shift+Space</1> 단축키를 사용합니다.",
|
||||
@@ -352,13 +428,55 @@
|
||||
"record_shortcut": "녹음 바로가기",
|
||||
"recording": "녹음 중…",
|
||||
"save": "저장",
|
||||
"reset_default": "기본값으로 재설정"
|
||||
"reset_default": "기본값으로 재설정",
|
||||
"listening_label": "듣는 중…",
|
||||
"transcribing_label": "스크립트 작성 중…",
|
||||
"pasted": "붙여넣음",
|
||||
"no_speech": "음성이 감지되지 않았습니다.",
|
||||
"mic_denied": "마이크 액세스가 거부되었습니다.",
|
||||
"mic_denied_toast": "마이크 액세스가 거부되었습니다. {{hint}}",
|
||||
"mic_hint_mac": "macOS: 시스템 설정 → 개인 정보 보호 및 보안 → 마이크를 열고 OmniVoice를 활성화합니다.",
|
||||
"mic_hint_windows": "Windows: 설정 → 개인 정보 보호 및 보안 → 마이크를 열고 OmniVoice를 허용합니다.",
|
||||
"mic_hint_linux": "Linux: 사용자가 오디오 그룹에 있고 WebView에 마이크 액세스 권한이 있는지 확인하세요.",
|
||||
"transcription_failed": "스크립트 작성 실패: {{message}}"
|
||||
},
|
||||
"logs": {
|
||||
"no_tauri_log": "아직 디스크에 Tauri 로그가 없습니다. 데스크탑 빌드를 통해 실행하여 생성하세요.",
|
||||
"empty_frontend": "아직 캡처된 프런트엔드 콘솔 항목이 없습니다. 앱과 상호작용하세요. 모든 콘솔*이 여기에 표시됩니다.",
|
||||
"empty_tauri": "Tauri 로그를 사용할 수 없습니다. 데스크톱 셸에서만 실행됩니다.",
|
||||
"empty_backend": "런타임 로그가 비어 있습니다. 백엔드가 기록할 때 활동이 여기에 표시됩니다."
|
||||
"empty_backend": "런타임 로그가 비어 있습니다. 백엔드가 기록할 때 활동이 여기에 표시됩니다.",
|
||||
"title": "로그",
|
||||
"source_backend": "백엔드",
|
||||
"source_frontend": "프런트엔드",
|
||||
"source_tauri": "타우리",
|
||||
"expand": "로그 펼치기",
|
||||
"collapse": "로그 접기",
|
||||
"expand_aria": "로그 패널 확장",
|
||||
"collapse_aria": "로그 패널 접기",
|
||||
"drag_resize": "드래그하여 크기 조정",
|
||||
"refresh": "새로고침",
|
||||
"refresh_aria": "로그 새로 고침",
|
||||
"copy_visible": "보이는 로그 복사",
|
||||
"copy_visible_aria": "보이는 로그 복사",
|
||||
"clear": "지우기",
|
||||
"clear_aria": "로그 지우기",
|
||||
"report_issue": "문제 신고(복사 진단)",
|
||||
"report_issue_aria": "문제 신고",
|
||||
"close": "닫기",
|
||||
"close_aria": "로그 패널 닫기",
|
||||
"join_discord": "우리의 디스코드에 참여하세요",
|
||||
"join_discord_aria": "Discord 커뮤니티에 참여하세요",
|
||||
"support_project": "이 프로젝트를 지원하세요",
|
||||
"support_project_aria": "이 프로젝트를 지원하세요",
|
||||
"empty_frontend_short": "아직 프런트엔드 콘솔 출력이 없습니다.",
|
||||
"empty_lines": "줄이 없습니다.",
|
||||
"all_clear": "✅ 모두 명확함 — 발견된 문제 없음",
|
||||
"log_cleared": "{{source}} 로그가 지워졌습니다.",
|
||||
"clear_failed": "지우기 실패: {{message}}",
|
||||
"log_copied": "{{source}} 로그를 복사했습니다.",
|
||||
"copy_failed": "복사 실패: {{message}}",
|
||||
"report_copied": "진단 보고서가 복사되었습니다. GitHub 문제에 붙여넣으세요.",
|
||||
"report_failed": "보고 실패: {{message}}"
|
||||
},
|
||||
"voice_profile": {
|
||||
"test_text": "안녕하세요. 이 목소리에 대한 테스트입니다.",
|
||||
@@ -526,7 +644,17 @@
|
||||
"install_already": "{{engine}}이(가) 이미 설치되었습니다.",
|
||||
"install_ok": "{{engine}} 설치됨",
|
||||
"install_failed": "설치 실패: {{message}}",
|
||||
"prep_elapsed": "{{time}} 경과"
|
||||
"prep_elapsed": "{{time}} 경과",
|
||||
"add_language": "언어 추가",
|
||||
"search_languages": "언어 검색…",
|
||||
"languages_selected_one": "{{count}} 언어 선택됨",
|
||||
"languages_selected_other": "{{count}} 언어 선택됨",
|
||||
"more_to_narrow": "+{{count}} 더보기 — 범위를 좁히려면 입력하세요.",
|
||||
"no_matches": "일치하는 항목 없음",
|
||||
"diagnostic_copied": "진단이 복사되었습니다.",
|
||||
"copy_failed": "복사 실패",
|
||||
"open_docs": "문서 열기",
|
||||
"copy_diagnostic": "진단 복사"
|
||||
},
|
||||
"glossary": {
|
||||
"title": "용어집",
|
||||
@@ -601,7 +729,17 @@
|
||||
"more_actions_title": "추가 작업",
|
||||
"speaker_pick": "선택…",
|
||||
"speaker_title_detected": "스피커 — 감지된 스피커 중에서 선택하거나 사용자 정의 이름을 입력하세요.",
|
||||
"speaker_title_custom": "발표자 - 이름을 입력하세요(분할 클론이 감지되지 않음)"
|
||||
"speaker_title_custom": "발표자 - 이름을 입력하세요(분할 클론이 감지되지 않음)",
|
||||
"time_edit_title": "시작 시간(m:ss.s)을 편집하려면 클릭하세요. 커밋하려면 Enter를, 취소하려면 Esc를 누르세요.",
|
||||
"fit_fits": "적합",
|
||||
"fit_fits_title": "자연스러운 속도의 오디오가 슬롯에 맞습니다.",
|
||||
"fit_overflows": "오버플로 +{{seconds}}s",
|
||||
"fit_overflows_title": "번역된 텍스트가 원래 슬롯보다 {{seconds}}s 길었습니다. 오디오가 잘 다듬어졌습니다. 텍스트를 줄이거나 타이밍을 \"비디오 늘이기\"로 전환하세요.",
|
||||
"fit_stretched": "비디오 {{ratio}}×",
|
||||
"fit_stretched_title": "비디오 늘이기 모드: 자연스러운 더빙 오디오에 맞추기 위해 이 세그먼트의 비디오가 {{ratio}}× 속도로 느려졌습니다.",
|
||||
"fit_compressed_title": "TTS 오디오는 슬롯의 {{pct}}%입니다. — 심하게 압축되었습니다.",
|
||||
"fit_audio_title": "오디오는 슬롯 안에 맞습니다.",
|
||||
"fit_ratio_title": "TTS 오디오는 슬롯의 {{pct}}%입니다."
|
||||
},
|
||||
"voice": {
|
||||
"personality": "성격",
|
||||
@@ -696,10 +834,24 @@
|
||||
"status_running": "달리기",
|
||||
"status_done": "완료",
|
||||
"status_failed": "실패했다",
|
||||
"status_cancelled": "취소됨"
|
||||
"status_cancelled": "취소됨",
|
||||
"add_to_queue_title": "대기열에 비디오 추가",
|
||||
"drop_hint_text": "여기에 비디오 파일을 놓거나 클릭하여 찾아보세요.",
|
||||
"drop_formats": "MP4 · MOV · MKV · WEBM",
|
||||
"files_kicker": "파일({{count}})",
|
||||
"file_size_mb": "{{size}}MB",
|
||||
"target_languages": "대상 언어",
|
||||
"voice_kicker": "음성",
|
||||
"default_option": "기본값",
|
||||
"clone_profiles": "프로필 복제",
|
||||
"presets": "사전 설정",
|
||||
"preserve_bg": "배경 오디오 보존(음악/FX)",
|
||||
"estimate": "{{videos}} 동영상 × {{langs}} 언어 = {{jobs}} 작업",
|
||||
"select_files_langs": "파일 및 언어 선택",
|
||||
"add_to_queue": "대기열에 추가"
|
||||
},
|
||||
"gallery": {
|
||||
"title": "갤러리",
|
||||
"title": "OmniVoice 갤러리",
|
||||
"search_placeholder": "유튜브 검색…",
|
||||
"all_voices": "모든 음색({{count}})",
|
||||
"no_voices": "아직 음성이 없습니다.",
|
||||
@@ -714,6 +866,14 @@
|
||||
"youtube_results": "YouTube 결과({{count}})",
|
||||
"clone_profile": "프로필 복제",
|
||||
"crop_audio": "오디오 자르기",
|
||||
"cat_disney": "디즈니",
|
||||
"cat_anime": "애니메이션",
|
||||
"cat_marvel": "마블/DC",
|
||||
"cat_celebs": "연예인",
|
||||
"cat_politicians": "정치인",
|
||||
"cat_news": "뉴스 앵커",
|
||||
"cat_gaming": "게임",
|
||||
"cat_books": "책/영화",
|
||||
"subtitle": "수백 가지의 미리 만들어진 디자인 음성 중 하나를 선택하여 사용하세요.",
|
||||
"zone_archetypes": "원형",
|
||||
"zone_imports": "내 수입품",
|
||||
@@ -818,7 +978,6 @@
|
||||
"badge": "상업용 라이센스",
|
||||
"hero_title": "프로덕션에 AI 음성 전달",
|
||||
"hero_desc": "OmniVoice Studio는 FSL(Functional Source License)에 따라 소스를 사용할 수 있습니다. 대부분의 사용자는 상업적 계약 없이 내부적으로 평가, 프로토타입 작성 및 배포까지 할 수 있습니다. 경쟁 제품이나 서비스를 구축하는 경우 또는 사용 사례가 FSL의 경계를 벗어나는 경우에만 상업용 라이선스가 필요합니다.",
|
||||
"hero_note": "경쟁 제품이나 서비스를 구축하거나 대규모 배포(예: 종량제 API 제공)에는 상용 라이선스가 필요합니다. 가격 책정 단계가 곧 제공될 예정입니다. 그동안 연락해 주세요.",
|
||||
"why_title": "기업이 OmniVoice를 선택하는 이유",
|
||||
"pricing_title": "가격",
|
||||
"faq_title": "일반적인 질문",
|
||||
@@ -838,7 +997,8 @@
|
||||
"benefit_source": "소스 사용 가능한 코어",
|
||||
"benefit_source_desc": "스택에 대한 완전한 가시성. 라이선스 조건 내에서 감사, 분기 및 조정합니다.",
|
||||
"benefit_lang": "646개 언어",
|
||||
"benefit_lang_desc": "인간 수준의 품질로 646개 언어로 전사, 번역, 더빙하세요."
|
||||
"benefit_lang_desc": "인간 수준의 품질로 646개 언어로 전사, 번역, 더빙하세요.",
|
||||
"hero_note": "내부 사용은 규모에 상관없이 FSL에 따라 무료입니다. 상업용 라이선스는 OmniVoice를 경쟁 제품이나 서비스(호스팅 또는 종량제 API, 재판매 또는 화이트 라벨 앱)로 다른 사람에게 제공하는 데만 필요합니다. 가격 등급이 곧 제공될 예정입니다. 그동안 연락해 주세요."
|
||||
},
|
||||
"exportModal": {
|
||||
"export": "수출",
|
||||
@@ -927,7 +1087,479 @@
|
||||
"dubbing_title": "실제 더빙 보기",
|
||||
"dubbing_sync": "동기화된 재생",
|
||||
"dubbing_picker": "다른 언어를 사용해 보세요:",
|
||||
"dubbing_cta": "자신의 비디오에서 이것을 실행 →"
|
||||
"dubbing_cta": "자신의 비디오에서 이것을 실행 →",
|
||||
"dubbing_loading": "더빙 데모 로드 중…",
|
||||
"dubbing_dismiss": "더빙 데모 닫기",
|
||||
"original_tag": "원본",
|
||||
"dubbed_tag": "더빙된",
|
||||
"script_conversational": "대화",
|
||||
"script_technical": "기술 어휘",
|
||||
"script_french": "비영어권(프랑스어)",
|
||||
"aria_pause": "일시중지 {{label}}",
|
||||
"aria_hear": "{{label}} 듣기",
|
||||
"aria_replay": "전사기를 통해 {{label}} 재생"
|
||||
},
|
||||
"direction": {
|
||||
"title": "세그먼트 #{{id}} 방향",
|
||||
"desc": "이 라인이 어떻게 느껴져야 하는지 파이프라인에 알려주세요. 일반 영어가 작동합니다. 시스템은 단어를 안정적인 분류(에너지/감정/속도/친밀감/형식)에 매핑한 다음 영화 번역, TTS 및 슬롯 맞춤을 통해 분류를 연결합니다.",
|
||||
"label": "방향",
|
||||
"lineHint": "줄: \"{{text}}\"",
|
||||
"placeholder": "예를 들어 다급하고 놀라는 / 따뜻하고 희망적인 / 속삭이는, 친밀한",
|
||||
"previewParse": "미리보기 구문 분석",
|
||||
"previewFailed": "미리보기 실패: {{message}}",
|
||||
"clear": "지우기",
|
||||
"cancel": "취소",
|
||||
"saveDirection": "방향 저장",
|
||||
"ttsInstruct": "TTS 지시:",
|
||||
"nothingParsed": "— (파싱된 항목 없음)",
|
||||
"translateHint": "힌트 번역:",
|
||||
"rateBias": "비율 편향:",
|
||||
"speedsUp": "속도를 높이다",
|
||||
"slowsDown": "속도가 느려지다",
|
||||
"taxonomyTokens": "분류 토큰"
|
||||
},
|
||||
"errors": {
|
||||
"title": "이 탭에 문제가 발생했습니다.",
|
||||
"desc": "걱정하지 마세요. 앱의 나머지 부분은 여전히 작동합니다. 탭을 전환하거나 아래에서 다시 시도해 보세요.",
|
||||
"tryAgain": "다시 시도하세요",
|
||||
"openDocs": "이 오류에 대한 문서 열기"
|
||||
},
|
||||
"keyboard": {
|
||||
"title": "키보드 단축키",
|
||||
"footer": "이 항목을 열려면 언제든지 <1>?</1>을 누르세요.",
|
||||
"or": "또는",
|
||||
"nav": "네비게이션",
|
||||
"nav_cheatsheet": "이 치트시트를 보여주세요",
|
||||
"nav_closeModal": "모달 닫기/취소",
|
||||
"nav_save": "프로젝트 저장/트림 커밋",
|
||||
"segmentEditor": "세그먼트 편집기",
|
||||
"seg_split": "커서에서 세그먼트 분할",
|
||||
"seg_merge": "다음 세그먼트와 병합",
|
||||
"seg_undo": "실행 취소",
|
||||
"seg_redo": "다시 실행",
|
||||
"seg_click": "기본 작업",
|
||||
"seg_shiftClick": "범위 선택",
|
||||
"trimmer": "오디오 트리머",
|
||||
"trim_playPause": "미리보기 재생/일시중지",
|
||||
"trim_nudgeStart": "너지 시작 핸들",
|
||||
"trim_nudgeEnd": "너지 엔드 핸들",
|
||||
"trim_fineNudge": "미세한 넛지",
|
||||
"trim_coarseNudge": "거친 넛지",
|
||||
"trim_zoomIn": "확대/축소",
|
||||
"trim_fitAll": "모두 맞춤 / 맞춤 선택",
|
||||
"trim_confirm": "트림 확인",
|
||||
"dub": "더빙",
|
||||
"dub_generate": "더빙 생성",
|
||||
"dub_sidebar": "사이드바 전환"
|
||||
},
|
||||
"network": {
|
||||
"sharing_on_title": "공유 대상 - 자세한 내용을 보려면 클릭하세요",
|
||||
"share_on_network": "네트워크에서 공유",
|
||||
"switching": "전환 중…",
|
||||
"network": "네트워크",
|
||||
"local": "지역",
|
||||
"share_confirm_title": "네트워크에서 공유하시겠습니까?",
|
||||
"share_confirm_hint": "Wi-Fi/이더넷의 다른 장치는 켜져 있으면 표시된 액세스 PIN을 사용하여 OmniVoice에 연결할 수 있습니다.",
|
||||
"enabling": "활성화 중…",
|
||||
"enable": "활성화",
|
||||
"shared_title": "네트워크에서 공유됨",
|
||||
"no_interface": "연결할 수 있는 네트워크 인터페이스가 없습니다. Wi-Fi/이더넷에 연결하세요.",
|
||||
"copy_link": "링크 복사",
|
||||
"open_in_browser": "브라우저에서 열기",
|
||||
"qr_alt": "{{ip}}에 대한 QR",
|
||||
"pin": "핀:",
|
||||
"stop_sharing": "공유 중지",
|
||||
"copied": "복사됨",
|
||||
"enable_error": "공유를 활성화할 수 없습니다: {{message}}",
|
||||
"disable_error": "비활성화할 수 없습니다: {{message}}"
|
||||
},
|
||||
"readiness": {
|
||||
"checking_system": "시스템 확인 중…",
|
||||
"all_ready": "모든 시스템 준비",
|
||||
"system_readiness": "시스템 준비",
|
||||
"asr_model": "ASR 모델",
|
||||
"loaded_ready": "로드 및 준비됨",
|
||||
"loading_first_run": "로드 중… (처음 실행 시 1~2분 정도 소요될 수 있음)",
|
||||
"failed_to_load": "로드하지 못했습니다.",
|
||||
"not_loaded_yet": "아직 로드되지 않음 - 첫 번째 기록 시 로드됩니다.",
|
||||
"error_check_logs": "오류: {{error}}. 로그를 확인하고 다시 시작해 보세요.",
|
||||
"check_logs_restart": "모델 로드 오류에 대한 로그를 확인하세요. 다시 시작해 보세요.",
|
||||
"llm_cinematic": "LLM (영화)",
|
||||
"llm_configure": "영화 번역 품질을 위해 TRANSLATE_BASE_URL 구성",
|
||||
"llm_set_env": "TRANSLATE_BASE_URL 및 TRANSLATE_API_KEY 환경 변수를 설정합니다. Ollama, OpenAI, LM Studio 등과 함께 작동합니다.",
|
||||
"llm_optional": "선택 사항 - 영화 품질을 위해 TRANSLATE_BASE_URL을 설정합니다."
|
||||
},
|
||||
"license": {
|
||||
"title": "Supertonic-3 — 라이센스 승인",
|
||||
"intro": "Supertonic-3은 두 가지 별도의 라이센스에 따라 배송됩니다. 엔진을 활성화하기 전에 두 가지를 모두 검토하십시오.",
|
||||
"sdk_heading": "SDK 코드 · MIT",
|
||||
"sdk_desc": "Python 추론 SDK(supertonic)는 MIT 라이선스를 받았습니다. 상업용을 포함한 허용적인 사용.",
|
||||
"read_mit": "MIT 라이센스 읽기 →",
|
||||
"model_heading": "모델 중량 · OpenRAIL-M",
|
||||
"model_desc": "Supertonic-3 모델 가중치는 OpenRAIL-M 라이센스에 따라 출시됩니다. 이 라이선스는 악의적이지 않은 목적으로 사용을 제한합니다. 전체 사용 기반 제한 사항은 링크된 라이선스를 참조하세요.",
|
||||
"read_openrail": "OpenRAIL-M 라이센스 읽기 →",
|
||||
"footer": "수락을 클릭하면 OmniVoice의 로컬 설정에 수락이 기록되고 엔진이 활성화됩니다. 귀하의 수락은 이 컴퓨터에만 저장되며 Supertone Inc.나 제3자에게는 아무 것도 보고되지 않습니다.",
|
||||
"saving": "저장 중…",
|
||||
"accept": "수락",
|
||||
"accepted_toast": "Supertonic-3 라이센스가 허용됩니다.",
|
||||
"accept_error": "라이센스 승인을 기록하지 못했습니다: {{message}}"
|
||||
},
|
||||
"voicePreview": {
|
||||
"title": "음성 미리보기",
|
||||
"close": "미리보기 닫기",
|
||||
"default_text": "안녕하세요! 이것은 이 목소리에서 내가 어떻게 들리는지에 대한 미리보기입니다.",
|
||||
"default_voice": "기본 음성",
|
||||
"clone_profiles": "프로필 복제",
|
||||
"designed_voices": "디자인된 보이스",
|
||||
"presets": "사전 설정",
|
||||
"placeholder": "듣고 싶은 내용을 입력하세요…",
|
||||
"stop": "중지",
|
||||
"regenerate": "재생성",
|
||||
"preview": "미리보기",
|
||||
"hint": "8단계 · 빠른 미리보기"
|
||||
},
|
||||
"header": {
|
||||
"kicker_studio": "스튜디오",
|
||||
"kicker_library": "도서관",
|
||||
"kicker_preferences": "환경설정",
|
||||
"kicker_licensing": "라이선스",
|
||||
"label_launchpad": "런치패드",
|
||||
"label_clone": "음성 복제",
|
||||
"label_design": "음성 디자인",
|
||||
"label_dub": "더빙",
|
||||
"label_projects": "옴니드라이브",
|
||||
"label_gallery": "갤러리",
|
||||
"label_transcriptions": "전사",
|
||||
"label_settings": "설정",
|
||||
"label_enterprise": "상업용 라이센스",
|
||||
"status_ready": "준비",
|
||||
"status_loading": "로드 중…",
|
||||
"status_idle": "유휴",
|
||||
"memory_management": "메모리 관리",
|
||||
"flush": "플러시",
|
||||
"loaded_models": "로드된 모델",
|
||||
"no_models": "로드된 모델이 없습니다.",
|
||||
"unload": "언로드",
|
||||
"flush_caches": "캐시 플러시",
|
||||
"unload_all_flush": "모두 언로드 + 플러시"
|
||||
},
|
||||
"sidebar": {
|
||||
"tab_drive": "드라이브",
|
||||
"tab_history": "역사",
|
||||
"tab_exports": "수출",
|
||||
"save_project": "더빙 프로젝트 저장",
|
||||
"save_new_project": "새 더빙 프로젝트로 저장",
|
||||
"dub_projects": "더빙 프로젝트",
|
||||
"voice_clones": "음성 클론",
|
||||
"designed_voices": "디자인된 보이스",
|
||||
"no_dub_projects": "저장된 더빙 프로젝트가 없습니다.",
|
||||
"no_dub_hint": "비디오를 업로드하고 저장을 클릭하여 작업 내용을 유지하세요.",
|
||||
"no_clones": "아직 음성 클론이 없습니다.",
|
||||
"no_clones_hint": "오디오를 녹음하거나 업로드한 다음 음성 프로필로 저장을 클릭하세요.",
|
||||
"no_designs": "아직 디자인된 음성이 없습니다.",
|
||||
"no_designs_hint": "음성을 생성하고 기록에 저장하세요.",
|
||||
"history_subtitle": "생성 내역 · SQLite에 저장됨",
|
||||
"no_history": "세대 이력 없음",
|
||||
"no_history_hint": "오디오를 합성하거나 비디오를 더빙하세요. 결과가 여기에 표시됩니다.",
|
||||
"clear_history": "기록 지우기",
|
||||
"clear_confirm": "{{count}} 기록 항목을 모두 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.",
|
||||
"history_cleared": "기록이 삭제되었습니다.",
|
||||
"recent_exports": "최근 수출",
|
||||
"no_exports": "다운로드한 출력 없음",
|
||||
"no_exports_hint": "여기에서 추적된 내용을 확인하려면 Tauri를 통해 파일을 내보내세요.",
|
||||
"show_in_folder": "폴더에 표시",
|
||||
"open": "열기",
|
||||
"select": "선택",
|
||||
"try_voice": "시도해 보세요",
|
||||
"consistent": "일관된",
|
||||
"locked": "잠김",
|
||||
"clone_label": "클론",
|
||||
"design_label": "디자인",
|
||||
"dub_label": "더빙",
|
||||
"save_label": "저장",
|
||||
"lock_identity": "음성 신원 잠금",
|
||||
"in_folder": "{{folder}}에서"
|
||||
},
|
||||
"trimmer": {
|
||||
"title": "참조 오디오 다듬기",
|
||||
"decoding": "오디오 디코딩 중…",
|
||||
"meta_length": "길이 {{duration}} · {{sampleRate}}Hz",
|
||||
"meta_rendering": "렌더링 파형 {{percent}}%",
|
||||
"keyboard_hint": "스크롤 = 확대/축소 · Shift+스크롤 = 이동 · Alt+드래그 = 이동 · ⏐ ⟵ ⟶ ⏐ 키 핸들 조정",
|
||||
"zoom_in": "확대(+)",
|
||||
"zoom_out": "축소(-)",
|
||||
"fit_all": "모두 적합(홈)",
|
||||
"fit_selection": "맞춤 선택(종료)",
|
||||
"fit_sel_btn": "맞춤 선택",
|
||||
"view_range": "{{start}} → {{end}}({{duration}}) 보기",
|
||||
"start_label": "시작",
|
||||
"end_label": "끝",
|
||||
"length_label": "길이",
|
||||
"too_long": ">{{max}}s",
|
||||
"too_short": "너무 짧다",
|
||||
"length_ok": "알았어",
|
||||
"loop_preview": "루프 미리보기",
|
||||
"pause": "일시중지",
|
||||
"preview_selection": "미리보기 선택",
|
||||
"play_hint": "플레이할 공간 · 확인하려면 Enter · 취소하려면 Esc",
|
||||
"cancel": "취소",
|
||||
"use_trimmed": "잘라서 사용",
|
||||
"decode_failed": "디코딩 실패: {{message}}",
|
||||
"playback_failed": "재생 실패: {{message}}",
|
||||
"audio_load_failed": "오디오 로드 실패",
|
||||
"unit_seconds": "초"
|
||||
},
|
||||
"casting": {
|
||||
"title": "스피커 캐스팅",
|
||||
"auto_assign_title": "추출된 스피커 클론에서 음성 자동 할당",
|
||||
"auto_cast": "자동시전",
|
||||
"all_cast": "올캐스트",
|
||||
"segments_count": "{{count}} 세그먼트",
|
||||
"assign_voice": "음성 할당…",
|
||||
"from_video": "🎤 영상에서 ({{name}})",
|
||||
"no_profiles": "아직 저장된 음성 프로필이 없습니다.",
|
||||
"preview_voice": "음성 미리보기"
|
||||
},
|
||||
"checkpoint": {
|
||||
"asr_title": "성적 증명서 준비됨",
|
||||
"asr_cta": "번역하다",
|
||||
"asr_hint": "지금 ASR 오류를 수정하세요. 엄격한 사전을 사용하면 나중에 TTS 시도를 저장할 수 있습니다.",
|
||||
"translate_title": "번역 준비 완료",
|
||||
"translate_cta": "더빙 생성",
|
||||
"translate_hint": "대상 텍스트를 훑어보세요. 길이가 긴 라인은 속도가 향상됩니다. 직접 편집할 수도 있습니다.",
|
||||
"done_title": "더빙 완료",
|
||||
"done_hint": "타이밍과 동기화 비율을 검토하세요. 빠른 부분 다시 실행을 위해 라인을 조정하고 \"변경됨 재생성\"을 누르십시오.",
|
||||
"segment_one": "{{count}} 세그먼트",
|
||||
"segment_other": "{{count}} 세그먼트",
|
||||
"dismiss_title": "닫기 — 새로고침할 때까지 이 단계에서는 다시 나타나지 않습니다."
|
||||
},
|
||||
"compare": {
|
||||
"title": "A/B 음성 비교",
|
||||
"close": "가까운 비교",
|
||||
"desc": "캐스팅 결정을 내리려면 두 목소리를 나란히 비교하세요. 앱은 뒤에 대화형으로 유지됩니다.",
|
||||
"test_phrase": "테스트 문구",
|
||||
"voice_a": "음색 A",
|
||||
"voice_b": "보이스 B",
|
||||
"select_voice": "— 음성 선택 —",
|
||||
"preset_suffix": "(프리셋)",
|
||||
"no_audio": "아직 오디오가 없습니다.",
|
||||
"close_btn": "닫기",
|
||||
"comparing": "비교 중…",
|
||||
"compare_btn": "비교",
|
||||
"preparing_voice": "음성 준비 중...",
|
||||
"generating_voice_a": "음성 생성 중...",
|
||||
"generating_voice_b": "음성 B 생성 중...",
|
||||
"comparison_complete": "비교 완료!",
|
||||
"play_failed": "플레이 실패: {{message}}"
|
||||
},
|
||||
"models": {
|
||||
"hf_token_set_toast": "HuggingFace 토큰 세트 - 더 빠른 다운로드 가능",
|
||||
"hf_token_save_failed": "토큰을 저장하지 못했습니다.",
|
||||
"install_started": "설치 시작됨 - 행의 진행률",
|
||||
"delete_confirm": "{{repoId}}을 삭제하시겠습니까? 나중에 다시 설치할 수 있습니다.",
|
||||
"delete_confirm_title": "모델 삭제",
|
||||
"deleted": "{{repoId}} 삭제됨",
|
||||
"reinstall_confirm": "{{repoId}}을(를) 다시 설치하시겠습니까? 현재 사본이 삭제되고 다시 다운로드됩니다.",
|
||||
"reinstall_confirm_title": "모델 재설치",
|
||||
"reinstalling": "재설치 중",
|
||||
"recommended_installed": "권장 모델이 이미 설치되어 있습니다.",
|
||||
"started_downloading_one": "{{count}} 모델 다운로드 시작됨",
|
||||
"started_downloading_other": "{{count}} 모델 다운로드 시작됨",
|
||||
"install_failed": "설치 실패: {{message}}",
|
||||
"removing_cached": "캐시된 버전 삭제 중…",
|
||||
"resolving_metadata": "저장소 메타데이터 해결",
|
||||
"retry_attempt": "재시도 {{attempt}} — {{error}}",
|
||||
"connecting_hf": "HuggingFace에 연결하는 중…",
|
||||
"resolving_files_one": "{{count}} 파일 해결 중…",
|
||||
"resolving_files_other": "{{count}} 파일 해결 중…",
|
||||
"resolving_files_active": "{{count}} 파일 해결 중… · {{file}}",
|
||||
"measuring": "측정…",
|
||||
"files_progress": "{{done}}/{{total}} 파일",
|
||||
"install_error": "설치 실패: {{error}}",
|
||||
"view_on_hf": "HuggingFace에서 보기",
|
||||
"install_btn": "설치",
|
||||
"reinstall_btn": "재설치",
|
||||
"downloading": "다운로드 중",
|
||||
"deleting": "삭제 중",
|
||||
"working": "일하는",
|
||||
"installed": "설치됨",
|
||||
"not_installed": "설치되지 않음",
|
||||
"required_tag": "필수",
|
||||
"delete_btn": "삭제",
|
||||
"hf_token_btn": "HF 토큰",
|
||||
"hf_set_title": "더 빠른 다운로드를 위해 HuggingFace 토큰 설정",
|
||||
"get_token": "토큰 받기 →",
|
||||
"reco_installed_for": "{{device}}에 권장 번들 설치",
|
||||
"reco_for": "{{device}}에 권장됨",
|
||||
"starting": "시작 중…",
|
||||
"required_size": "필수 ~{{size}}GB",
|
||||
"all_size": "전체 ~{{size}}GB",
|
||||
"req_tag": "요구",
|
||||
"search_placeholder": "모델 검색…",
|
||||
"search_label": "모델 검색",
|
||||
"no_matches": "필터와 일치하는 모델이 없습니다.",
|
||||
"sort_by": "{{column}} 기준으로 정렬",
|
||||
"column_model": "모델",
|
||||
"column_role": "역할",
|
||||
"column_size": "크기",
|
||||
"column_status": "상태",
|
||||
"ready_badge": "준비",
|
||||
"loading_badge": "로드 중…",
|
||||
"idle_badge": "유휴",
|
||||
"started_downloading_required_one": "{{count}} 필수 모델 다운로드 시작",
|
||||
"started_downloading_required_other": "{{count}} 필수 모델 다운로드 시작"
|
||||
},
|
||||
"enterprise_faq": {
|
||||
"q_internal_tools": "내부 도구에 대한 라이선스가 필요합니까?",
|
||||
"a_internal_tools": "직원 및 계약자의 내부 사용은 FSL에 따라 허용된 목적이며 라이선스가 필요하지 않습니다. OmniVoice를 경쟁 제품 또는 서비스(재판매, 호스팅 SaaS, 화이트 라벨)의 일부로 다른 사람에게 제공하려면 상용 라이선스가 필요합니다.",
|
||||
"q_try_before": "커밋하기 전에 시도해 볼 수 있나요?",
|
||||
"a_try_before": "그렇습니다. FSL에 따른 평가를 위해 전체 앱을 무료로 다운로드하고 로컬에서 실행할 수 있습니다. 상용 배포에 대해 논의할 준비가 되면 이메일을 보내주시면 세부 사항을 함께 논의해 보겠습니다.",
|
||||
"q_watermark": "워터마크는 어떻습니까?",
|
||||
"a_watermark": "보이지 않는 AudioSeal 워터마크가 기본적으로 내장되어 있습니다. 상업용 라이선스 사용자는 설정 → 개인정보 보호에서 이를 비활성화할 수 있습니다. 무료/개인 사용에는 항상 워터마크가 포함됩니다.",
|
||||
"q_apache": "소스가 Apache 2.0이 됩니까?",
|
||||
"a_apache": "그렇습니다. 각 릴리스는 출판 2주년이 되는 날 자동으로 Apache 라이센스 버전 2.0으로 변환됩니다. 즉, 오늘의 릴리스는 2년 후 Apache 2.0이며 우리가 취할 조치는 없습니다. FSL은 이를 취소할 수 없게 보장합니다."
|
||||
},
|
||||
"gallery_extra": {
|
||||
"save_prompt": "이 음성 프로필의 이름을 입력하세요.",
|
||||
"saved_as_profile": "음성이 프로필로 저장되었습니다!",
|
||||
"save_profile_failed": "프로필을 저장하지 못했습니다.",
|
||||
"download_failed": "다운로드 실패: {{message}}",
|
||||
"trim_load_failed": "자르기를 위해 오디오를 로드하지 못했습니다: {{message}}",
|
||||
"upload_crop_failed": "잘린 음성을 업로드하지 못했습니다: {{message}}"
|
||||
},
|
||||
"dub_workflow": {
|
||||
"preparing_audio": "오디오 준비 중…",
|
||||
"preparing_video": "영상 준비 중…",
|
||||
"extracting_audio_scenes": "오디오 및 장면 추출 중…",
|
||||
"transcribing_audio": "오디오 스크립트 작성 중…",
|
||||
"transcription_complete": "전사 완료",
|
||||
"upload_cancelled": "업로드가 취소되었습니다.",
|
||||
"upload_failed": "업로드 실패: {{message}}",
|
||||
"downloading_video": "동영상 다운로드 중…",
|
||||
"ingested": "{{url}} 수집됨",
|
||||
"ingest_cancelled": "수집이 취소되었습니다.",
|
||||
"ingest_failed": "URL 수집 실패: {{message}}",
|
||||
"retry_cancelled": "재시도가 취소되었습니다.",
|
||||
"transcription_failed": "스크립트 작성 실패: {{message}}",
|
||||
"import_srt_no_job": "먼저 비디오를 업로드하거나 처리하십시오. 자막을 첨부할 작업이 없습니다.",
|
||||
"imported_cues": "{{file}}에서 {{count}} 큐를 가져왔습니다.",
|
||||
"skipped_malformed": "{{count}} 건너뛰기(잘못된 형식)",
|
||||
"dropped_overlap": "{{count}} 삭제됨(겹침)",
|
||||
"clamped_to_duration": "{{count}} 미디어 길이에 고정됨",
|
||||
"srt_import_failed": "SRT 가져오기 실패",
|
||||
"cleaned_one": "{{count}} 조각을 청소했습니다.",
|
||||
"cleaned_other": "{{count}} 조각을 청소했습니다.",
|
||||
"segments_clean": "세그먼트가 이미 깨끗함",
|
||||
"cleanup_failed": "정리 실패: {{message}}",
|
||||
"cinematic_no_llm": "영화 품질에는 LLM이 필요합니다. TRANSLATE_BASE_URL + TRANSLATE_API_KEY를 설정하세요(Ollama는 로컬에서 작동함). Fast로 다시 돌아갑니다.",
|
||||
"translate_errors": "{{errorCount}}/{{totalCount}} 세그먼트 실패: {{firstError}}",
|
||||
"translated_segments": "번역된 {{count}} 세그먼트 → {{lang}}",
|
||||
"translated_cinematic_suffix": "(영화)",
|
||||
"translation_failed": "번역 실패: {{message}}",
|
||||
"regenerating": "{{count}} 세그먼트를 재생성하는 중…",
|
||||
"generating_dub": "더빙 생성 중…",
|
||||
"generating_progress": "더빙 생성 중… {{current}}/{{total}}",
|
||||
"generation_aborted": "생성이 중단되었습니다.",
|
||||
"dubbing_aborted": "더빙이 중단되었습니다.",
|
||||
"generation_stream_ended": "생성 스트림이 완료되기 전에 종료되었습니다.",
|
||||
"dub_complete": "더빙 완료",
|
||||
"stop_failed": "중지하지 못했습니다.",
|
||||
"save_first": "저장하기 전에 동영상이 서버에서 처리되도록 먼저 '업로드 및 스크립트 작성'을 클릭하세요.",
|
||||
"project_saved": "프로젝트가 저장되었습니다",
|
||||
"project_created": "프로젝트가 생성되었습니다.",
|
||||
"save_failed": "저장 실패: {{message}}",
|
||||
"opened_project": "열림: {{name}}",
|
||||
"delete_project_confirm": "이 프로젝트를 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.",
|
||||
"project_deleted": "프로젝트가 삭제되었습니다.",
|
||||
"delete_history_confirm": "이 기록 항목을 삭제하시겠습니까?",
|
||||
"history_deleted": "기록 항목이 삭제되었습니다.",
|
||||
"restored_state": "이전 세대 상태를 복원했습니다.",
|
||||
"upgrading_preview": "{{count}} 미리보기 품질 세그먼트를 전체 품질로 업그레이드하는 중…"
|
||||
},
|
||||
"tts_errors": {
|
||||
"enter_text": "텍스트를 입력해주세요",
|
||||
"upload_or_select": "오디오 업로드 또는 음성 프로필 선택",
|
||||
"trim_hint": "오디오는 {{duration}}s입니다. 최상의 복제를 위해 ≤{{max}}s로 조정하세요.",
|
||||
"timeout": "생성 시간이 초과되었습니다. 모델이 아직 다운로드 중일 수 있습니다. 설정 → 로그를 확인한 후 다시 시도하세요.",
|
||||
"error_prefix": "오류: {{message}}",
|
||||
"ignored_unsupported": "지원되지 않는 지침을 무시했습니다: {{items}}",
|
||||
"ignored_duplicate": "무시됨(카테고리가 이미 설정됨): {{items}}"
|
||||
},
|
||||
"sharing": {
|
||||
"title": "공유 및 원격 액세스",
|
||||
"help": "실행 중인 OmniVoice 인스턴스를 다시 시작하지 않고 다른 시스템에 노출하세요. 루프백 전용이 기본값입니다. 여기에서 활성화할 때까지 아무 것도 공유되지 않습니다.",
|
||||
"local_network": "로컬 네트워크",
|
||||
"local_help": "일회성 액세스 PIN을 사용하여 Wi-Fi/이더넷에서 공유하세요. 다른 장치에서는 QR 코드를 스캔하거나 링크를 엽니다.",
|
||||
"ports_title": "포트",
|
||||
"ports_help": "이는 시작 시 읽은 환경 변수를 통해 설정됩니다. 변수를 설정하고 OmniVoice를 다시 시작하여 백엔드 또는 UI 포트를 변경하세요.",
|
||||
"backend_port": "백엔드 포트",
|
||||
"ui_port": "UI 포트",
|
||||
"lan_share_port": "LAN 공유 포트",
|
||||
"port_error": "1024에서 65535 사이의 포트를 입력하세요.",
|
||||
"port_saved": "LAN 공유 포트가 저장됨 - 다음에 공유를 활성화할 때 적용됩니다.",
|
||||
"port_save_failed": "포트를 저장할 수 없습니다: {{message}}",
|
||||
"saving": "저장 중…",
|
||||
"ports_note": "백엔드 및 UI 포트는 다시 시작 시 적용됩니다. LAN 공유 포트는 다음에 공유를 활성화할 때 적용됩니다.",
|
||||
"tailscale_title": "Tailscale(개인 원격 액세스)",
|
||||
"tailscale_checking": "Tailscale 확인 중…",
|
||||
"tailscale_absent": "테일 스케일이 감지되지 않았습니다. 개인 테일넷의 어느 곳에서나 안전하게 OmniVoice에 연결하려면 이를 설치하세요.",
|
||||
"tailscale_install": "테일스케일 설치",
|
||||
"tailscale_running": "테일스케일이 실행 중입니다. 개인 tailnet을 통해 OmniVoice를 제공하십시오.",
|
||||
"tailscale_not_logged_in": "Tailscale이 설치되었지만 로그인되지 않았습니다. 먼저 Tailscale을 시작하고 로그인하세요.",
|
||||
"tailscale_enabled": "테일스케일 서브 활성화됨",
|
||||
"tailscale_enable_failed": "Tailscale을 활성화할 수 없습니다.",
|
||||
"tailscale_enable_error": "Tailscale을 활성화할 수 없습니다: {{message}}",
|
||||
"tailscale_disabled": "테일스케일 서브 비활성화됨",
|
||||
"tailscale_disable_failed": "Tailscale을 비활성화할 수 없습니다.",
|
||||
"tailscale_disable_error": "Tailscale을 비활성화할 수 없습니다: {{message}}",
|
||||
"tailscale_enabling": "활성화 중…",
|
||||
"tailscale_enable_btn": "Tailscale 서브 활성화",
|
||||
"tailscale_disabling": "비활성화 중…",
|
||||
"tailscale_disable_btn": "Tailscale 서브 중지",
|
||||
"tailscale_copy": "링크 복사",
|
||||
"tailscale_open": "브라우저에서 열기",
|
||||
"copied": "복사됨",
|
||||
"tailscale_qr_alt": "Tailscale URL의 QR 코드"
|
||||
},
|
||||
"reportBug": {
|
||||
"label": "버그 신고",
|
||||
"title": "브라우저에서 미리 채워진 GitHub 문제 페이지를 엽니다. 제출을 클릭할 때까지 아무 것도 전송되지 않습니다."
|
||||
},
|
||||
"app": {
|
||||
"loading": "로드 중…",
|
||||
"trimmed_loaded": "잘린 오디오 로드됨",
|
||||
"toast_exported": "내보낸 날짜: {{name}}",
|
||||
"toast_export_failed": "내보내기 실패: {{message}}",
|
||||
"toast_open_folder_failed": "폴더를 열 수 없습니다: {{message}}",
|
||||
"toast_saving": "{{name}} 저장 중...",
|
||||
"toast_saved": "저장됨: {{path}}",
|
||||
"toast_save_error": "저장 오류: {{message}}",
|
||||
"toast_processing": "{{name}} 처리 중...",
|
||||
"toast_downloaded": "{{name}} 다운로드됨",
|
||||
"toast_download_error": "다운로드 오류: {{message}}",
|
||||
"toast_upload_first": "저장하기 전에 동영상이 서버에서 처리되도록 먼저 '업로드 및 스크립트 작성'을 클릭하세요.",
|
||||
"toast_save_failed": "저장 실패: {{message}}",
|
||||
"toast_opened": "열림: {{name}}",
|
||||
"toast_project_deleted": "프로젝트가 삭제되었습니다.",
|
||||
"toast_restored_state": "이전 세대 상태를 복원했습니다.",
|
||||
"toast_history_deleted": "기록 항목이 삭제되었습니다.",
|
||||
"toast_flushed": "플러시 — RAM {{ram}}G · VRAM {{vram}}G{{unloaded}}",
|
||||
"toast_model_unloaded": "· 모델 언로드됨",
|
||||
"toast_flush_failed": "플러시 실패: {{message}}",
|
||||
"toast_project_saved": "프로젝트가 저장되었습니다",
|
||||
"toast_project_created": "프로젝트가 생성되었습니다."
|
||||
},
|
||||
"update": {
|
||||
"available": "업데이트 {{version}} 사용 가능",
|
||||
"install": "설치 후 다시 시작",
|
||||
"install_hint": "업데이트를 다운로드하고 새 버전으로 다시 시작합니다",
|
||||
"downloading": "업데이트 중… {{pct}}%",
|
||||
"restart": "다시 시작하여 업데이트",
|
||||
"busy": "먼저 더빙을 완료한 후 업데이트를 설치하세요.",
|
||||
"whats_new": "새로운 소식",
|
||||
"failed": "업데이트 실패",
|
||||
"retry": "재시도",
|
||||
"dismiss": "닫기"
|
||||
},
|
||||
"archetypes": {
|
||||
"featured": "추천",
|
||||
@@ -945,5 +1577,25 @@
|
||||
"facet_accent": "악센트",
|
||||
"facet_lang": "언어",
|
||||
"facet_whisper": "속삭임"
|
||||
},
|
||||
"support": {
|
||||
"tab_support": "지원",
|
||||
"tab_license": "상업용 라이센스",
|
||||
"toggle_label": "지원 또는 상업용 라이센스",
|
||||
"other_ways": "도움을 줄 수 있는 다른 방법",
|
||||
"star_github": "GitHub의 스타",
|
||||
"join_discord": "디스코드에 참여하세요"
|
||||
},
|
||||
"updates": {
|
||||
"tab": "업데이트",
|
||||
"up_to_date": "최신 · v{{version}}",
|
||||
"check_now": "지금 확인하세요",
|
||||
"releases": "릴리스",
|
||||
"current": "현재",
|
||||
"prerelease": "미리보기",
|
||||
"loading": "릴리스 로드 중…",
|
||||
"none": "릴리스를 찾을 수 없습니다.",
|
||||
"load_error": "릴리스를 로드할 수 없습니다(오프라인?)",
|
||||
"retry_load": "재시도"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,4 @@
|
||||
{
|
||||
"update": {
|
||||
"available": "Update {{version}} beschikbaar",
|
||||
"install": "Installeren en opnieuw opstarten",
|
||||
"install_hint": "Download de update en start opnieuw op in de nieuwe versie",
|
||||
"downloading": "Bijwerken… {{pct}}%",
|
||||
"restart": "Opnieuw opstarten om bij te werken",
|
||||
"busy": "Voltooi eerst je nasynchronisatie — installeer daarna de update."
|
||||
},
|
||||
"nav": {
|
||||
"clone": "Klonen",
|
||||
"design": "Ontwerpen",
|
||||
@@ -16,7 +8,10 @@
|
||||
"launchpad": "Lanceerplatform",
|
||||
"gallery": "Galerij",
|
||||
"transcripts": "Afschriften",
|
||||
"omnidrive": "OmniDrive"
|
||||
"omnidrive": "OmniDrive",
|
||||
"move_rail_right": "Verplaats de rail naar rechts",
|
||||
"move_rail_left": "Verplaats de rail naar links",
|
||||
"flip_rail": "Draai de railzijde om"
|
||||
},
|
||||
"settings": {
|
||||
"general": "Algemeen",
|
||||
@@ -45,7 +40,40 @@
|
||||
"ffmpeg_missing": "Niet gevonden",
|
||||
"ffmpeg_current": "Huidig pad",
|
||||
"ffmpeg_desc": "Stel een aangepast ffmpeg-pad in als automatische detectie mislukt.",
|
||||
"ffmpeg_saved": "FFmpeg-pad ingesteld - start de backend opnieuw om toe te passen."
|
||||
"ffmpeg_saved": "FFmpeg-pad ingesteld - start de backend opnieuw om toe te passen.",
|
||||
"diagnostics_copied": "Diagnostische gegevens gekopieerd: plak deze in uw probleemrapport.",
|
||||
"updater_desktop": "Updater werkt alleen in de desktop-app.",
|
||||
"latest_version": "Je gebruikt de nieuwste versie.",
|
||||
"logs_load_failed": "Kan logboeken niet laden: {{message}}",
|
||||
"clear_frontend_confirm": "De frontend-logboekbuffer in het geheugen wissen?",
|
||||
"clear_frontend_title": "Logboeken wissen",
|
||||
"frontend_logs_cleared": "Frontend-logboeken gewist",
|
||||
"clear_tauri_confirm": "De logbestanden aan de Tauri-zijde inkorten? Het besturingssysteem blijft nieuwe vermeldingen schrijven.",
|
||||
"clear_tauri_title": "Wis Tauri-logboeken",
|
||||
"nothing_to_clear": "Niets te wissen - nog geen Tauri-logbestand op schijf.",
|
||||
"cleared_tauri_one": "{{count}} Tauri-logbestand gewist",
|
||||
"cleared_tauri_other": "{{count}} Tauri-logbestanden gewist",
|
||||
"clear_tauri_failed": "Kan Tauri-logboeken niet wissen: {{message}}",
|
||||
"clear_backend_confirm": "De backend-runtime- en crashlogboeken wissen? Dit kan niet ongedaan worden gemaakt.",
|
||||
"clear_backend_title": "Logboeken wissen",
|
||||
"backend_logs_cleared": "Backend-logboeken gewist",
|
||||
"clear_backend_failed": "Kan de logboeken niet wissen",
|
||||
"copy_failed": "Kopiëren mislukt: {{message}}",
|
||||
"update_check_failed": "Updatecontrole mislukt: {{message}}",
|
||||
"save_failed": "Opslaan mislukt: {{message}}",
|
||||
"clear_failed": "Wissen mislukt: {{message}}",
|
||||
"engine_switched": "{{family}} → {{engine}}",
|
||||
"channel_set_failed": "Kan kanaal niet instellen: {{message}}",
|
||||
"updater_downloading": "{{version}} downloaden…",
|
||||
"updater_installed": "Geïnstalleerd - opnieuw gestart.",
|
||||
"updater_available_title": "Update beschikbaar",
|
||||
"updater_available_body": "Versie {{version}} is beschikbaar.\n\n{{notes}}\n\nNu downloaden en installeren?",
|
||||
"updater_notes_fallback": "Zie release-opmerkingen op GitHub.",
|
||||
"shortcut_load_failed": "Kan snelkoppeling niet laden: {{message}}",
|
||||
"shortcut_set": "Dicteersnelkoppeling ingesteld op {{shortcut}}",
|
||||
"shortcut_register_failed": "Kon niet registreren: {{message}}",
|
||||
"shortcut_reset": "Resetten naar standaard",
|
||||
"shortcut_reset_failed": "Reset mislukt: {{message}}"
|
||||
},
|
||||
"bootstrap": {
|
||||
"title": "OmniVoice Studio",
|
||||
@@ -69,7 +97,13 @@
|
||||
"waiting_output": "Wachten op uitvoer…",
|
||||
"auto_detect": "Automatisch detecteren",
|
||||
"suggest_lang": "Overschakelen naar het Nederlands?",
|
||||
"select_lang": "Taal:"
|
||||
"select_lang": "Taal:",
|
||||
"region_global": "Globaal (direct)",
|
||||
"region_china": "China (spiegel)",
|
||||
"region_russia": "Rusland (spiegel)",
|
||||
"region_restricted": "Beperkt (spiegel)",
|
||||
"unknown_error": "Onbekende fout",
|
||||
"retrying": "Opnieuw proberen…"
|
||||
},
|
||||
"stories": {
|
||||
"title": "Verhalen-editor",
|
||||
@@ -158,7 +192,17 @@
|
||||
"reload": "Forceer herladen UI",
|
||||
"backend": "Achterkant",
|
||||
"frontend": "Frontend",
|
||||
"tauri": "Tauri"
|
||||
"tauri": "Tauri",
|
||||
"cancelOp": "Annuleer de bewerking",
|
||||
"dismiss": "Negeren",
|
||||
"dismissStatus": "Status afwijzen",
|
||||
"search": "Zoek…",
|
||||
"no_matches": "Geen overeenkomsten",
|
||||
"recent_and_popular": "Recent en populair",
|
||||
"popular_label": "Populair",
|
||||
"showing_of": "Toont {{shown}} van {{total}}. Typ om te zoeken…",
|
||||
"yes": "Ja",
|
||||
"no": "Nee"
|
||||
},
|
||||
"launchpad": {
|
||||
"greeting": "Hallo daar",
|
||||
@@ -180,7 +224,16 @@
|
||||
"locked": "GESLOTEN",
|
||||
"open": "Openen",
|
||||
"try_it": "Probeer het",
|
||||
"audio_only": "Alleen audio"
|
||||
"audio_only": "Alleen audio",
|
||||
"stories_title": "Verhalen",
|
||||
"stories_desc": "Audioboeken met meerdere stemmen: cast je personages, voeg een script toe en exporteer per hoofdstuk.",
|
||||
"gallery_title": "Stemgalerij",
|
||||
"gallery_desc": "Blader door kant-en-klare ontworpen stemmen op accent, leeftijd en stijl - geen installatie.",
|
||||
"transcripts_title": "Afschriften",
|
||||
"transcripts_desc": "Zet audio of video om in bewerkbare, doorzoekbare tekst – in 646 talen.",
|
||||
"recent_files": "Recente bestanden",
|
||||
"view_all_files": "Bekijk alle bestanden",
|
||||
"file": "Bestand"
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "Prompt",
|
||||
@@ -271,11 +324,6 @@
|
||||
"outputs": "Uitgangen",
|
||||
"crash_log": "Crashlogboek",
|
||||
"update_endpoint": "Eindpunt bijwerken",
|
||||
"update_channel": "Updatekanaal",
|
||||
"channel_stable": "Stabiel",
|
||||
"channel_preview": "Voorbeeld",
|
||||
"channel_set": "Updatekanaal ingesteld op {{channel}}",
|
||||
"channel_preview_hint": "Voorbeeld volgt de nieuwste main-build — nieuwere functies, minder getest. Valt terug op stabiel als er een nieuwere stabiele release is.",
|
||||
"yes": "ja",
|
||||
"no": "nee",
|
||||
"web_preview": "webvoorbeeld",
|
||||
@@ -284,7 +332,12 @@
|
||||
"copy_diagnostics": "Diagnostische gegevens kopiëren",
|
||||
"github": "OmniVoice op GitHub",
|
||||
"model_card": "Modelkaart",
|
||||
"commercial_license": "Commerciële licentie"
|
||||
"commercial_license": "Commerciële licentie",
|
||||
"update_channel": "Updatekanaal",
|
||||
"channel_stable": "Stabiel",
|
||||
"channel_preview": "Voorbeeld",
|
||||
"channel_set": "Updatekanaal ingesteld op {{channel}}",
|
||||
"channel_preview_hint": "Voorbeeld volgt de nieuwste main-build — nieuwere functies, minder getest. Valt terug op stabiel als er een nieuwere stabiele release is."
|
||||
},
|
||||
"privacy": {
|
||||
"desc": "Alles draait op <1>deze machine</1>. Uw audio, video en transcripties verlaten uw computer nooit, tenzij u expliciet een online vertaler (Google, DeepL, enz.) gebruikt of naar HuggingFace pusht.",
|
||||
@@ -340,7 +393,30 @@
|
||||
"unavailable": "niet beschikbaar",
|
||||
"use": "Gebruik",
|
||||
"loading": "Motoren laden…",
|
||||
"refresh": "Vernieuwen"
|
||||
"refresh": "Vernieuwen",
|
||||
"matrixTitle": "Motorcompatibiliteitsmatrix",
|
||||
"loadFailed": "Kan motoren niet laden: {{message}}",
|
||||
"couldNotLoad": "Kan motoren niet laden: {{message}}",
|
||||
"retry": "Opnieuw proberen",
|
||||
"activeEngine": "Actief {{family}}: {{engine}}",
|
||||
"engineCompatLabel": "{{family}} motorcompatibiliteit",
|
||||
"active": "actief",
|
||||
"whyUnavailable": "Waarom niet beschikbaar?",
|
||||
"lastError": "Laatste fout: {{error}}",
|
||||
"installedAndReady": "Geïnstalleerd en klaar",
|
||||
"notInstalled": "Niet geïnstalleerd",
|
||||
"available": "Beschikbaar",
|
||||
"subprocessTitle": "Draait in een eigen subproces + venv",
|
||||
"inProcessTitle": "Wordt uitgevoerd in het OmniVoice Python-proces",
|
||||
"testEngine": "Testmotor",
|
||||
"testing": "Testen…",
|
||||
"recheck": "Controleer opnieuw",
|
||||
"rechecking": "Opnieuw controleren…",
|
||||
"latencyMs": "{{ms}} ms",
|
||||
"failed": "mislukt",
|
||||
"acceptLicense": "Accepteer licentie",
|
||||
"noBackends": "Geen backends geregistreerd.",
|
||||
"switch_failed": "Het is niet gelukt om van motor te wisselen"
|
||||
},
|
||||
"capture": {
|
||||
"desc": "Globale sneltoetsen werken alleen in de desktop-app. De webinterface gebruikt een sneltoets <1>Ctrl+Shift+Spatie</1> op de pagina terwijl het venster focus heeft.",
|
||||
@@ -352,13 +428,55 @@
|
||||
"record_shortcut": "Snelkoppeling opnemen",
|
||||
"recording": "Opnemen…",
|
||||
"save": "Opslaan",
|
||||
"reset_default": "Resetten naar standaard"
|
||||
"reset_default": "Resetten naar standaard",
|
||||
"listening_label": "Luisteren…",
|
||||
"transcribing_label": "Transcriberen…",
|
||||
"pasted": "Geplakt",
|
||||
"no_speech": "Geen spraak gedetecteerd",
|
||||
"mic_denied": "Microfoontoegang geweigerd",
|
||||
"mic_denied_toast": "Microfoontoegang geweigerd. {{hint}}",
|
||||
"mic_hint_mac": "macOS: open Systeeminstellingen → Privacy en beveiliging → Microfoon en schakel OmniVoice in.",
|
||||
"mic_hint_windows": "Windows: open Instellingen → Privacy en beveiliging → Microfoon en sta OmniVoice toe.",
|
||||
"mic_hint_linux": "Linux: controleer of uw gebruiker in de audiogroep zit en of de WebView microfoontoegang heeft.",
|
||||
"transcription_failed": "Transcriptie mislukt: {{message}}"
|
||||
},
|
||||
"logs": {
|
||||
"no_tauri_log": "Nog geen Tauri-inlogschijf: start via de desktop-build om er een te maken",
|
||||
"empty_frontend": "Er zijn nog geen frontendconsole-items vastgelegd. Communiceer met de app: elke console.* verschijnt hier.",
|
||||
"empty_tauri": "Geen Tauri-logboek beschikbaar. Werkt alleen in de desktopshell.",
|
||||
"empty_backend": "Runtimelogboek is leeg. Activiteit wordt hier weergegeven terwijl de backend deze registreert."
|
||||
"empty_backend": "Runtimelogboek is leeg. Activiteit wordt hier weergegeven terwijl de backend deze registreert.",
|
||||
"title": "Logboeken",
|
||||
"source_backend": "Achterkant",
|
||||
"source_frontend": "Frontend",
|
||||
"source_tauri": "Tauri",
|
||||
"expand": "Vouw logboeken uit",
|
||||
"collapse": "Logboeken samenvouwen",
|
||||
"expand_aria": "Vouw het logboekpaneel uit",
|
||||
"collapse_aria": "Deelvenster Logboeken samenvouwen",
|
||||
"drag_resize": "Sleep om het formaat te wijzigen",
|
||||
"refresh": "Vernieuwen",
|
||||
"refresh_aria": "Vernieuw logboeken",
|
||||
"copy_visible": "Kopieer zichtbaar logboek",
|
||||
"copy_visible_aria": "Kopieer zichtbaar logboek",
|
||||
"clear": "Duidelijk",
|
||||
"clear_aria": "Logboek wissen",
|
||||
"report_issue": "Probleem melden (diagnostische kopie)",
|
||||
"report_issue_aria": "Probleem melden",
|
||||
"close": "Sluiten",
|
||||
"close_aria": "Sluit het logboekpaneel",
|
||||
"join_discord": "Sluit je aan bij onze onenigheid",
|
||||
"join_discord_aria": "Sluit je aan bij onze Discord-community",
|
||||
"support_project": "Steun dit project",
|
||||
"support_project_aria": "Steun dit project",
|
||||
"empty_frontend_short": "Nog geen frontendconsole-uitvoer.",
|
||||
"empty_lines": "Geen lijnen.",
|
||||
"all_clear": "✅ Alles duidelijk: geen problemen gedetecteerd",
|
||||
"log_cleared": "{{source}} log gewist",
|
||||
"clear_failed": "Wissen mislukt: {{message}}",
|
||||
"log_copied": "Gekopieerd {{source}} log",
|
||||
"copy_failed": "Kopiëren mislukt: {{message}}",
|
||||
"report_copied": "Diagnostisch rapport gekopieerd: plak het in een GitHub-probleem.",
|
||||
"report_failed": "Rapport mislukt: {{message}}"
|
||||
},
|
||||
"voice_profile": {
|
||||
"test_text": "Hallo – dit is een test van deze stem.",
|
||||
@@ -526,7 +644,17 @@
|
||||
"install_already": "{{engine}} was al geïnstalleerd",
|
||||
"install_ok": "{{engine}} geïnstalleerd",
|
||||
"install_failed": "Installatie mislukt: {{message}}",
|
||||
"prep_elapsed": "{{time}} verstreken"
|
||||
"prep_elapsed": "{{time}} verstreken",
|
||||
"add_language": "Taal toevoegen",
|
||||
"search_languages": "Talen zoeken…",
|
||||
"languages_selected_one": "{{count}} taal geselecteerd",
|
||||
"languages_selected_other": "{{count}} talen geselecteerd",
|
||||
"more_to_narrow": "+{{count}} meer — typ om te verkleinen",
|
||||
"no_matches": "Geen overeenkomsten",
|
||||
"diagnostic_copied": "Diagnostiek gekopieerd",
|
||||
"copy_failed": "Kopiëren mislukt",
|
||||
"open_docs": "Documenten openen",
|
||||
"copy_diagnostic": "Diagnostiek kopiëren"
|
||||
},
|
||||
"glossary": {
|
||||
"title": "Woordenlijst",
|
||||
@@ -601,7 +729,17 @@
|
||||
"more_actions_title": "Meer acties",
|
||||
"speaker_pick": "Kies…",
|
||||
"speaker_title_detected": "Spreker: kies uit de gedetecteerde naam of typ een aangepaste naam",
|
||||
"speaker_title_custom": "Spreker — typ een naam (geen dagboekklonen gedetecteerd)"
|
||||
"speaker_title_custom": "Spreker — typ een naam (geen dagboekklonen gedetecteerd)",
|
||||
"time_edit_title": "Klik om de starttijd te bewerken (m:ss.s). Enter om vast te leggen, Esc om te annuleren.",
|
||||
"fit_fits": "Past",
|
||||
"fit_fits_title": "Audio met natuurlijke snelheid past in de sleuf.",
|
||||
"fit_overflows": "Overstromen +{{seconds}}s",
|
||||
"fit_overflows_title": "De vertaalde tekst was {{seconds}}s langer dan de oorspronkelijke tekst. De audio was hard getrimd; verkort de tekst of schakel Timing naar \"Video uitrekken\".",
|
||||
"fit_stretched": "Video {{ratio}}×",
|
||||
"fit_stretched_title": "Stretch Video-modus: de video van dit segment is vertraagd tot {{ratio}}× om te passen bij de natuurlijke dub-audio.",
|
||||
"fit_compressed_title": "TTS-audio is {{pct}}% van de sleuf – zwaar gecomprimeerd.",
|
||||
"fit_audio_title": "Audio past in de sleuf.",
|
||||
"fit_ratio_title": "TTS-audio is {{pct}}% van de sleuf."
|
||||
},
|
||||
"voice": {
|
||||
"personality": "Persoonlijkheid",
|
||||
@@ -696,10 +834,24 @@
|
||||
"status_running": "rennen",
|
||||
"status_done": "gedaan",
|
||||
"status_failed": "mislukt",
|
||||
"status_cancelled": "geannuleerd"
|
||||
"status_cancelled": "geannuleerd",
|
||||
"add_to_queue_title": "Voeg video's toe aan de wachtrij",
|
||||
"drop_hint_text": "Zet hier videobestanden neer of klik om te bladeren",
|
||||
"drop_formats": "MP4 · MOV · MKV · WEBM",
|
||||
"files_kicker": "BESTANDEN ({{count}})",
|
||||
"file_size_mb": "{{size}} MB",
|
||||
"target_languages": "DOELTALEN",
|
||||
"voice_kicker": "STEM",
|
||||
"default_option": "Standaard",
|
||||
"clone_profiles": "Kloon profielen",
|
||||
"presets": "Voorinstellingen",
|
||||
"preserve_bg": "Achtergrondaudio behouden (muziek/FX)",
|
||||
"estimate": "{{videos}} video(s) × {{langs}} taal(s) = {{jobs}} vacature(s)",
|
||||
"select_files_langs": "Selecteer bestanden en talen",
|
||||
"add_to_queue": "Toevoegen aan wachtrij"
|
||||
},
|
||||
"gallery": {
|
||||
"title": "Galerij",
|
||||
"title": "OmniVoice Galerij",
|
||||
"search_placeholder": "Zoek op YouTube...",
|
||||
"all_voices": "Alle stemmen ({{count}})",
|
||||
"no_voices": "Nog geen stemmen",
|
||||
@@ -714,6 +866,14 @@
|
||||
"youtube_results": "YouTube-resultaten ({{count}})",
|
||||
"clone_profile": "Kloon profiel",
|
||||
"crop_audio": "Audio bijsnijden",
|
||||
"cat_disney": "Disney",
|
||||
"cat_anime": "Anime",
|
||||
"cat_marvel": "Wonder/DC",
|
||||
"cat_celebs": "Beroemdheden",
|
||||
"cat_politicians": "Politici",
|
||||
"cat_news": "Nieuwsankers",
|
||||
"cat_gaming": "Gamen",
|
||||
"cat_books": "Boeken/films",
|
||||
"subtitle": "Honderden kant-en-klare ontworpen stemmen: kies er één en ga aan de slag.",
|
||||
"zone_archetypes": "Archetypen",
|
||||
"zone_imports": "Mijn import",
|
||||
@@ -818,7 +978,6 @@
|
||||
"badge": "Commerciële licentie",
|
||||
"hero_title": "Verzend AI-stemmen in productie",
|
||||
"hero_desc": "OmniVoice Studio is bron-beschikbaar onder de Functionele Bronlicentie (FSL). De meeste gebruikers kunnen zonder commerciële overeenkomst evalueren, prototypen maken en zelfs intern implementeren. U heeft alleen een commerciële licentie nodig als u een concurrerend product of dienst bouwt, of als uw gebruiksscenario buiten de grenzen van de FSL valt.",
|
||||
"hero_note": "Voor het bouwen van een concurrerend product of dienst of het op grote schaal implementeren (bijvoorbeeld het aanbieden van een pay-per-use API) is een commerciële licentie vereist. Prijsniveaus volgen binnenkort. Neem in de tussentijd contact met ons op.",
|
||||
"why_title": "Waarom bedrijven voor OmniVoice kiezen",
|
||||
"pricing_title": "Prijzen",
|
||||
"faq_title": "Veelgestelde vragen",
|
||||
@@ -838,7 +997,8 @@
|
||||
"benefit_source": "Bronbeschikbare kern",
|
||||
"benefit_source_desc": "Volledig zicht op de stapel. Audit, fork en pas aan binnen de licentievoorwaarden.",
|
||||
"benefit_lang": "646 talen",
|
||||
"benefit_lang_desc": "Transcribeer, vertaal en kopieer in 646 talen met kwaliteit op menselijk niveau."
|
||||
"benefit_lang_desc": "Transcribeer, vertaal en kopieer in 646 talen met kwaliteit op menselijk niveau.",
|
||||
"hero_note": "Intern gebruik – zelfs op grote schaal – is gratis onder de FSL; een commerciële licentie is alleen vereist om OmniVoice aan anderen aan te bieden als een concurrerend product of dienst (een gehoste of pay-per-use API, een doorverkochte of white-labeled app). Prijsniveaus volgen binnenkort. Neem in de tussentijd contact met ons op."
|
||||
},
|
||||
"exportModal": {
|
||||
"export": "Exporteren",
|
||||
@@ -927,7 +1087,479 @@
|
||||
"dubbing_title": "Zie nasynchronisatie in actie",
|
||||
"dubbing_sync": "Gesynchroniseerd afspelen",
|
||||
"dubbing_picker": "Probeer een andere taal:",
|
||||
"dubbing_cta": "Voer dit uit op uw eigen video →"
|
||||
"dubbing_cta": "Voer dit uit op uw eigen video →",
|
||||
"dubbing_loading": "Dubdemo laden…",
|
||||
"dubbing_dismiss": "Nasynchronisatiedemo sluiten",
|
||||
"original_tag": "origineel",
|
||||
"dubbed_tag": "nagesynchroniseerd",
|
||||
"script_conversational": "Gemoedelijk",
|
||||
"script_technical": "Technische woordenschat",
|
||||
"script_french": "Niet-Engels (Frans)",
|
||||
"aria_pause": "Pauze {{label}}",
|
||||
"aria_hear": "Hoor {{label}}",
|
||||
"aria_replay": "Speel {{label}} opnieuw af via de transcriber"
|
||||
},
|
||||
"direction": {
|
||||
"title": "Richting voor segment #{{id}}",
|
||||
"desc": "Vertel de pijpleiding hoe deze lijn moet aanvoelen. Gewoon Engels werkt: het systeem brengt uw woorden in kaart in een stabiele taxonomie (energie / emotie / tempo / intimiteit / formaliteit) en leidt de taxonomie vervolgens via filmische vertaling, TTS en slot-fit.",
|
||||
"label": "Richting",
|
||||
"lineHint": "Regel: \"{{text}}\"",
|
||||
"placeholder": "bijv. urgent en verrast/warm, hoopvol/gefluisterd, intiem",
|
||||
"previewParse": "Voorbeeld parseren",
|
||||
"previewFailed": "Voorbeeld mislukt: {{message}}",
|
||||
"clear": "Duidelijk",
|
||||
"cancel": "Annuleer",
|
||||
"saveDirection": "Bewaar richting",
|
||||
"ttsInstruct": "TTS instrueren:",
|
||||
"nothingParsed": "- (niets ontleed)",
|
||||
"translateHint": "Vertaal hint:",
|
||||
"rateBias": "Beoordeel bias:",
|
||||
"speedsUp": "versnelt",
|
||||
"slowsDown": "vertraagt",
|
||||
"taxonomyTokens": "taxonomie tokens"
|
||||
},
|
||||
"errors": {
|
||||
"title": "Dit tabblad stuitte op een probleem.",
|
||||
"desc": "Maak je geen zorgen: de rest van de app werkt nog steeds. U kunt van tabblad wisselen of het hieronder opnieuw proberen.",
|
||||
"tryAgain": "Probeer het opnieuw",
|
||||
"openDocs": "Open documenten voor deze fout"
|
||||
},
|
||||
"keyboard": {
|
||||
"title": "Sneltoetsen",
|
||||
"footer": "Druk op elk gewenst moment op <1>?</1> om dit te openen.",
|
||||
"or": "of",
|
||||
"nav": "Navigatie",
|
||||
"nav_cheatsheet": "Laat dit spiekbriefje zien",
|
||||
"nav_closeModal": "Modaal sluiten / annuleren",
|
||||
"nav_save": "Project opslaan / trimmen vastleggen",
|
||||
"segmentEditor": "Segmenteditor",
|
||||
"seg_split": "Segment splitsen bij cursor",
|
||||
"seg_merge": "Samenvoegen met het volgende segment",
|
||||
"seg_undo": "Ongedaan maken",
|
||||
"seg_redo": "Opnieuw uitvoeren",
|
||||
"seg_click": "Primaire actie",
|
||||
"seg_shiftClick": "Bereik selecteren",
|
||||
"trimmer": "Audiotrimmer",
|
||||
"trim_playPause": "Voorbeeld afspelen / pauzeren",
|
||||
"trim_nudgeStart": "Duw starthandvat",
|
||||
"trim_nudgeEnd": "Duw eindhandgreep",
|
||||
"trim_fineNudge": "Fijn duwtje",
|
||||
"trim_coarseNudge": "Grof duwtje",
|
||||
"trim_zoomIn": "In-/uitzoomen",
|
||||
"trim_fitAll": "Pas alles aan / Pas selectie aan",
|
||||
"trim_confirm": "Bevestig het trimmen",
|
||||
"dub": "Dub",
|
||||
"dub_generate": "Dub genereren",
|
||||
"dub_sidebar": "Zijbalk schakelen"
|
||||
},
|
||||
"network": {
|
||||
"sharing_on_title": "Delen op - klik voor details",
|
||||
"share_on_network": "Deel op uw netwerk",
|
||||
"switching": "Overstappen...",
|
||||
"network": "Netwerk",
|
||||
"local": "Lokaal",
|
||||
"share_confirm_title": "Delen op uw netwerk?",
|
||||
"share_confirm_hint": "Andere apparaten op uw Wi-Fi/Ethernet kunnen OmniVoice bereiken met behulp van de toegangspincode die wordt weergegeven zodra deze is ingeschakeld.",
|
||||
"enabling": "Inschakelen…",
|
||||
"enable": "Inschakelen",
|
||||
"shared_title": "Gedeeld op uw netwerk",
|
||||
"no_interface": "Geen bereikbare netwerkinterface: maak verbinding met Wi-Fi/Ethernet.",
|
||||
"copy_link": "Kopieer link",
|
||||
"open_in_browser": "Openen in browser",
|
||||
"qr_alt": "QR voor {{ip}}",
|
||||
"pin": "Pincode:",
|
||||
"stop_sharing": "Stop met delen",
|
||||
"copied": "Gekopieerd",
|
||||
"enable_error": "Kan delen niet inschakelen: {{message}}",
|
||||
"disable_error": "Kon niet uitschakelen: {{message}}"
|
||||
},
|
||||
"readiness": {
|
||||
"checking_system": "Systeem controleren…",
|
||||
"all_ready": "Alle systemen klaar",
|
||||
"system_readiness": "Systeemgereedheid",
|
||||
"asr_model": "ASR-model",
|
||||
"loaded_ready": "Geladen en klaar",
|
||||
"loading_first_run": "Laden... (dit kan bij de eerste run 1-2 minuten duren)",
|
||||
"failed_to_load": "Kan niet laden",
|
||||
"not_loaded_yet": "Nog niet geladen: wordt geladen bij de eerste transcriptie",
|
||||
"error_check_logs": "Fout: {{error}}. Controleer de logboeken en probeer opnieuw op te starten.",
|
||||
"check_logs_restart": "Controleer logboeken op fouten bij het laden van modellen. Probeer opnieuw op te starten.",
|
||||
"llm_cinematic": "LLM (filmisch)",
|
||||
"llm_configure": "Configureer TRANSLATE_BASE_URL voor filmische vertaalkwaliteit",
|
||||
"llm_set_env": "Stel de omgevingsvariabelen TRANSLATE_BASE_URL en TRANSLATE_API_KEY in. Werkt met Ollama, OpenAI, LM Studio, enz.",
|
||||
"llm_optional": "Optioneel: stel TRANSLATE_BASE_URL in voor filmische kwaliteit"
|
||||
},
|
||||
"license": {
|
||||
"title": "Supertonic-3 — Licentieacceptatie",
|
||||
"intro": "Supertonic-3 schepen onder twee verschillende licenties. Controleer beide voordat u de engine inschakelt.",
|
||||
"sdk_heading": "SDK-code · MIT",
|
||||
"sdk_desc": "De Python-inferentie-SDK (supertonic) heeft een MIT-licentie. Toegestaan gebruik, inclusief commercieel gebruik.",
|
||||
"read_mit": "Lees de MIT-licentie →",
|
||||
"model_heading": "Modelgewichten · OpenRAIL-M",
|
||||
"model_desc": "De Supertonic-3 modelgewichten zijn vrijgegeven onder de OpenRAIL-M-licentie. Deze licentie beperkt het gebruik tot niet-kwaadwillige doeleinden. Zie de gekoppelde licentie voor de volledige set op gebruik gebaseerde beperkingen.",
|
||||
"read_openrail": "Lees de OpenRAIL-M licentie →",
|
||||
"footer": "Als u op Accepteren klikt, wordt uw acceptatie vastgelegd in de lokale instellingen van OmniVoice en wordt de engine ingeschakeld. Uw aanvaarding wordt alleen op deze machine opgeslagen; er wordt niets gerapporteerd aan Supertone Inc. of een derde partij.",
|
||||
"saving": "Opslaan…",
|
||||
"accept": "Accepteer",
|
||||
"accepted_toast": "Supertonic-3-licentie geaccepteerd.",
|
||||
"accept_error": "Kan licentieacceptatie niet vastleggen: {{message}}"
|
||||
},
|
||||
"voicePreview": {
|
||||
"title": "Stemvoorbeeld",
|
||||
"close": "Voorbeeld sluiten",
|
||||
"default_text": "Hallo! Dit is een voorproefje van hoe ik met deze stem klink.",
|
||||
"default_voice": "Standaard stem",
|
||||
"clone_profiles": "Kloon profielen",
|
||||
"designed_voices": "Ontworpen stemmen",
|
||||
"presets": "Voorinstellingen",
|
||||
"placeholder": "Typ iets om te horen...",
|
||||
"stop": "Stop",
|
||||
"regenerate": "Regenereer",
|
||||
"preview": "Voorbeeld",
|
||||
"hint": "8 stappen · snelle preview"
|
||||
},
|
||||
"header": {
|
||||
"kicker_studio": "Studio",
|
||||
"kicker_library": "Bibliotheek",
|
||||
"kicker_preferences": "Voorkeuren",
|
||||
"kicker_licensing": "Licentie",
|
||||
"label_launchpad": "Lanceerplatform",
|
||||
"label_clone": "Stemkloon",
|
||||
"label_design": "Stemontwerp",
|
||||
"label_dub": "Nasynchronisatie",
|
||||
"label_projects": "OmniDrive",
|
||||
"label_gallery": "Galerij",
|
||||
"label_transcriptions": "Transcripties",
|
||||
"label_settings": "Instellingen",
|
||||
"label_enterprise": "Commerciële licentie",
|
||||
"status_ready": "Klaar",
|
||||
"status_loading": "Laden…",
|
||||
"status_idle": "Inactief",
|
||||
"memory_management": "Geheugenbeheer",
|
||||
"flush": "Spoelen",
|
||||
"loaded_models": "Geladen modellen",
|
||||
"no_models": "Er zijn geen modellen geladen",
|
||||
"unload": "Lossen",
|
||||
"flush_caches": "Caches leegmaken",
|
||||
"unload_all_flush": "Alles leegmaken + spoelen"
|
||||
},
|
||||
"sidebar": {
|
||||
"tab_drive": "Rijd",
|
||||
"tab_history": "Geschiedenis",
|
||||
"tab_exports": "Exporteert",
|
||||
"save_project": "Dub-project opslaan",
|
||||
"save_new_project": "Opslaan als nieuw dubproject",
|
||||
"dub_projects": "Dub-projecten",
|
||||
"voice_clones": "Stemklonen",
|
||||
"designed_voices": "Ontworpen stemmen",
|
||||
"no_dub_projects": "Geen opgeslagen dubprojecten",
|
||||
"no_dub_hint": "Upload een video en klik op Opslaan om uw werk te behouden.",
|
||||
"no_clones": "Nog geen stemklonen",
|
||||
"no_clones_hint": "Neem audio op of upload deze en klik vervolgens op Opslaan als stemprofiel.",
|
||||
"no_designs": "Nog geen ontworpen stemmen",
|
||||
"no_designs_hint": "Genereer een stem en sla deze op uit de geschiedenis.",
|
||||
"history_subtitle": "Generatiegeschiedenis · Opgeslagen in SQLite",
|
||||
"no_history": "Geen generatiegeschiedenis",
|
||||
"no_history_hint": "Synthetiseer audio of kopieer een video: de resultaten verschijnen hier.",
|
||||
"clear_history": "Geschiedenis wissen",
|
||||
"clear_confirm": "Alle {{count}} geschiedenisitems wissen? Dit kan niet ongedaan worden gemaakt.",
|
||||
"history_cleared": "Geschiedenis gewist",
|
||||
"recent_exports": "Recente exporten",
|
||||
"no_exports": "Geen gedownloade uitvoer",
|
||||
"no_exports_hint": "Exporteer een bestand via Tauri om het hier bij te houden.",
|
||||
"show_in_folder": "Weergeven in map",
|
||||
"open": "Openen",
|
||||
"select": "Selecteer",
|
||||
"try_voice": "Probeer",
|
||||
"consistent": "consistent",
|
||||
"locked": "Vergrendeld",
|
||||
"clone_label": "Kloon",
|
||||
"design_label": "Ontwerp",
|
||||
"dub_label": "Dub",
|
||||
"save_label": "Opslaan",
|
||||
"lock_identity": "Stemidentiteit vergrendelen",
|
||||
"in_folder": "in {{folder}}"
|
||||
},
|
||||
"trimmer": {
|
||||
"title": "Referentie-audio trimmen",
|
||||
"decoding": "Audio decoderen...",
|
||||
"meta_length": "Lengte {{duration}} · {{sampleRate}} Hz",
|
||||
"meta_rendering": "weergavegolfvorm {{percent}}%",
|
||||
"keyboard_hint": "scrollen = zoomen · shift+scroll = pannen · alt+slepen = pannen · ⏐ ⟵ ⟶ ⏐ toetsen grepen aanpassen",
|
||||
"zoom_in": "Inzoomen (+)",
|
||||
"zoom_out": "Uitzoomen (-)",
|
||||
"fit_all": "Geschikt voor iedereen (thuis)",
|
||||
"fit_selection": "Passende selectie (Einde)",
|
||||
"fit_sel_btn": "FIT SEL",
|
||||
"view_range": "Bekijk {{start}} → {{end}} ({{duration}})",
|
||||
"start_label": "Begin",
|
||||
"end_label": "Einde",
|
||||
"length_label": "Lengte",
|
||||
"too_long": ">{{max}}s",
|
||||
"too_short": "te kort",
|
||||
"length_ok": "oké",
|
||||
"loop_preview": "Lusvoorbeeld",
|
||||
"pause": "Pauze",
|
||||
"preview_selection": "Voorbeeldselectie",
|
||||
"play_hint": "Ruimte om te spelen · Enter om te bevestigen · Esc om te annuleren",
|
||||
"cancel": "Annuleer",
|
||||
"use_trimmed": "Gebruik bijgesneden",
|
||||
"decode_failed": "Decodering mislukt: {{message}}",
|
||||
"playback_failed": "Afspelen mislukt: {{message}}",
|
||||
"audio_load_failed": "Audio laden mislukt",
|
||||
"unit_seconds": "s"
|
||||
},
|
||||
"casting": {
|
||||
"title": "Sprekerscasting",
|
||||
"auto_assign_title": "Wijs automatisch stemmen toe uit geëxtraheerde luidsprekerklonen",
|
||||
"auto_cast": "Automatisch casten",
|
||||
"all_cast": "Allemaal gegoten",
|
||||
"segments_count": "{{count}} segmenten",
|
||||
"assign_voice": "Stem toewijzen…",
|
||||
"from_video": "🎤 Uit video ({{name}})",
|
||||
"no_profiles": "Er zijn nog geen stemprofielen opgeslagen.",
|
||||
"preview_voice": "Voorbeeldstem"
|
||||
},
|
||||
"checkpoint": {
|
||||
"asr_title": "Afschriften klaar",
|
||||
"asr_cta": "Vertalen",
|
||||
"asr_hint": "Los eventuele ASR-fouten nu op: strakke dictie bespaart TTS-pogingen later.",
|
||||
"translate_title": "Vertalingen klaar",
|
||||
"translate_cta": "Dub genereren",
|
||||
"translate_hint": "Blader door de doeltekst. Lijnen die te lang zijn, krijgen een snelheidsboost; u kunt ook rechtstreeks bewerken.",
|
||||
"done_title": "Dubben voltooid",
|
||||
"done_hint": "Controleer de timing en synchronisatieverhoudingen. Pas een willekeurige regel aan en druk op \"Regen gewijzigd\" voor een snelle gedeeltelijke herhaling.",
|
||||
"segment_one": "{{count}} segment",
|
||||
"segment_other": "{{count}} segmenten",
|
||||
"dismiss_title": "Afsluiten — verschijnt pas opnieuw in deze fase als u opnieuw laadt"
|
||||
},
|
||||
"compare": {
|
||||
"title": "A/B-stemvergelijking",
|
||||
"close": "Nauwkeurige vergelijking",
|
||||
"desc": "Vergelijk twee stemmen naast elkaar om castingbeslissingen te nemen. App blijft interactief achter.",
|
||||
"test_phrase": "Testzin",
|
||||
"voice_a": "Stem A",
|
||||
"voice_b": "Stem B",
|
||||
"select_voice": "— Selecteer stem —",
|
||||
"preset_suffix": "(Vooraf ingesteld)",
|
||||
"no_audio": "Nog geen geluid",
|
||||
"close_btn": "Sluiten",
|
||||
"comparing": "Vergelijken…",
|
||||
"compare_btn": "Vergelijk",
|
||||
"preparing_voice": "Stem voorbereiden...",
|
||||
"generating_voice_a": "Stem genereren...",
|
||||
"generating_voice_b": "Stem B genereren...",
|
||||
"comparison_complete": "Vergelijking voltooid!",
|
||||
"play_failed": "Afspelen mislukt: {{message}}"
|
||||
},
|
||||
"models": {
|
||||
"hf_token_set_toast": "HuggingFace-tokenset — snellere downloads ingeschakeld",
|
||||
"hf_token_save_failed": "Kan token niet opslaan",
|
||||
"install_started": "Installatie gestart — voortgang in de rij",
|
||||
"delete_confirm": "{{repoId}} verwijderen? U kunt het later opnieuw installeren.",
|
||||
"delete_confirm_title": "Model verwijderen",
|
||||
"deleted": "Verwijderd {{repoId}}",
|
||||
"reinstall_confirm": "{{repoId}} opnieuw installeren? Hiermee wordt de huidige kopie verwijderd en opnieuw gedownload.",
|
||||
"reinstall_confirm_title": "Installeer het model opnieuw",
|
||||
"reinstalling": "Opnieuw installeren",
|
||||
"recommended_installed": "Aanbevolen modellen zijn al geïnstalleerd.",
|
||||
"started_downloading_one": "Begonnen met downloaden van model {{count}}",
|
||||
"started_downloading_other": "Begonnen met het downloaden van {{count}} modellen",
|
||||
"install_failed": "Installatie mislukt: {{message}}",
|
||||
"removing_cached": "In het cachegeheugen opgeslagen revisies verwijderen...",
|
||||
"resolving_metadata": "Repo-metagegevens oplossen",
|
||||
"retry_attempt": "Nieuwe poging {{attempt}} — {{error}}",
|
||||
"connecting_hf": "Verbinding maken met HuggingFace…",
|
||||
"resolving_files_one": "{{count}} bestand oplossen…",
|
||||
"resolving_files_other": "{{count}} bestanden oplossen…",
|
||||
"resolving_files_active": "{{count}} bestand(en) oplossen… · {{file}}",
|
||||
"measuring": "meten…",
|
||||
"files_progress": "{{done}}/{{total}} bestanden",
|
||||
"install_error": "Installatie mislukt: {{error}}",
|
||||
"view_on_hf": "Bekijk op KnuffelFace",
|
||||
"install_btn": "Installeren",
|
||||
"reinstall_btn": "Opnieuw installeren",
|
||||
"downloading": "downloaden",
|
||||
"deleting": "verwijderen",
|
||||
"working": "werken",
|
||||
"installed": "geïnstalleerd",
|
||||
"not_installed": "niet geïnstalleerd",
|
||||
"required_tag": "vereist",
|
||||
"delete_btn": "Verwijderen",
|
||||
"hf_token_btn": "HF-token",
|
||||
"hf_set_title": "Stel HuggingFace-token in voor snellere downloads",
|
||||
"get_token": "Token verkrijgen →",
|
||||
"reco_installed_for": "Aanbevolen bundel geïnstalleerd voor {{device}}",
|
||||
"reco_for": "Aanbevolen voor {{device}}",
|
||||
"starting": "Beginnen…",
|
||||
"required_size": "Vereist ~{{size}} GB",
|
||||
"all_size": "Alles ~{{size}} GB",
|
||||
"req_tag": "aanvr",
|
||||
"search_placeholder": "Modellen zoeken…",
|
||||
"search_label": "Zoek modellen",
|
||||
"no_matches": "Er zijn geen modellen die overeenkomen met uw filters.",
|
||||
"sort_by": "Sorteren op {{column}}",
|
||||
"column_model": "Model",
|
||||
"column_role": "Rol",
|
||||
"column_size": "Grootte",
|
||||
"column_status": "Status",
|
||||
"ready_badge": "Klaar",
|
||||
"loading_badge": "Laden…",
|
||||
"idle_badge": "Inactief",
|
||||
"started_downloading_required_one": "Begonnen met het downloaden van het vereiste model {{count}}",
|
||||
"started_downloading_required_other": "Begonnen met het downloaden van {{count}} vereiste modellen"
|
||||
},
|
||||
"enterprise_faq": {
|
||||
"q_internal_tools": "Heb ik een licentie nodig voor interne tools?",
|
||||
"a_internal_tools": "Intern gebruik door uw werknemers en contractanten is een toegestaan doel onder de FSL – er is geen licentie vereist. Een commerciële licentie is nodig wanneer u OmniVoice beschikbaar stelt aan anderen als onderdeel van een concurrerend product of dienst (wederverkoop, gehoste SaaS, white-label).",
|
||||
"q_try_before": "Kan ik het proberen voordat ik me vastleg?",
|
||||
"a_try_before": "Ja. De volledige app kan gratis worden gedownload en lokaal worden uitgevoerd voor evaluatie onder de FSL. Wanneer u klaar bent om een commerciële implementatie te bespreken, kunt u ons een e-mail sturen, zodat we samen de details kunnen doornemen.",
|
||||
"q_watermark": "Hoe zit het met het watermerk?",
|
||||
"a_watermark": "Het onzichtbare AudioSeal-watermerk is standaard ingesloten. Commerciële licentiehouders kunnen dit uitschakelen via Instellingen → Privacy. Bij gratis/persoonlijk gebruik is altijd het watermerk inbegrepen.",
|
||||
"q_apache": "Wordt de bron ooit Apache 2.0?",
|
||||
"a_apache": "Ja. Elke release wordt automatisch geconverteerd naar de Apache-licentie, versie 2.0, op de tweede verjaardag van de publicatie ervan. Dat betekent dat de release van vandaag over twee jaar Apache 2.0 is, zonder dat er actie van ons nodig is; de FSL garandeert dit onherroepelijk."
|
||||
},
|
||||
"gallery_extra": {
|
||||
"save_prompt": "Voer een naam in voor dit stemprofiel:",
|
||||
"saved_as_profile": "Stem opgeslagen als profiel!",
|
||||
"save_profile_failed": "Kan profiel niet opslaan",
|
||||
"download_failed": "Downloaden mislukt: {{message}}",
|
||||
"trim_load_failed": "Kan audio niet laden voor bijsnijden: {{message}}",
|
||||
"upload_crop_failed": "Kan bijgesneden stem niet uploaden: {{message}}"
|
||||
},
|
||||
"dub_workflow": {
|
||||
"preparing_audio": "Audio voorbereiden…",
|
||||
"preparing_video": "Video voorbereiden…",
|
||||
"extracting_audio_scenes": "Audio en scènes extraheren…",
|
||||
"transcribing_audio": "Audio transcriberen…",
|
||||
"transcription_complete": "Transcriptie voltooid",
|
||||
"upload_cancelled": "Uploaden geannuleerd",
|
||||
"upload_failed": "Uploaden mislukt: {{message}}",
|
||||
"downloading_video": "Video downloaden…",
|
||||
"ingested": "Ingenomen {{url}}",
|
||||
"ingest_cancelled": "Inname geannuleerd",
|
||||
"ingest_failed": "URL-opname mislukt: {{message}}",
|
||||
"retry_cancelled": "Nieuwe poging geannuleerd",
|
||||
"transcription_failed": "Transcriptie mislukt: {{message}}",
|
||||
"import_srt_no_job": "Upload of neem eerst een video op - er is geen taak om ondertitels aan toe te voegen.",
|
||||
"imported_cues": "{{count}} cue(s) geïmporteerd uit {{file}}",
|
||||
"skipped_malformed": "{{count}} overgeslagen (misvormd)",
|
||||
"dropped_overlap": "{{count}} weggevallen (overlap)",
|
||||
"clamped_to_duration": "{{count}} geklemd op medialengte",
|
||||
"srt_import_failed": "SRT-import mislukt",
|
||||
"cleaned_one": "{{count}} fragment schoongemaakt",
|
||||
"cleaned_other": "{{count}} fragmenten schoongemaakt",
|
||||
"segments_clean": "Segmenten zijn al schoon",
|
||||
"cleanup_failed": "Opschonen mislukt: {{message}}",
|
||||
"cinematic_no_llm": "Filmische kwaliteit heeft een LLM nodig: stel TRANSLATE_BASE_URL + TRANSLATE_API_KEY in (Ollama werkt lokaal). Terugvallend op Snel.",
|
||||
"translate_errors": "{{errorCount}}/{{totalCount}} segment(en) mislukt: {{firstError}}",
|
||||
"translated_segments": "Vertaald {{count}} segment(en) → {{lang}}",
|
||||
"translated_cinematic_suffix": "(filmisch)",
|
||||
"translation_failed": "Vertaling mislukt: {{message}}",
|
||||
"regenerating": "{{count}} segment(en) opnieuw genereren…",
|
||||
"generating_dub": "Dub genereren…",
|
||||
"generating_progress": "Dub genereren… {{current}}/{{total}}",
|
||||
"generation_aborted": "Generatie afgebroken.",
|
||||
"dubbing_aborted": "Het kopiëren is afgebroken",
|
||||
"generation_stream_ended": "De generatiestroom is vóór voltooiing beëindigd",
|
||||
"dub_complete": "Dubben voltooid",
|
||||
"stop_failed": "Het is niet gelukt om te stoppen",
|
||||
"save_first": "Klik eerst op 'Uploaden en transcriberen', zodat de video op de server wordt verwerkt voordat deze wordt opgeslagen.",
|
||||
"project_saved": "Project opgeslagen",
|
||||
"project_created": "Project gemaakt",
|
||||
"save_failed": "Opslaan mislukt: {{message}}",
|
||||
"opened_project": "Geopend: {{name}}",
|
||||
"delete_project_confirm": "Dit project verwijderen? Dit kan niet ongedaan worden gemaakt.",
|
||||
"project_deleted": "Project verwijderd",
|
||||
"delete_history_confirm": "Dit geschiedenisitem verwijderen?",
|
||||
"history_deleted": "Geschiedenisitem verwijderd",
|
||||
"restored_state": "Herstelde staat van de vorige generatie",
|
||||
"upgrading_preview": "{{count}} segment(en) van voorbeeldkwaliteit upgraden naar volledige kwaliteit…"
|
||||
},
|
||||
"tts_errors": {
|
||||
"enter_text": "Voer tekst in",
|
||||
"upload_or_select": "Upload een audio of selecteer een stemprofiel",
|
||||
"trim_hint": "Audio is {{duration}}s — trim tot ≤{{max}}s voor de beste kloonfunctie",
|
||||
"timeout": "Er is een time-out voor het genereren opgetreden. Mogelijk wordt het model nog gedownload. Controleer Instellingen → Logboeken en probeer het opnieuw.",
|
||||
"error_prefix": "Fout: {{message}}",
|
||||
"ignored_unsupported": "Genegeerde niet-ondersteunde instructie: {{items}}",
|
||||
"ignored_duplicate": "Genegeerd (categorie al ingesteld): {{items}}"
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Delen en externe toegang",
|
||||
"help": "Stel dit actieve OmniVoice-exemplaar bloot aan uw andere machines zonder het opnieuw op te starten. Alleen loopback is de standaardinstelling: er wordt niets gedeeld totdat u dit hier inschakelt.",
|
||||
"local_network": "Lokaal netwerk",
|
||||
"local_help": "Deel via uw Wi-Fi/Ethernet met een eenmalige toegangspincode. Andere apparaten scannen de QR-code of openen de link.",
|
||||
"ports_title": "Poorten",
|
||||
"ports_help": "Deze worden ingesteld via omgevingsvariabelen die bij het opstarten worden gelezen. Wijzig de backend of UI-poort door de variabele in te stellen en OmniVoice opnieuw te starten.",
|
||||
"backend_port": "Backend-poort",
|
||||
"ui_port": "UI-poort",
|
||||
"lan_share_port": "LAN-share-poort",
|
||||
"port_error": "Voer een poort in tussen 1024 en 65535",
|
||||
"port_saved": "LAN-share-poort opgeslagen: geldt de volgende keer dat u delen inschakelt",
|
||||
"port_save_failed": "Kan poort niet opslaan: {{message}}",
|
||||
"saving": "Opslaan…",
|
||||
"ports_note": "Backend- en UI-poorten zijn van toepassing bij opnieuw opstarten. De LAN-share-poort is van toepassing de volgende keer dat u delen inschakelt.",
|
||||
"tailscale_title": "Tailscale (privé toegang op afstand)",
|
||||
"tailscale_checking": "Controleren op staartschaal...",
|
||||
"tailscale_absent": "Staartschaal niet gedetecteerd. Installeer het om OmniVoice veilig te bereiken vanaf elke plek op uw privé-staartnet.",
|
||||
"tailscale_install": "Installeer Tailscale",
|
||||
"tailscale_running": "Tailscale loopt. Bedien OmniVoice via uw privé staartnet.",
|
||||
"tailscale_not_logged_in": "Tailscale is geïnstalleerd maar niet ingelogd. Start en log eerst in bij Tailscale.",
|
||||
"tailscale_enabled": "Tailscale-service ingeschakeld",
|
||||
"tailscale_enable_failed": "Kan Tailscale niet inschakelen",
|
||||
"tailscale_enable_error": "Kon Tailscale niet inschakelen: {{message}}",
|
||||
"tailscale_disabled": "Tailscale-service uitgeschakeld",
|
||||
"tailscale_disable_failed": "Kan Tailscale niet uitschakelen",
|
||||
"tailscale_disable_error": "Kan Tailscale: {{message}} niet uitschakelen",
|
||||
"tailscale_enabling": "Inschakelen…",
|
||||
"tailscale_enable_btn": "Schakel Tailscale-service in",
|
||||
"tailscale_disabling": "Uitschakelen…",
|
||||
"tailscale_disable_btn": "Stop Tailscale-service",
|
||||
"tailscale_copy": "Kopieer link",
|
||||
"tailscale_open": "Openen in browser",
|
||||
"copied": "Gekopieerd",
|
||||
"tailscale_qr_alt": "QR-code voor de Tailscale-URL"
|
||||
},
|
||||
"reportBug": {
|
||||
"label": "Rapporteer een bug",
|
||||
"title": "Opent een vooraf ingevulde GitHub Issues-pagina in uw browser. Er wordt niets verzonden totdat u op Verzenden klikt."
|
||||
},
|
||||
"app": {
|
||||
"loading": "Laden…",
|
||||
"trimmed_loaded": "Bijgesneden audio geladen",
|
||||
"toast_exported": "Geëxporteerd: {{name}}",
|
||||
"toast_export_failed": "Exporteren mislukt: {{message}}",
|
||||
"toast_open_folder_failed": "Kan map niet openen: {{message}}",
|
||||
"toast_saving": "{{name}} opslaan...",
|
||||
"toast_saved": "Opgeslagen: {{path}}",
|
||||
"toast_save_error": "Fout bij opslaan: {{message}}",
|
||||
"toast_processing": "Verwerking {{name}}...",
|
||||
"toast_downloaded": "Gedownload {{name}}",
|
||||
"toast_download_error": "Downloadfout: {{message}}",
|
||||
"toast_upload_first": "Klik eerst op 'Uploaden en transcriberen', zodat de video op de server wordt verwerkt voordat deze wordt opgeslagen.",
|
||||
"toast_save_failed": "Opslaan mislukt: {{message}}",
|
||||
"toast_opened": "Geopend: {{name}}",
|
||||
"toast_project_deleted": "Project verwijderd",
|
||||
"toast_restored_state": "Herstelde staat van de vorige generatie",
|
||||
"toast_history_deleted": "Geschiedenisitem verwijderd",
|
||||
"toast_flushed": "Gespoeld — RAM {{ram}}G · VRAM {{vram}}G{{unloaded}}",
|
||||
"toast_model_unloaded": "· model onbeladen",
|
||||
"toast_flush_failed": "Spoelen mislukt: {{message}}",
|
||||
"toast_project_saved": "Project opgeslagen",
|
||||
"toast_project_created": "Project gemaakt"
|
||||
},
|
||||
"update": {
|
||||
"available": "Update {{version}} beschikbaar",
|
||||
"install": "Installeren en opnieuw opstarten",
|
||||
"install_hint": "Download de update en start opnieuw op in de nieuwe versie",
|
||||
"downloading": "Bijwerken… {{pct}}%",
|
||||
"restart": "Opnieuw opstarten om bij te werken",
|
||||
"busy": "Voltooi eerst je nasynchronisatie — installeer daarna de update.",
|
||||
"whats_new": "Wat is er nieuw",
|
||||
"failed": "Update mislukt",
|
||||
"retry": "Opnieuw proberen",
|
||||
"dismiss": "Negeren"
|
||||
},
|
||||
"archetypes": {
|
||||
"featured": "Uitgelicht",
|
||||
@@ -945,5 +1577,25 @@
|
||||
"facet_accent": "Accent",
|
||||
"facet_lang": "Taal",
|
||||
"facet_whisper": "Fluister"
|
||||
},
|
||||
"support": {
|
||||
"tab_support": "Ondersteuning",
|
||||
"tab_license": "Commerciële licentie",
|
||||
"toggle_label": "Ondersteuning of commerciële licentie",
|
||||
"other_ways": "Andere manieren om te helpen",
|
||||
"star_github": "Ster op GitHub",
|
||||
"join_discord": "Sluit je aan bij Discord"
|
||||
},
|
||||
"updates": {
|
||||
"tab": "Updates",
|
||||
"up_to_date": "Up-to-date · v{{version}}",
|
||||
"check_now": "Controleer nu",
|
||||
"releases": "Releases",
|
||||
"current": "actueel",
|
||||
"prerelease": "voorbeeld",
|
||||
"loading": "Releases laden…",
|
||||
"none": "Geen releases gevonden",
|
||||
"load_error": "Kan releases niet laden (offline?)",
|
||||
"retry_load": "Opnieuw proberen"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,4 @@
|
||||
{
|
||||
"update": {
|
||||
"available": "Dostępna aktualizacja {{version}}",
|
||||
"install": "Zainstaluj i uruchom ponownie",
|
||||
"install_hint": "Pobierz aktualizację i uruchom ponownie w nowej wersji",
|
||||
"downloading": "Aktualizowanie… {{pct}}%",
|
||||
"restart": "Uruchom ponownie, aby zaktualizować",
|
||||
"busy": "Najpierw dokończ dubbing — potem zainstaluj aktualizację."
|
||||
},
|
||||
"nav": {
|
||||
"clone": "Klonuj",
|
||||
"design": "Projektuj",
|
||||
@@ -16,7 +8,10 @@
|
||||
"launchpad": "Launchpad",
|
||||
"gallery": "Galeria",
|
||||
"transcripts": "Transkrypcje",
|
||||
"omnidrive": "OmniDrive"
|
||||
"omnidrive": "OmniDrive",
|
||||
"move_rail_right": "Przesuń szynę w prawo",
|
||||
"move_rail_left": "Przesuń szynę w lewo",
|
||||
"flip_rail": "Odwróć stronę szyny"
|
||||
},
|
||||
"settings": {
|
||||
"general": "Ogólne",
|
||||
@@ -45,7 +40,40 @@
|
||||
"ffmpeg_missing": "Nie znaleziono",
|
||||
"ffmpeg_current": "Aktualna ścieżka",
|
||||
"ffmpeg_desc": "Ustaw niestandardową ścieżkę ffmpeg, jeśli automatyczne wykrywanie nie powiedzie się.",
|
||||
"ffmpeg_saved": "Zestaw ścieżki FFmpeg — uruchom ponownie backend, aby zastosować."
|
||||
"ffmpeg_saved": "Zestaw ścieżki FFmpeg — uruchom ponownie backend, aby zastosować.",
|
||||
"diagnostics_copied": "Diagnostyka skopiowana — wklej do raportu o problemie.",
|
||||
"updater_desktop": "Updater działa tylko w aplikacji komputerowej.",
|
||||
"latest_version": "Korzystasz z najnowszej wersji.",
|
||||
"logs_load_failed": "Nie udało się załadować dzienników: {{message}}",
|
||||
"clear_frontend_confirm": "Wyczyścić bufor dziennika w pamięci?",
|
||||
"clear_frontend_title": "Wyczyść logi",
|
||||
"frontend_logs_cleared": "Dzienniki frontendu zostały wyczyszczone",
|
||||
"clear_tauri_confirm": "Obciąć pliki dziennika po stronie Tauri? System operacyjny będzie nadal zapisywać nowe wpisy.",
|
||||
"clear_tauri_title": "Wyczyść dzienniki Tauri",
|
||||
"nothing_to_clear": "Nie ma nic do wyczyszczenia — nie ma jeszcze pliku dziennika Tauri na dysku.",
|
||||
"cleared_tauri_one": "Wyczyszczono plik dziennika {{count}} Tauri",
|
||||
"cleared_tauri_other": "Wyczyszczono pliki dziennika {{count}} Tauri",
|
||||
"clear_tauri_failed": "Nie udało się wyczyścić dzienników Tauri: {{message}}",
|
||||
"clear_backend_confirm": "Wyczyścić środowisko wykonawcze backendu i dzienniki awarii? Tego nie można cofnąć.",
|
||||
"clear_backend_title": "Wyczyść logi",
|
||||
"backend_logs_cleared": "Dzienniki backendu zostały wyczyszczone",
|
||||
"clear_backend_failed": "Nie udało się wyczyścić dzienników",
|
||||
"copy_failed": "Kopiowanie nie powiodło się: {{message}}",
|
||||
"update_check_failed": "Sprawdzanie aktualizacji nie powiodło się: {{message}}",
|
||||
"save_failed": "Zapis nie powiódł się: {{message}}",
|
||||
"clear_failed": "Wyczyszczenie nie powiodło się: {{message}}",
|
||||
"engine_switched": "{{family}} → {{engine}}",
|
||||
"channel_set_failed": "Nie udało się ustawić kanału: {{message}}",
|
||||
"updater_downloading": "Pobieranie {{version}}…",
|
||||
"updater_installed": "Zainstalowano — uruchamiam ponownie.",
|
||||
"updater_available_title": "Dostępna aktualizacja",
|
||||
"updater_available_body": "Dostępna jest wersja {{version}}.\n\n{{notes}}\n\nPobrać i zainstalować teraz?",
|
||||
"updater_notes_fallback": "Zobacz informacje o wersji w serwisie GitHub.",
|
||||
"shortcut_load_failed": "Nie można załadować skrótu: {{message}}",
|
||||
"shortcut_set": "Skrót do dyktowania ustawiony na {{shortcut}}",
|
||||
"shortcut_register_failed": "Nie udało się zarejestrować: {{message}}",
|
||||
"shortcut_reset": "Przywróć ustawienia domyślne",
|
||||
"shortcut_reset_failed": "Resetowanie nie powiodło się: {{message}}"
|
||||
},
|
||||
"bootstrap": {
|
||||
"title": "OmniVoice Studio",
|
||||
@@ -69,7 +97,13 @@
|
||||
"waiting_output": "Oczekiwanie na wyjście…",
|
||||
"auto_detect": "Wykryj automatycznie",
|
||||
"suggest_lang": "Przełączyć na język Polski?",
|
||||
"select_lang": "Język:"
|
||||
"select_lang": "Język:",
|
||||
"region_global": "Globalny (bezpośredni)",
|
||||
"region_china": "Chiny (lustro)",
|
||||
"region_russia": "Rosja (lustro)",
|
||||
"region_restricted": "Ograniczone (lustro)",
|
||||
"unknown_error": "Nieznany błąd",
|
||||
"retrying": "Ponawiam próbę…"
|
||||
},
|
||||
"stories": {
|
||||
"title": "Redaktor opowiadań",
|
||||
@@ -158,7 +192,17 @@
|
||||
"reload": "Wymuś ponowne załadowanie interfejsu użytkownika",
|
||||
"backend": "Zaplecze",
|
||||
"frontend": "Frontend",
|
||||
"tauri": "Tauri"
|
||||
"tauri": "Tauri",
|
||||
"cancelOp": "Anuluj operację",
|
||||
"dismiss": "Odrzuć",
|
||||
"dismissStatus": "Odrzuć status",
|
||||
"search": "Szukaj…",
|
||||
"no_matches": "Brak dopasowań",
|
||||
"recent_and_popular": "Najnowsze i popularne",
|
||||
"popular_label": "Popularne",
|
||||
"showing_of": "Wyświetlanie {{shown}} z {{total}}. Wpisz, aby wyszukać…",
|
||||
"yes": "Tak",
|
||||
"no": "Nie"
|
||||
},
|
||||
"launchpad": {
|
||||
"greeting": "cześć",
|
||||
@@ -180,7 +224,16 @@
|
||||
"locked": "ZABLOKOWANE",
|
||||
"open": "Otwórz",
|
||||
"try_it": "Spróbuj",
|
||||
"audio_only": "Tylko dźwięk"
|
||||
"audio_only": "Tylko dźwięk",
|
||||
"stories_title": "Historie",
|
||||
"stories_desc": "Audiobooki z wieloma głosami — rzucaj postaciami, wstawiaj scenariusz, eksportuj według rozdziałów.",
|
||||
"gallery_title": "Galeria Głosów",
|
||||
"gallery_desc": "Przeglądaj gotowe, zaprojektowane głosy według akcentu, wieku i stylu — bez konieczności konfiguracji.",
|
||||
"transcripts_title": "Transkrypcje",
|
||||
"transcripts_desc": "Zamień dźwięk lub wideo w edytowalny i przeszukiwalny tekst — w 646 językach.",
|
||||
"recent_files": "Najnowsze pliki",
|
||||
"view_all_files": "Wyświetl wszystkie pliki",
|
||||
"file": "Plik"
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "Podpowiedź",
|
||||
@@ -271,11 +324,6 @@
|
||||
"outputs": "Wyjścia",
|
||||
"crash_log": "Dziennik awarii",
|
||||
"update_endpoint": "Zaktualizuj punkt końcowy",
|
||||
"update_channel": "Kanał aktualizacji",
|
||||
"channel_stable": "Stabilny",
|
||||
"channel_preview": "Podgląd",
|
||||
"channel_set": "Kanał aktualizacji ustawiony na {{channel}}",
|
||||
"channel_preview_hint": "Podgląd śledzi najnowszą kompilację main — nowsze funkcje, mniej testów. Wraca do stabilnej, jeśli stabilne wydanie jest nowsze.",
|
||||
"yes": "tak",
|
||||
"no": "nie",
|
||||
"web_preview": "podgląd internetowy",
|
||||
@@ -284,7 +332,12 @@
|
||||
"copy_diagnostics": "Kopiuj diagnostykę",
|
||||
"github": "OmniVoice na GitHubie",
|
||||
"model_card": "Karta modelowa",
|
||||
"commercial_license": "Licencja komercyjna"
|
||||
"commercial_license": "Licencja komercyjna",
|
||||
"update_channel": "Kanał aktualizacji",
|
||||
"channel_stable": "Stabilny",
|
||||
"channel_preview": "Podgląd",
|
||||
"channel_set": "Kanał aktualizacji ustawiony na {{channel}}",
|
||||
"channel_preview_hint": "Podgląd śledzi najnowszą kompilację main — nowsze funkcje, mniej testów. Wraca do stabilnej, jeśli stabilne wydanie jest nowsze."
|
||||
},
|
||||
"privacy": {
|
||||
"desc": "Wszystko działa na <1>tej maszynie</1>. Twoje nagrania audio, wideo i transkrypcje nigdy nie opuszczają Twojego komputera, chyba że wyraźnie skorzystasz z tłumacza online (Google, DeepL itp.) lub przekażesz je do HuggingFace.",
|
||||
@@ -340,7 +393,30 @@
|
||||
"unavailable": "niedostępne",
|
||||
"use": "Użyj",
|
||||
"loading": "Ładowanie silników…",
|
||||
"refresh": "Odśwież"
|
||||
"refresh": "Odśwież",
|
||||
"matrixTitle": "Matryca kompatybilności silników",
|
||||
"loadFailed": "Nie udało się załadować silników: {{message}}",
|
||||
"couldNotLoad": "Nie można załadować silników: {{message}}",
|
||||
"retry": "Spróbuj ponownie",
|
||||
"activeEngine": "Aktywny {{family}}: {{engine}}",
|
||||
"engineCompatLabel": "{{family}} kompatybilność silnika",
|
||||
"active": "aktywny",
|
||||
"whyUnavailable": "Dlaczego niedostępne?",
|
||||
"lastError": "Ostatni błąd: {{error}}",
|
||||
"installedAndReady": "Zainstalowany i gotowy",
|
||||
"notInstalled": "Nie zainstalowano",
|
||||
"available": "Dostępne",
|
||||
"subprocessTitle": "Działa we własnym podprocesie + venv",
|
||||
"inProcessTitle": "Działa w procesie OmniVoice Python",
|
||||
"testEngine": "Silnik testowy",
|
||||
"testing": "Testowanie…",
|
||||
"recheck": "Sprawdź ponownie",
|
||||
"rechecking": "Ponowne sprawdzanie…",
|
||||
"latencyMs": "{{ms}} ms",
|
||||
"failed": "nie powiodło się",
|
||||
"acceptLicense": "Zaakceptuj licencję",
|
||||
"noBackends": "Brak zarejestrowanych backendów.",
|
||||
"switch_failed": "Nie udało się przełączyć silnika"
|
||||
},
|
||||
"capture": {
|
||||
"desc": "Globalne skróty klawiszowe działają tylko w aplikacji komputerowej. Interfejs WWW korzysta ze skrótu <1>Ctrl+Shift+Spacja</1> znajdującego się na stronie, gdy okno jest aktywne.",
|
||||
@@ -352,13 +428,55 @@
|
||||
"record_shortcut": "Nagraj skrót",
|
||||
"recording": "Nagrywanie…",
|
||||
"save": "Zapisz",
|
||||
"reset_default": "Przywróć ustawienia domyślne"
|
||||
"reset_default": "Przywróć ustawienia domyślne",
|
||||
"listening_label": "Słucham…",
|
||||
"transcribing_label": "Transkrypcja…",
|
||||
"pasted": "Wklejony",
|
||||
"no_speech": "Nie wykryto mowy",
|
||||
"mic_denied": "Odmowa dostępu do mikrofonu",
|
||||
"mic_denied_toast": "Odmowa dostępu do mikrofonu. {{hint}}",
|
||||
"mic_hint_mac": "macOS: otwórz Ustawienia systemowe → Prywatność i bezpieczeństwo → Mikrofon i włącz OmniVoice.",
|
||||
"mic_hint_windows": "Windows: otwórz Ustawienia → Prywatność i bezpieczeństwo → Mikrofon i zezwól na OmniVoice.",
|
||||
"mic_hint_linux": "Linux: sprawdź, czy użytkownik należy do grupy audio i czy WebView ma dostęp do mikrofonu.",
|
||||
"transcription_failed": "Transkrypcja nie powiodła się: {{message}}"
|
||||
},
|
||||
"logs": {
|
||||
"no_tauri_log": "Brak jeszcze logowania Tauri na dysku — uruchom go za pomocą wersji desktopowej, aby go utworzyć",
|
||||
"empty_frontend": "Nie przechwycono jeszcze żadnych wpisów konsoli frontonu. Wejdź w interakcję z aplikacją — tutaj pojawią się wszystkie konsole.*.",
|
||||
"empty_tauri": "Brak dostępnego dziennika Tauri. Działa tylko w powłoce pulpitu.",
|
||||
"empty_backend": "Dziennik wykonawczy jest pusty. Aktywność pojawi się tutaj, gdy backend ją zarejestruje."
|
||||
"empty_backend": "Dziennik wykonawczy jest pusty. Aktywność pojawi się tutaj, gdy backend ją zarejestruje.",
|
||||
"title": "Dzienniki",
|
||||
"source_backend": "Zaplecze",
|
||||
"source_frontend": "Frontend",
|
||||
"source_tauri": "Tauri",
|
||||
"expand": "Rozwiń logi",
|
||||
"collapse": "Zwiń dzienniki",
|
||||
"expand_aria": "Rozwiń panel dzienników",
|
||||
"collapse_aria": "Zwiń panel dzienników",
|
||||
"drag_resize": "Przeciągnij, aby zmienić rozmiar",
|
||||
"refresh": "Odśwież",
|
||||
"refresh_aria": "Odśwież logi",
|
||||
"copy_visible": "Skopiuj widoczny dziennik",
|
||||
"copy_visible_aria": "Skopiuj widoczny dziennik",
|
||||
"clear": "Jasne",
|
||||
"clear_aria": "Wyczyść dziennik",
|
||||
"report_issue": "Zgłoś problem (kopiuj diagnostykę)",
|
||||
"report_issue_aria": "Zgłoś problem",
|
||||
"close": "Zamknij",
|
||||
"close_aria": "Zamknij panel dzienników",
|
||||
"join_discord": "Dołącz do naszego Discorda",
|
||||
"join_discord_aria": "Dołącz do naszej społeczności Discord",
|
||||
"support_project": "Wesprzyj ten projekt",
|
||||
"support_project_aria": "Wesprzyj ten projekt",
|
||||
"empty_frontend_short": "Nie ma jeszcze danych wyjściowych konsoli frontendowej.",
|
||||
"empty_lines": "Brak linii.",
|
||||
"all_clear": "✅ Wszystko jasne — nie wykryto żadnych problemów",
|
||||
"log_cleared": "{{source}} dziennik został usunięty",
|
||||
"clear_failed": "Wyczyszczenie nie powiodło się: {{message}}",
|
||||
"log_copied": "Skopiowano dziennik {{source}}",
|
||||
"copy_failed": "Kopiowanie nie powiodło się: {{message}}",
|
||||
"report_copied": "Raport diagnostyczny skopiowany — wklej go do problemu w GitHubie.",
|
||||
"report_failed": "Raport nie powiódł się: {{message}}"
|
||||
},
|
||||
"voice_profile": {
|
||||
"test_text": "Witamy — to jest test tego głosu.",
|
||||
@@ -526,7 +644,17 @@
|
||||
"install_already": "{{engine}} został już zainstalowany",
|
||||
"install_ok": "{{engine}} zainstalowany",
|
||||
"install_failed": "Instalacja nie powiodła się: {{message}}",
|
||||
"prep_elapsed": "upłynął {{time}}"
|
||||
"prep_elapsed": "upłynął {{time}}",
|
||||
"add_language": "Dodaj język",
|
||||
"search_languages": "Wyszukaj języki…",
|
||||
"languages_selected_one": "Wybrano {{count}} język",
|
||||
"languages_selected_other": "Wybrano {{count}} języki",
|
||||
"more_to_narrow": "+{{count}} więcej — wpisz, aby zawęzić",
|
||||
"no_matches": "Brak dopasowań",
|
||||
"diagnostic_copied": "Diagnostyka skopiowana",
|
||||
"copy_failed": "Kopiowanie nie powiodło się",
|
||||
"open_docs": "Otwórz dokumenty",
|
||||
"copy_diagnostic": "Kopiuj diagnostykę"
|
||||
},
|
||||
"glossary": {
|
||||
"title": "Glosariusz",
|
||||
@@ -601,7 +729,17 @@
|
||||
"more_actions_title": "Więcej akcji",
|
||||
"speaker_pick": "Wybierz…",
|
||||
"speaker_title_detected": "Głośnik — wybierz spośród wykrytych lub wpisz własną nazwę",
|
||||
"speaker_title_custom": "Głośnik — wpisz nazwę (nie wykryto klonów diaryzacji)"
|
||||
"speaker_title_custom": "Głośnik — wpisz nazwę (nie wykryto klonów diaryzacji)",
|
||||
"time_edit_title": "Kliknij, aby edytować czas rozpoczęcia (m:ss.s). Enter, aby zatwierdzić, Esc, aby anulować.",
|
||||
"fit_fits": "Pasuje",
|
||||
"fit_fits_title": "Naturalny dźwięk mieści się w gnieździe.",
|
||||
"fit_overflows": "Przepełnienia +{{seconds}}s",
|
||||
"fit_overflows_title": "Przetłumaczony tekst był dłuższy niż oryginalny fragment o {{seconds}}s. Dźwięk został mocno przycięty; skróć tekst lub przełącz synchronizację na „Rozciągnij wideo”.",
|
||||
"fit_stretched": "Wideo {{ratio}}×",
|
||||
"fit_stretched_title": "Tryb rozciągania wideo: wideo tego segmentu zostało spowolnione do {{ratio}}×, aby dopasować je do naturalnego dźwięku dubowego.",
|
||||
"fit_compressed_title": "Dźwięk TTS stanowi {{pct}}% szczeliny — jest mocno skompresowany.",
|
||||
"fit_audio_title": "Dźwięk mieści się w gnieździe.",
|
||||
"fit_ratio_title": "Dźwięk TTS zajmuje {{pct}}% szczeliny."
|
||||
},
|
||||
"voice": {
|
||||
"personality": "Osobowość",
|
||||
@@ -696,10 +834,24 @@
|
||||
"status_running": "bieganie",
|
||||
"status_done": "zrobione",
|
||||
"status_failed": "nie powiodło się",
|
||||
"status_cancelled": "anulowane"
|
||||
"status_cancelled": "anulowane",
|
||||
"add_to_queue_title": "Dodaj filmy do kolejki",
|
||||
"drop_hint_text": "Upuść pliki wideo tutaj lub kliknij, aby przeglądać",
|
||||
"drop_formats": "MP4 · MOV · MKV · WEBM",
|
||||
"files_kicker": "PLIKI ({{count}})",
|
||||
"file_size_mb": "{{size}}MB",
|
||||
"target_languages": "JĘZYKI DOCELOWE",
|
||||
"voice_kicker": "GŁOS",
|
||||
"default_option": "Domyślne",
|
||||
"clone_profiles": "Profile klonowania",
|
||||
"presets": "Ustawienia wstępne",
|
||||
"preserve_bg": "Zachowaj dźwięk w tle (muzyka/FX)",
|
||||
"estimate": "{{videos}} filmy × {{langs}} języki = {{jobs}} zadania",
|
||||
"select_files_langs": "Wybierz pliki i języki",
|
||||
"add_to_queue": "Dodaj do kolejki"
|
||||
},
|
||||
"gallery": {
|
||||
"title": "Galeria",
|
||||
"title": "OmniVoice Galeria",
|
||||
"search_placeholder": "Wyszukaj w YouTube…",
|
||||
"all_voices": "Wszystkie głosy ({{count}})",
|
||||
"no_voices": "Nie ma jeszcze głosów",
|
||||
@@ -714,6 +866,14 @@
|
||||
"youtube_results": "Wyniki w YouTube ({{count}})",
|
||||
"clone_profile": "Klonuj profil",
|
||||
"crop_audio": "Przytnij dźwięk",
|
||||
"cat_disney": "Disneya",
|
||||
"cat_anime": "Anime",
|
||||
"cat_marvel": "Marvela/DC",
|
||||
"cat_celebs": "Gwiazdy",
|
||||
"cat_politicians": "Politycy",
|
||||
"cat_news": "Kotwice wiadomości",
|
||||
"cat_gaming": "Gry",
|
||||
"cat_books": "Książki/filmy",
|
||||
"subtitle": "Setki gotowych, zaprojektowanych głosów — wybierz jeden i gotowe.",
|
||||
"zone_archetypes": "Archetypy",
|
||||
"zone_imports": "Mój import",
|
||||
@@ -818,7 +978,6 @@
|
||||
"badge": "Licencja komercyjna",
|
||||
"hero_title": "Wysyłaj głosy AI w produkcji",
|
||||
"hero_desc": "OmniVoice Studio jest dostępne w formie źródłowej na podstawie licencji Functional Source License (FSL). Większość użytkowników może oceniać, tworzyć prototypy, a nawet wdrażać wewnętrznie bez umowy komercyjnej. Licencja komercyjna jest Ci potrzebna tylko wtedy, gdy budujesz konkurencyjny produkt lub usługę lub jeśli Twój przypadek użycia wykracza poza granice FSL.",
|
||||
"hero_note": "Tworzenie konkurencyjnego produktu lub usługi lub wdrażanie na dużą skalę (np. obsługa interfejsu API płatności za użycie) wymaga licencji komercyjnej. Poziomy cenowe już wkrótce — skontaktuj się z nami w międzyczasie.",
|
||||
"why_title": "Dlaczego firmy wybierają OmniVoice",
|
||||
"pricing_title": "Ceny",
|
||||
"faq_title": "Często zadawane pytania",
|
||||
@@ -838,7 +997,8 @@
|
||||
"benefit_source": "Rdzeń dostępny ze źródła",
|
||||
"benefit_source_desc": "Pełna widoczność stosu. Audytuj, rozwidlaj i dostosowuj zgodnie z warunkami licencji.",
|
||||
"benefit_lang": "646 języków",
|
||||
"benefit_lang_desc": "Transkrypcja, tłumaczenie i kopiowanie w 646 językach z jakością na poziomie ludzkim."
|
||||
"benefit_lang_desc": "Transkrypcja, tłumaczenie i kopiowanie w 646 językach z jakością na poziomie ludzkim.",
|
||||
"hero_note": "Do użytku wewnętrznego — nawet na dużą skalę — jest bezpłatne w ramach FSL; licencja komercyjna jest wymagana jedynie do oferowania OmniVoice innym jako konkurencyjny produkt lub usługa (interfejs API hostowany lub płatny za użycie, aplikacja odsprzedawana lub z białą etykietą). Poziomy cenowe już wkrótce — skontaktuj się z nami w międzyczasie."
|
||||
},
|
||||
"exportModal": {
|
||||
"export": "Eksportuj",
|
||||
@@ -927,7 +1087,479 @@
|
||||
"dubbing_title": "Zobacz dubbing w akcji",
|
||||
"dubbing_sync": "Zsynchronizowane odtwarzanie",
|
||||
"dubbing_picker": "Spróbuj innego języka:",
|
||||
"dubbing_cta": "Uruchom to na swoim własnym filmie →"
|
||||
"dubbing_cta": "Uruchom to na swoim własnym filmie →",
|
||||
"dubbing_loading": "Ładowanie wersji demonstracyjnej kopiowania…",
|
||||
"dubbing_dismiss": "Zamknij demonstrację dubbingu",
|
||||
"original_tag": "oryginał",
|
||||
"dubbed_tag": "dubbingowany",
|
||||
"script_conversational": "Konwersacyjny",
|
||||
"script_technical": "Słownictwo techniczne",
|
||||
"script_french": "Język inny niż angielski (francuski)",
|
||||
"aria_pause": "Wstrzymaj {{label}}",
|
||||
"aria_hear": "Usłysz {{label}}",
|
||||
"aria_replay": "Odtwórz ponownie {{label}} przez transkrypcję"
|
||||
},
|
||||
"direction": {
|
||||
"title": "Kierunek odcinka #{{id}}",
|
||||
"desc": "Powiedz rurociągowi, jak powinna wyglądać ta linia. Zwykły angielski działa — system mapuje słowa na stabilną taksonomię (energia / emocje / tempo / intymność / formalność), a następnie łączy taksonomię za pomocą tłumaczenia kinowego, TTS i dopasowania szczelinowego.",
|
||||
"label": "Kierunek",
|
||||
"lineHint": "Linia: „{{text}}”",
|
||||
"placeholder": "np. pilne i zaskoczone / ciepłe, pełne nadziei / szeptane, intymne",
|
||||
"previewParse": "Podgląd analizy",
|
||||
"previewFailed": "Podgląd nie powiódł się: {{message}}",
|
||||
"clear": "Jasne",
|
||||
"cancel": "Anuluj",
|
||||
"saveDirection": "Zapisz kierunek",
|
||||
"ttsInstruct": "Instrukcja TTS:",
|
||||
"nothingParsed": "— (nic nie zostało przeanalizowane)",
|
||||
"translateHint": "Przetłumacz wskazówkę:",
|
||||
"rateBias": "Odchylenie stawki:",
|
||||
"speedsUp": "przyspiesza",
|
||||
"slowsDown": "spowalnia",
|
||||
"taxonomyTokens": "żetony taksonomii"
|
||||
},
|
||||
"errors": {
|
||||
"title": "Ta zakładka napotkała przeszkodę.",
|
||||
"desc": "Nie martw się — reszta aplikacji nadal działa. Możesz przełączyć karty lub spróbować ponownie poniżej.",
|
||||
"tryAgain": "Spróbuj ponownie",
|
||||
"openDocs": "Otwórz dokumenty dotyczące tego błędu"
|
||||
},
|
||||
"keyboard": {
|
||||
"title": "Skróty klawiaturowe",
|
||||
"footer": "Naciśnij <1>?</1> w dowolnym momencie, aby to otworzyć.",
|
||||
"or": "lub",
|
||||
"nav": "Nawigacja",
|
||||
"nav_cheatsheet": "Pokaż tę ściągawkę",
|
||||
"nav_closeModal": "Zamknij modalne / anuluj",
|
||||
"nav_save": "Zapisz projekt / zatwierdź przycięcie",
|
||||
"segmentEditor": "Edytor segmentów",
|
||||
"seg_split": "Podziel segment przy kursorze",
|
||||
"seg_merge": "Połącz z następnym segmentem",
|
||||
"seg_undo": "Cofnij",
|
||||
"seg_redo": "Powtórz",
|
||||
"seg_click": "Akcja podstawowa",
|
||||
"seg_shiftClick": "Wybór zakresu",
|
||||
"trimmer": "Trymer audio",
|
||||
"trim_playPause": "Podgląd odtwarzania/pauzy",
|
||||
"trim_nudgeStart": "Naciśnij uchwyt startowy",
|
||||
"trim_nudgeEnd": "Popchnij uchwyt końcowy",
|
||||
"trim_fineNudge": "Świetne szturchnięcie",
|
||||
"trim_coarseNudge": "Grube szturchnięcie",
|
||||
"trim_zoomIn": "Powiększ / pomniejsz",
|
||||
"trim_fitAll": "Dopasuj wszystko / Dopasuj wybór",
|
||||
"trim_confirm": "Potwierdź przycięcie",
|
||||
"dub": "Dub",
|
||||
"dub_generate": "Wygeneruj dubbing",
|
||||
"dub_sidebar": "Przełącz pasek boczny"
|
||||
},
|
||||
"network": {
|
||||
"sharing_on_title": "Udostępnij dalej — kliknij, aby zobaczyć szczegóły",
|
||||
"share_on_network": "Udostępnij w swojej sieci",
|
||||
"switching": "Przełączanie…",
|
||||
"network": "Sieć",
|
||||
"local": "Lokalny",
|
||||
"share_confirm_title": "Udostępnić w swojej sieci?",
|
||||
"share_confirm_hint": "Inne urządzenia w Twojej sieci Wi-Fi/Ethernet będą mogły połączyć się z OmniVoice przy użyciu kodu PIN dostępu wyświetlanego po włączeniu.",
|
||||
"enabling": "Włączam…",
|
||||
"enable": "Włącz",
|
||||
"shared_title": "Udostępnione w Twojej sieci",
|
||||
"no_interface": "Brak osiągalnego interfejsu sieciowego — połącz się z siecią Wi-Fi/Ethernet.",
|
||||
"copy_link": "Skopiuj link",
|
||||
"open_in_browser": "Otwórz w przeglądarce",
|
||||
"qr_alt": "QR dla {{ip}}",
|
||||
"pin": "PIN:",
|
||||
"stop_sharing": "Przestań się dzielić",
|
||||
"copied": "Skopiowano",
|
||||
"enable_error": "Nie można włączyć udostępniania: {{message}}",
|
||||
"disable_error": "Nie można wyłączyć: {{message}}"
|
||||
},
|
||||
"readiness": {
|
||||
"checking_system": "Sprawdzanie systemu…",
|
||||
"all_ready": "Wszystkie systemy gotowe",
|
||||
"system_readiness": "Gotowość systemu",
|
||||
"asr_model": "Model ASR",
|
||||
"loaded_ready": "Załadowany i gotowy",
|
||||
"loading_first_run": "Ładowanie… (przy pierwszym uruchomieniu może to zająć 1–2 minuty)",
|
||||
"failed_to_load": "Nie udało się załadować",
|
||||
"not_loaded_yet": "Jeszcze nie załadowano — zostanie załadowane przy pierwszej transkrypcji",
|
||||
"error_check_logs": "Błąd: {{error}}. Sprawdź dzienniki i spróbuj uruchomić ponownie.",
|
||||
"check_logs_restart": "Sprawdź dzienniki pod kątem błędów ładowania modelu. Spróbuj uruchomić ponownie.",
|
||||
"llm_cinematic": "LLM (film)",
|
||||
"llm_configure": "Skonfiguruj TRANSLATE_BASE_URL, aby uzyskać jakość tłumaczenia kinowego",
|
||||
"llm_set_env": "Ustaw zmienne środowiskowe TRANSLATE_BASE_URL i TRANSLATE_API_KEY. Współpracuje z Ollamą, OpenAI, LM Studio itp.",
|
||||
"llm_optional": "Opcjonalnie — ustaw TRANSLATE_BASE_URL dla jakości kinowej"
|
||||
},
|
||||
"license": {
|
||||
"title": "Supertonic-3 — Akceptacja licencji",
|
||||
"intro": "Supertonic-3 statki na podstawie dwóch odrębnych licencji. Przed włączeniem silnika przejrzyj oba.",
|
||||
"sdk_heading": "Kod SDK · MIT",
|
||||
"sdk_desc": "Zestaw SDK do wnioskowania w języku Python (supertoniczny) jest objęty licencją MIT. Dozwolone wykorzystanie, w tym komercyjne.",
|
||||
"read_mit": "Przeczytaj licencję MIT →",
|
||||
"model_heading": "Masy modeli · OpenRAIL-M",
|
||||
"model_desc": "Obciążniki modelu Supertonic-3 są udostępniane na licencji OpenRAIL-M. Niniejsza licencja ogranicza użycie do celów innych niż złośliwe — zobacz powiązaną licencję, aby zapoznać się z pełnym zestawem ograniczeń związanych z użytkowaniem.",
|
||||
"read_openrail": "Przeczytaj licencję OpenRAIL-M →",
|
||||
"footer": "Kliknięcie przycisku Akceptuj rejestruje Twoją akceptację w lokalnych ustawieniach OmniVoice i włącza silnik. Twoja akceptacja jest przechowywana wyłącznie na tym urządzeniu — nic nie jest zgłaszane firmie Supertone Inc. ani żadnej stronie trzeciej.",
|
||||
"saving": "Zapisuję…",
|
||||
"accept": "Zaakceptuj",
|
||||
"accepted_toast": "Zaakceptowano licencję Supertonic-3.",
|
||||
"accept_error": "Nie udało się zarejestrować akceptacji licencji: {{message}}"
|
||||
},
|
||||
"voicePreview": {
|
||||
"title": "Podgląd głosu",
|
||||
"close": "Zamknij podgląd",
|
||||
"default_text": "Witam! To jest podgląd tego, jak brzmię tym głosem.",
|
||||
"default_voice": "Domyślny głos",
|
||||
"clone_profiles": "Profile klonowania",
|
||||
"designed_voices": "Zaprojektowane głosy",
|
||||
"presets": "Ustawienia wstępne",
|
||||
"placeholder": "Wpisz coś, co chcesz usłyszeć…",
|
||||
"stop": "Zatrzymaj się",
|
||||
"regenerate": "Zregeneruj się",
|
||||
"preview": "Podgląd",
|
||||
"hint": "8 kroków · szybki podgląd"
|
||||
},
|
||||
"header": {
|
||||
"kicker_studio": "Studio",
|
||||
"kicker_library": "Biblioteka",
|
||||
"kicker_preferences": "Preferencje",
|
||||
"kicker_licensing": "Licencjonowanie",
|
||||
"label_launchpad": "Launchpad",
|
||||
"label_clone": "Klon głosu",
|
||||
"label_design": "Projekt głosu",
|
||||
"label_dub": "Dubbingowanie",
|
||||
"label_projects": "OmniDrive",
|
||||
"label_gallery": "Galeria",
|
||||
"label_transcriptions": "Transkrypcje",
|
||||
"label_settings": "Ustawienia",
|
||||
"label_enterprise": "Licencja komercyjna",
|
||||
"status_ready": "Gotowy",
|
||||
"status_loading": "Ładowanie…",
|
||||
"status_idle": "Bezczynny",
|
||||
"memory_management": "Zarządzanie pamięcią",
|
||||
"flush": "Spłukać",
|
||||
"loaded_models": "Załadowane modele",
|
||||
"no_models": "Nie załadowano żadnych modeli",
|
||||
"unload": "Rozładuj",
|
||||
"flush_caches": "Opróżnij pamięci podręczne",
|
||||
"unload_all_flush": "Rozładuj wszystko + spłucz"
|
||||
},
|
||||
"sidebar": {
|
||||
"tab_drive": "Jedź",
|
||||
"tab_history": "Historia",
|
||||
"tab_exports": "Eksport",
|
||||
"save_project": "Zapisz projekt dubowy",
|
||||
"save_new_project": "Zapisz jako nowy projekt dubowy",
|
||||
"dub_projects": "Projekty dubowe",
|
||||
"voice_clones": "Klony głosowe",
|
||||
"designed_voices": "Zaprojektowane głosy",
|
||||
"no_dub_projects": "Brak zapisanych projektów dubowych",
|
||||
"no_dub_hint": "Prześlij film i kliknij Zapisz, aby zachować swoją pracę.",
|
||||
"no_clones": "Nie ma jeszcze klonów głosowych",
|
||||
"no_clones_hint": "Nagraj lub prześlij dźwięk, a następnie kliknij Zapisz jako profil głosowy.",
|
||||
"no_designs": "Nie ma jeszcze zaprojektowanych głosów",
|
||||
"no_designs_hint": "Wygeneruj głos i zapisz go w Historii.",
|
||||
"history_subtitle": "Historia generacji · Przechowywane w SQLite",
|
||||
"no_history": "Brak historii pokoleń",
|
||||
"no_history_hint": "Syntezuj dźwięk lub kopiuj wideo — wyniki pojawią się tutaj.",
|
||||
"clear_history": "Wyczyść historię",
|
||||
"clear_confirm": "Wyczyścić wszystkie {{count}} elementy historii? Tego nie można cofnąć.",
|
||||
"history_cleared": "Historia wyczyszczona",
|
||||
"recent_exports": "Najnowsze eksporty",
|
||||
"no_exports": "Brak pobranych wyników",
|
||||
"no_exports_hint": "Wyeksportuj plik przez Tauri, aby zobaczyć go tutaj śledzonego.",
|
||||
"show_in_folder": "Pokaż w folderze",
|
||||
"open": "Otwórz",
|
||||
"select": "Wybierz",
|
||||
"try_voice": "Spróbuj",
|
||||
"consistent": "spójne",
|
||||
"locked": "Zablokowane",
|
||||
"clone_label": "Klon",
|
||||
"design_label": "Projekt",
|
||||
"dub_label": "Dub",
|
||||
"save_label": "Zapisz",
|
||||
"lock_identity": "Zablokuj tożsamość głosową",
|
||||
"in_folder": "w {{folder}}"
|
||||
},
|
||||
"trimmer": {
|
||||
"title": "Przytnij dźwięk referencyjny",
|
||||
"decoding": "Dekodowanie dźwięku…",
|
||||
"meta_length": "Długość {{duration}} · {{sampleRate}} Hz",
|
||||
"meta_rendering": "renderowanie przebiegu {{percent}}%",
|
||||
"keyboard_hint": "scroll = powiększanie · shift+scroll = przesuwanie · alt+przeciąganie = przesuwanie · Klawisze ⏐ ⟵ ⟶ ⏐ dostosowują uchwyty",
|
||||
"zoom_in": "Powiększ (+)",
|
||||
"zoom_out": "Pomniejsz (-)",
|
||||
"fit_all": "Pasuje do wszystkich (Dom)",
|
||||
"fit_selection": "Wybór dopasowania (koniec)",
|
||||
"fit_sel_btn": "DOPASUJ WYB",
|
||||
"view_range": "Zobacz {{start}} → {{end}} ({{duration}})",
|
||||
"start_label": "Zacznij",
|
||||
"end_label": "Koniec",
|
||||
"length_label": "Długość",
|
||||
"too_long": ">{{max}}s",
|
||||
"too_short": "za krótki",
|
||||
"length_ok": "ok",
|
||||
"loop_preview": "Podgląd pętli",
|
||||
"pause": "Pauza",
|
||||
"preview_selection": "Podgląd wyboru",
|
||||
"play_hint": "Spacja do gry · Enter, aby potwierdzić · Esc, aby anulować",
|
||||
"cancel": "Anuluj",
|
||||
"use_trimmed": "Użyj przyciętego",
|
||||
"decode_failed": "Dekodowanie nie powiodło się: {{message}}",
|
||||
"playback_failed": "Odtwarzanie nie powiodło się: {{message}}",
|
||||
"audio_load_failed": "Wczytanie dźwięku nie powiodło się",
|
||||
"unit_seconds": "s"
|
||||
},
|
||||
"casting": {
|
||||
"title": "Casting głośników",
|
||||
"auto_assign_title": "Automatyczne przypisywanie głosów z wyodrębnionych klonów głośników",
|
||||
"auto_cast": "Automatyczne przesyłanie",
|
||||
"all_cast": "Wszystko w obsadzie",
|
||||
"segments_count": "{{count}} segmentów",
|
||||
"assign_voice": "Przypisz głos…",
|
||||
"from_video": "🎤 Z filmu ({{name}})",
|
||||
"no_profiles": "Nie zapisano jeszcze żadnych profili głosowych.",
|
||||
"preview_voice": "Podgląd głosu"
|
||||
},
|
||||
"checkpoint": {
|
||||
"asr_title": "Transkrypcje gotowe",
|
||||
"asr_cta": "Przetłumacz",
|
||||
"asr_hint": "Napraw teraz wszelkie błędy ASR — ścisła dykcja pozwala uniknąć późniejszych prób TTS.",
|
||||
"translate_title": "Tłumaczenia gotowe",
|
||||
"translate_cta": "Wygeneruj dubbing",
|
||||
"translate_hint": "Przejrzyj tekst docelowy. Linie o zbyt dużej długości uzyskują zwiększoną prędkość; możesz także edytować bezpośrednio.",
|
||||
"done_title": "Dubbing ukończony",
|
||||
"done_hint": "Przejrzyj współczynniki synchronizacji i synchronizacji. Popraw dowolną linię i naciśnij „Regen zmieniony”, aby szybko wykonać częściowe ponowne wykonanie.",
|
||||
"segment_one": "Segment {{count}}",
|
||||
"segment_other": "{{count}} segmentów",
|
||||
"dismiss_title": "Odrzuć — nie pojawi się ponownie na tym etapie, dopóki nie zostanie przeładowany"
|
||||
},
|
||||
"compare": {
|
||||
"title": "Porównanie głosu A/B",
|
||||
"close": "Bliskie porównanie",
|
||||
"desc": "Porównaj dwa głosy obok siebie, aby podjąć decyzję o obsadzie. Aplikacja pozostaje interaktywna.",
|
||||
"test_phrase": "Fraza testowa",
|
||||
"voice_a": "Głos A",
|
||||
"voice_b": "Głos B",
|
||||
"select_voice": "— Wybierz głos —",
|
||||
"preset_suffix": "(Ustawienie wstępne)",
|
||||
"no_audio": "Jeszcze nie ma dźwięku",
|
||||
"close_btn": "Zamknij",
|
||||
"comparing": "Porównywanie…",
|
||||
"compare_btn": "Porównaj",
|
||||
"preparing_voice": "Przygotowuję głos...",
|
||||
"generating_voice_a": "Generowanie głosu A...",
|
||||
"generating_voice_b": "Generowanie głosu B...",
|
||||
"comparison_complete": "Porównanie zakończone!",
|
||||
"play_failed": "Odtwarzanie nie powiodło się: {{message}}"
|
||||
},
|
||||
"models": {
|
||||
"hf_token_set_toast": "Zestaw tokenów HuggingFace — włączone szybsze pobieranie",
|
||||
"hf_token_save_failed": "Nie udało się zapisać tokena",
|
||||
"install_started": "Instalacja rozpoczęta — postęp w rzędzie",
|
||||
"delete_confirm": "Usunąć {{repoId}}? Możesz zainstalować go ponownie później.",
|
||||
"delete_confirm_title": "Usuń model",
|
||||
"deleted": "Usunięto {{repoId}}",
|
||||
"reinstall_confirm": "Zainstalować ponownie {{repoId}}? Spowoduje to usunięcie bieżącej kopii i ponowne pobranie.",
|
||||
"reinstall_confirm_title": "Zainstaluj ponownie model",
|
||||
"reinstalling": "Ponowna instalacja",
|
||||
"recommended_installed": "Polecane modele są już zainstalowane.",
|
||||
"started_downloading_one": "Rozpoczęto pobieranie modelu {{count}}",
|
||||
"started_downloading_other": "Rozpoczęto pobieranie modeli {{count}}",
|
||||
"install_failed": "Instalacja nie powiodła się: {{message}}",
|
||||
"removing_cached": "Usuwanie wersji zapisanych w pamięci podręcznej…",
|
||||
"resolving_metadata": "Rozwiązywanie metadanych repo",
|
||||
"retry_attempt": "Ponów próbę {{attempt}} — {{error}}",
|
||||
"connecting_hf": "Łączę się z HuggingFace…",
|
||||
"resolving_files_one": "Rozwiązywanie pliku {{count}}…",
|
||||
"resolving_files_other": "Rozwiązywanie plików {{count}}…",
|
||||
"resolving_files_active": "Rozwiązywanie {{count}} plików… · {{file}}",
|
||||
"measuring": "pomiar…",
|
||||
"files_progress": "{{done}}/{{total}} plików",
|
||||
"install_error": "Instalacja nie powiodła się: {{error}}",
|
||||
"view_on_hf": "Zobacz na HuggingFace",
|
||||
"install_btn": "Zainstaluj",
|
||||
"reinstall_btn": "Zainstaluj ponownie",
|
||||
"downloading": "pobieranie",
|
||||
"deleting": "usuwanie",
|
||||
"working": "pracujący",
|
||||
"installed": "zainstalowany",
|
||||
"not_installed": "nie zainstalowany",
|
||||
"required_tag": "wymagane",
|
||||
"delete_btn": "Usuń",
|
||||
"hf_token_btn": "Token HF",
|
||||
"hf_set_title": "Ustaw token HuggingFace, aby przyspieszyć pobieranie",
|
||||
"get_token": "Zdobądź token →",
|
||||
"reco_installed_for": "Zalecany pakiet zainstalowany dla {{device}}",
|
||||
"reco_for": "Zalecane dla {{device}}",
|
||||
"starting": "Zaczynam…",
|
||||
"required_size": "Wymagane ~{{size}} GB",
|
||||
"all_size": "Wszystko ~{{size}} GB",
|
||||
"req_tag": "wymaganie",
|
||||
"search_placeholder": "Wyszukaj modele…",
|
||||
"search_label": "Wyszukaj modele",
|
||||
"no_matches": "Żaden model nie pasuje do Twoich filtrów.",
|
||||
"sort_by": "Sortuj według {{column}}",
|
||||
"column_model": "Modelka",
|
||||
"column_role": "Rola",
|
||||
"column_size": "Rozmiar",
|
||||
"column_status": "Stan",
|
||||
"ready_badge": "Gotowy",
|
||||
"loading_badge": "Ładowanie…",
|
||||
"idle_badge": "Bezczynny",
|
||||
"started_downloading_required_one": "Rozpoczęto pobieranie {{count}} wymaganego modelu",
|
||||
"started_downloading_required_other": "Rozpoczęto pobieranie {{count}} wymaganych modeli"
|
||||
},
|
||||
"enterprise_faq": {
|
||||
"q_internal_tools": "Czy potrzebuję licencji na narzędzia wewnętrzne?",
|
||||
"a_internal_tools": "Wewnętrzne użycie przez Twoich pracowników i wykonawców jest celem dozwolonym w ramach FSL – nie jest wymagana żadna licencja. Licencja komercyjna jest wymagana, jeśli udostępniasz OmniVoice innym jako część konkurencyjnego produktu lub usługi (odsprzedaż, hostowany SaaS, white-label).",
|
||||
"q_try_before": "Czy mogę spróbować przed zatwierdzeniem?",
|
||||
"a_try_before": "Tak. Pełną aplikację można pobrać bezpłatnie i uruchomić lokalnie w celu oceny w ramach licencji FSL. Kiedy będziesz gotowy, aby omówić wdrożenie komercyjne, napisz do nas e-mail, a wspólnie omówimy szczegóły.",
|
||||
"q_watermark": "A co ze znakiem wodnym?",
|
||||
"a_watermark": "Domyślnie osadzony jest niewidoczny znak wodny AudioSeal. Licencjobiorcy komercyjni mogą wyłączyć tę funkcję w Ustawieniach → Prywatność. Do użytku bezpłatnego/osobistego zawsze dołączany jest znak wodny.",
|
||||
"q_apache": "Czy źródłem kiedykolwiek stał się Apache 2.0?",
|
||||
"a_apache": "Tak. Każde wydanie jest automatycznie konwertowane do licencji Apache w wersji 2.0 w drugą rocznicę jego publikacji. Oznacza to, że dzisiejszą wersją będzie Apache 2.0 dostępny za dwa lata i nie wymaga to od nas żadnych działań — FSL gwarantuje to nieodwołalnie."
|
||||
},
|
||||
"gallery_extra": {
|
||||
"save_prompt": "Wprowadź nazwę tego profilu głosowego:",
|
||||
"saved_as_profile": "Głos zapisany jako profil!",
|
||||
"save_profile_failed": "Nie udało się zapisać profilu",
|
||||
"download_failed": "Pobieranie nie powiodło się: {{message}}",
|
||||
"trim_load_failed": "Nie udało się załadować dźwięku do przycięcia: {{message}}",
|
||||
"upload_crop_failed": "Nie udało się przesłać przyciętego głosu: {{message}}"
|
||||
},
|
||||
"dub_workflow": {
|
||||
"preparing_audio": "Przygotowuję dźwięk…",
|
||||
"preparing_video": "Przygotowuję film…",
|
||||
"extracting_audio_scenes": "Wyodrębnianie dźwięku i scen…",
|
||||
"transcribing_audio": "Transkrypcja dźwięku…",
|
||||
"transcription_complete": "Transkrypcja ukończona",
|
||||
"upload_cancelled": "Przesyłanie anulowane",
|
||||
"upload_failed": "Przesyłanie nie powiodło się: {{message}}",
|
||||
"downloading_video": "Pobieram wideo…",
|
||||
"ingested": "Połknięto {{url}}",
|
||||
"ingest_cancelled": "Przyjmowanie anulowane",
|
||||
"ingest_failed": "Pobranie adresu URL nie powiodło się: {{message}}",
|
||||
"retry_cancelled": "Ponowna próba anulowana",
|
||||
"transcription_failed": "Transkrypcja nie powiodła się: {{message}}",
|
||||
"import_srt_no_job": "Najpierw prześlij lub pobierz film — nie ma zadania, do którego można by dołączyć napisy.",
|
||||
"imported_cues": "Zaimportowano {{count}} pamięci z {{file}}",
|
||||
"skipped_malformed": "{{count}} pominięty (zniekształcony)",
|
||||
"dropped_overlap": "{{count}} upuszczony (nakładanie się)",
|
||||
"clamped_to_duration": "{{count}} mocowany do długości nośnika",
|
||||
"srt_import_failed": "Import SRT nie powiódł się",
|
||||
"cleaned_one": "Oczyszczony fragment {{count}}",
|
||||
"cleaned_other": "Oczyszczone {{count}} fragmenty",
|
||||
"segments_clean": "Segmenty już czyste",
|
||||
"cleanup_failed": "Czyszczenie nie powiodło się: {{message}}",
|
||||
"cinematic_no_llm": "Jakość kinowa wymaga LLM — ustaw TRANSLATE_BASE_URL + TRANSLATE_API_KEY (Ollama działa lokalnie). Wracając do Fasta.",
|
||||
"translate_errors": "{{errorCount}}/{{totalCount}} segment(y) nie powiodły się: {{firstError}}",
|
||||
"translated_segments": "Przetłumaczone segmenty {{count}} → {{lang}}",
|
||||
"translated_cinematic_suffix": "(Kinowy)",
|
||||
"translation_failed": "Tłumaczenie nie powiodło się: {{message}}",
|
||||
"regenerating": "Regeneruję {{count}} segment(y)…",
|
||||
"generating_dub": "Generuję dubbing…",
|
||||
"generating_progress": "Generuję dub… {{current}}/{{total}}",
|
||||
"generation_aborted": "Pokolenie przerwane.",
|
||||
"dubbing_aborted": "Dubbing przerwany",
|
||||
"generation_stream_ended": "Strumień generacji zakończył się przed zakończeniem",
|
||||
"dub_complete": "Dubbing ukończony",
|
||||
"stop_failed": "Nie udało się zatrzymać",
|
||||
"save_first": "Najpierw kliknij „Prześlij i transkrybuj”, aby wideo zostało przetworzone na serwerze przed zapisaniem.",
|
||||
"project_saved": "Projekt zapisany",
|
||||
"project_created": "Projekt stworzony",
|
||||
"save_failed": "Zapis nie powiódł się: {{message}}",
|
||||
"opened_project": "Otwarto: {{name}}",
|
||||
"delete_project_confirm": "Usunąć ten projekt? Tego nie można cofnąć.",
|
||||
"project_deleted": "Projekt usunięty",
|
||||
"delete_history_confirm": "Usunąć ten element historii?",
|
||||
"history_deleted": "Element historii usunięty",
|
||||
"restored_state": "Przywrócono stan poprzedniej generacji",
|
||||
"upgrading_preview": "Uaktualniam segmenty w jakości podglądu {{count}} do pełnej jakości…"
|
||||
},
|
||||
"tts_errors": {
|
||||
"enter_text": "Proszę wpisać tekst",
|
||||
"upload_or_select": "Prześlij plik audio lub wybierz profil głosowy",
|
||||
"trim_hint": "Dźwięk wynosi {{duration}}s — przytnij do ≤{{max}}s, aby uzyskać najlepsze klonowanie",
|
||||
"timeout": "Upłynął limit czasu generacji — model może nadal być pobierany. Sprawdź Ustawienia → Dzienniki i spróbuj ponownie.",
|
||||
"error_prefix": "Błąd: {{message}}",
|
||||
"ignored_unsupported": "Ignorowana nieobsługiwana instrukcja: {{items}}",
|
||||
"ignored_duplicate": "Ignorowane (kategoria już ustawiona): {{items}}"
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Udostępnianie i dostęp zdalny",
|
||||
"help": "Udostępnij tę działającą instancję OmniVoice innym komputerom bez ponownego jej uruchamiania. Opcja Tylko sprzężenie zwrotne jest ustawieniem domyślnym — nic nie jest udostępniane, dopóki nie włączysz tej opcji tutaj.",
|
||||
"local_network": "Sieć lokalna",
|
||||
"local_help": "Udostępnij w sieci Wi-Fi/Ethernet za pomocą jednorazowego kodu PIN. Inne urządzenia zeskanuj kod QR lub otwórz link.",
|
||||
"ports_title": "Porty",
|
||||
"ports_help": "Są one ustawiane za pomocą zmiennych środowiskowych odczytywanych podczas uruchamiania. Zmień port zaplecza lub interfejsu użytkownika, ustawiając zmienną i ponownie uruchamiając OmniVoice.",
|
||||
"backend_port": "Port zaplecza",
|
||||
"ui_port": "Port interfejsu użytkownika",
|
||||
"lan_share_port": "Port udostępniania sieci LAN",
|
||||
"port_error": "Wprowadź port z zakresu od 1024 do 65535",
|
||||
"port_saved": "Port udostępniania sieci LAN został zapisany — zostanie zastosowany przy następnym włączeniu udostępniania",
|
||||
"port_save_failed": "Nie można zapisać portu: {{message}}",
|
||||
"saving": "Zapisuję…",
|
||||
"ports_note": "Porty backendu i interfejsu użytkownika mają zastosowanie po ponownym uruchomieniu. Port udostępniania sieci LAN zostanie zastosowany przy następnym włączeniu udostępniania.",
|
||||
"tailscale_title": "Tailscale (prywatny zdalny dostęp)",
|
||||
"tailscale_checking": "Sprawdzanie skali ogonowej…",
|
||||
"tailscale_absent": "Nie wykryto łuski ogonowej. Zainstaluj go, aby bezpiecznie łączyć się z OmniVoice z dowolnego miejsca w Twojej prywatnej sieci ogonowej.",
|
||||
"tailscale_install": "Zainstaluj Tailscale",
|
||||
"tailscale_running": "Skala ogonowa działa. Obsługuj OmniVoice w swojej prywatnej sieci ogonowej.",
|
||||
"tailscale_not_logged_in": "Tailscale jest zainstalowany, ale nie jest zalogowany. Najpierw uruchom i zaloguj się do Tailscale.",
|
||||
"tailscale_enabled": "Włączono obsługę skali ogonowej",
|
||||
"tailscale_enable_failed": "Nie można włączyć Tailscale",
|
||||
"tailscale_enable_error": "Nie można włączyć skali Tailscale: {{message}}",
|
||||
"tailscale_disabled": "Obsługa skali ogonowej wyłączona",
|
||||
"tailscale_disable_failed": "Nie można wyłączyć Tailscale",
|
||||
"tailscale_disable_error": "Nie można wyłączyć skali ogonowej: {{message}}",
|
||||
"tailscale_enabling": "Włączam…",
|
||||
"tailscale_enable_btn": "Włącz obsługę Tailscale",
|
||||
"tailscale_disabling": "Wyłączam…",
|
||||
"tailscale_disable_btn": "Zatrzymaj serwis z łuską ogonową",
|
||||
"tailscale_copy": "Skopiuj link",
|
||||
"tailscale_open": "Otwórz w przeglądarce",
|
||||
"copied": "Skopiowano",
|
||||
"tailscale_qr_alt": "Kod QR dla adresu URL Tailscale"
|
||||
},
|
||||
"reportBug": {
|
||||
"label": "Zgłoś błąd",
|
||||
"title": "Otwiera wstępnie wypełnioną stronę problemów z GitHub w przeglądarce. Nic nie zostanie wysłane, dopóki nie klikniesz Prześlij."
|
||||
},
|
||||
"app": {
|
||||
"loading": "Ładowanie…",
|
||||
"trimmed_loaded": "Wczytano przycięty dźwięk",
|
||||
"toast_exported": "Wyeksportowano: {{name}}",
|
||||
"toast_export_failed": "Eksport nie powiódł się: {{message}}",
|
||||
"toast_open_folder_failed": "Nie można otworzyć folderu: {{message}}",
|
||||
"toast_saving": "Zapisywanie {{name}}...",
|
||||
"toast_saved": "Zapisano: {{path}}",
|
||||
"toast_save_error": "Zapisz błąd: {{message}}",
|
||||
"toast_processing": "Przetwarzanie {{name}}...",
|
||||
"toast_downloaded": "Pobrano {{name}}",
|
||||
"toast_download_error": "Błąd pobierania: {{message}}",
|
||||
"toast_upload_first": "Najpierw kliknij „Prześlij i transkrybuj”, aby wideo zostało przetworzone na serwerze przed zapisaniem.",
|
||||
"toast_save_failed": "Zapis nie powiódł się: {{message}}",
|
||||
"toast_opened": "Otwarto: {{name}}",
|
||||
"toast_project_deleted": "Projekt usunięty",
|
||||
"toast_restored_state": "Przywrócono stan poprzedniej generacji",
|
||||
"toast_history_deleted": "Element historii usunięty",
|
||||
"toast_flushed": "Opróżniony — RAM {{ram}}G · VRAM {{vram}}G{{unloaded}}",
|
||||
"toast_model_unloaded": "· model rozładowany",
|
||||
"toast_flush_failed": "Spłukiwanie nie powiodło się: {{message}}",
|
||||
"toast_project_saved": "Projekt zapisany",
|
||||
"toast_project_created": "Projekt stworzony"
|
||||
},
|
||||
"update": {
|
||||
"available": "Dostępna aktualizacja {{version}}",
|
||||
"install": "Zainstaluj i uruchom ponownie",
|
||||
"install_hint": "Pobierz aktualizację i uruchom ponownie w nowej wersji",
|
||||
"downloading": "Aktualizowanie… {{pct}}%",
|
||||
"restart": "Uruchom ponownie, aby zaktualizować",
|
||||
"busy": "Najpierw dokończ dubbing — potem zainstaluj aktualizację.",
|
||||
"whats_new": "Co nowego?",
|
||||
"failed": "Aktualizacja nie powiodła się",
|
||||
"retry": "Spróbuj ponownie",
|
||||
"dismiss": "Odrzuć"
|
||||
},
|
||||
"archetypes": {
|
||||
"featured": "Polecane",
|
||||
@@ -945,5 +1577,25 @@
|
||||
"facet_accent": "Akcent",
|
||||
"facet_lang": "Język",
|
||||
"facet_whisper": "Szeptać"
|
||||
},
|
||||
"support": {
|
||||
"tab_support": "Wsparcie",
|
||||
"tab_license": "Licencja komercyjna",
|
||||
"toggle_label": "Wsparcie lub licencja komercyjna",
|
||||
"other_ways": "Inne sposoby pomocy",
|
||||
"star_github": "Gwiazda na GitHubie",
|
||||
"join_discord": "Dołącz do Discorda"
|
||||
},
|
||||
"updates": {
|
||||
"tab": "Aktualizacje",
|
||||
"up_to_date": "Aktualne · v{{version}}",
|
||||
"check_now": "Sprawdź teraz",
|
||||
"releases": "Wydania",
|
||||
"current": "aktualna",
|
||||
"prerelease": "podgląd",
|
||||
"loading": "Ładowanie wersji…",
|
||||
"none": "Nie znaleziono żadnych wydań",
|
||||
"load_error": "Nie można wczytać wersji (offline?)",
|
||||
"retry_load": "Spróbuj ponownie"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,4 @@
|
||||
{
|
||||
"update": {
|
||||
"available": "Atualização {{version}} disponível",
|
||||
"install": "Instalar e reiniciar",
|
||||
"install_hint": "Baixe a atualização e reinicie na nova versão",
|
||||
"downloading": "Atualizando… {{pct}}%",
|
||||
"restart": "Reiniciar para atualizar",
|
||||
"busy": "Termine sua dublagem primeiro — depois instale a atualização."
|
||||
},
|
||||
"nav": {
|
||||
"clone": "Clonar",
|
||||
"design": "Desenhar",
|
||||
@@ -16,7 +8,10 @@
|
||||
"launchpad": "Barra de lançamento",
|
||||
"gallery": "Galeria",
|
||||
"transcripts": "Transcrições",
|
||||
"omnidrive": "OmniDrive"
|
||||
"omnidrive": "OmniDrive",
|
||||
"move_rail_right": "Mova o trilho para a direita",
|
||||
"move_rail_left": "Mova o trilho para a esquerda",
|
||||
"flip_rail": "Vire o lado do trilho"
|
||||
},
|
||||
"settings": {
|
||||
"general": "Geral",
|
||||
@@ -45,7 +40,40 @@
|
||||
"ffmpeg_missing": "Não encontrado",
|
||||
"ffmpeg_current": "Caminho atual",
|
||||
"ffmpeg_desc": "Defina um caminho ffmpeg personalizado se a detecção automática falhar.",
|
||||
"ffmpeg_saved": "Conjunto de caminho FFmpeg – reinicie o back-end para aplicar."
|
||||
"ffmpeg_saved": "Conjunto de caminho FFmpeg – reinicie o back-end para aplicar.",
|
||||
"diagnostics_copied": "Diagnóstico copiado – cole em seu relatório de problemas.",
|
||||
"updater_desktop": "O atualizador só é executado no aplicativo de desktop.",
|
||||
"latest_version": "Você está na versão mais recente.",
|
||||
"logs_load_failed": "Falha ao carregar registros: {{message}}",
|
||||
"clear_frontend_confirm": "Limpar o buffer de log do front-end na memória?",
|
||||
"clear_frontend_title": "Limpar registros",
|
||||
"frontend_logs_cleared": "Registros de front-end limpos",
|
||||
"clear_tauri_confirm": "Truncar os arquivos de log do lado Tauri? O sistema operacional continuará a escrever novas entradas.",
|
||||
"clear_tauri_title": "Limpar registros do Tauri",
|
||||
"nothing_to_clear": "Nada para limpar – ainda não há arquivo de log do Tauri no disco.",
|
||||
"cleared_tauri_one": "Arquivo de log Tauri {{count}} limpo",
|
||||
"cleared_tauri_other": "Arquivos de log Tauri {{count}} limpos",
|
||||
"clear_tauri_failed": "Falha ao limpar registros do Tauri: {{message}}",
|
||||
"clear_backend_confirm": "Limpar o tempo de execução do back-end + registros de falhas? Isto não pode ser desfeito.",
|
||||
"clear_backend_title": "Limpar registros",
|
||||
"backend_logs_cleared": "Registros de back-end limpos",
|
||||
"clear_backend_failed": "Falha ao limpar registros",
|
||||
"copy_failed": "Falha na cópia: {{message}}",
|
||||
"update_check_failed": "Falha na verificação de atualização: {{message}}",
|
||||
"save_failed": "Falha ao salvar: {{message}}",
|
||||
"clear_failed": "Falha na limpeza: {{message}}",
|
||||
"engine_switched": "{{family}} → {{engine}}",
|
||||
"channel_set_failed": "Falha ao definir canal: {{message}}",
|
||||
"updater_downloading": "Baixando {{version}}…",
|
||||
"updater_installed": "Instalado – relançando.",
|
||||
"updater_available_title": "Atualização disponível",
|
||||
"updater_available_body": "A versão {{version}} está disponível.\n\n{{notes}}\n\nBaixe e instale agora?",
|
||||
"updater_notes_fallback": "Veja as notas de lançamento no GitHub.",
|
||||
"shortcut_load_failed": "Não foi possível carregar o atalho: {{message}}",
|
||||
"shortcut_set": "Atalho de ditado definido como {{shortcut}}",
|
||||
"shortcut_register_failed": "Não foi possível registrar: {{message}}",
|
||||
"shortcut_reset": "Redefinir para o padrão",
|
||||
"shortcut_reset_failed": "Falha na reinicialização: {{message}}"
|
||||
},
|
||||
"bootstrap": {
|
||||
"title": "OmniVoice Studio",
|
||||
@@ -69,7 +97,13 @@
|
||||
"waiting_output": "Aguardando saída…",
|
||||
"auto_detect": "Detectar automaticamente",
|
||||
"suggest_lang": "Mudar para o Português?",
|
||||
"select_lang": "Idioma:"
|
||||
"select_lang": "Idioma:",
|
||||
"region_global": "Global (direto)",
|
||||
"region_china": "China (espelho)",
|
||||
"region_russia": "Rússia (espelho)",
|
||||
"region_restricted": "Restrito (espelho)",
|
||||
"unknown_error": "Erro desconhecido",
|
||||
"retrying": "Tentando novamente…"
|
||||
},
|
||||
"stories": {
|
||||
"title": "Editor de histórias",
|
||||
@@ -158,7 +192,17 @@
|
||||
"reload": "Forçar recarga da IU",
|
||||
"backend": "Back-end",
|
||||
"frontend": "Interface",
|
||||
"tauri": "Touro"
|
||||
"tauri": "Touro",
|
||||
"cancelOp": "Cancelar operação",
|
||||
"dismiss": "Dispensar",
|
||||
"dismissStatus": "Dispensar status",
|
||||
"search": "Pesquisar…",
|
||||
"no_matches": "Nenhuma correspondência",
|
||||
"recent_and_popular": "Recentes e populares",
|
||||
"popular_label": "Populares",
|
||||
"showing_of": "Mostrando {{shown}} de {{total}}. Digite para pesquisar…",
|
||||
"yes": "Sim",
|
||||
"no": "Não"
|
||||
},
|
||||
"launchpad": {
|
||||
"greeting": "Olá",
|
||||
@@ -180,7 +224,16 @@
|
||||
"locked": "BLOQUEADO",
|
||||
"open": "Abrir",
|
||||
"try_it": "Experimente",
|
||||
"audio_only": "Somente áudio"
|
||||
"audio_only": "Somente áudio",
|
||||
"stories_title": "Histórias",
|
||||
"stories_desc": "Audiolivros com múltiplas vozes – escolha seus personagens, insira um roteiro, exporte por capítulo.",
|
||||
"gallery_title": "Galeria de Voz",
|
||||
"gallery_desc": "Procure vozes projetadas e prontas por sotaque, idade e estilo — sem configuração.",
|
||||
"transcripts_title": "Transcrições",
|
||||
"transcripts_desc": "Transforme áudio ou vídeo em texto editável e pesquisável em 646 idiomas.",
|
||||
"recent_files": "Arquivos recentes",
|
||||
"view_all_files": "Ver todos os arquivos",
|
||||
"file": "Arquivo"
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "Alerta",
|
||||
@@ -271,11 +324,6 @@
|
||||
"outputs": "Resultados",
|
||||
"crash_log": "Registro de falhas",
|
||||
"update_endpoint": "Atualizar ponto de extremidade",
|
||||
"update_channel": "Canal de atualização",
|
||||
"channel_stable": "Estável",
|
||||
"channel_preview": "Prévia",
|
||||
"channel_set": "Canal de atualização definido como {{channel}}",
|
||||
"channel_preview_hint": "A prévia acompanha a compilação mais recente da main — recursos mais novos, menos testados. Volta para a estável se uma versão estável estiver à frente.",
|
||||
"yes": "sim",
|
||||
"no": "não",
|
||||
"web_preview": "visualização da web",
|
||||
@@ -284,7 +332,12 @@
|
||||
"copy_diagnostics": "Copiar diagnóstico",
|
||||
"github": "OmniVoice no GitHub",
|
||||
"model_card": "Cartão modelo",
|
||||
"commercial_license": "Licença Comercial"
|
||||
"commercial_license": "Licença Comercial",
|
||||
"update_channel": "Canal de atualização",
|
||||
"channel_stable": "Estável",
|
||||
"channel_preview": "Prévia",
|
||||
"channel_set": "Canal de atualização definido como {{channel}}",
|
||||
"channel_preview_hint": "A prévia acompanha a compilação mais recente da main — recursos mais novos, menos testados. Volta para a estável se uma versão estável estiver à frente."
|
||||
},
|
||||
"privacy": {
|
||||
"desc": "Tudo roda em <1>esta máquina</1>. Seu áudio, vídeo e transcrições nunca saem do seu computador, a menos que você use explicitamente um tradutor online (Google, DeepL, etc.) ou envie para o HuggingFace.",
|
||||
@@ -340,7 +393,30 @@
|
||||
"unavailable": "indisponível",
|
||||
"use": "Usar",
|
||||
"loading": "Carregando motores…",
|
||||
"refresh": "Atualizar"
|
||||
"refresh": "Atualizar",
|
||||
"matrixTitle": "Matriz de compatibilidade do motor",
|
||||
"loadFailed": "Falha ao carregar motores: {{message}}",
|
||||
"couldNotLoad": "Não foi possível carregar os motores: {{message}}",
|
||||
"retry": "Tentar novamente",
|
||||
"activeEngine": "Ativo {{family}}: {{engine}}",
|
||||
"engineCompatLabel": "{{family}} compatibilidade do motor",
|
||||
"active": "ativo",
|
||||
"whyUnavailable": "Por que indisponível?",
|
||||
"lastError": "Último erro: {{error}}",
|
||||
"installedAndReady": "Instalado e pronto",
|
||||
"notInstalled": "Não instalado",
|
||||
"available": "Disponível",
|
||||
"subprocessTitle": "Executa em seu próprio subprocesso + venv",
|
||||
"inProcessTitle": "É executado no processo OmniVoice Python",
|
||||
"testEngine": "Motor de teste",
|
||||
"testing": "Testando…",
|
||||
"recheck": "Verifique novamente",
|
||||
"rechecking": "Verificando novamente…",
|
||||
"latencyMs": "{{ms}} ms",
|
||||
"failed": "falhou",
|
||||
"acceptLicense": "Aceitar licença",
|
||||
"noBackends": "Nenhum back-end registrado.",
|
||||
"switch_failed": "Falha ao trocar de motor"
|
||||
},
|
||||
"capture": {
|
||||
"desc": "As teclas de atalho globais funcionam apenas no aplicativo de desktop. A IU da web usa um atalho <1>Ctrl+Shift+Space</1> na página enquanto a janela está em foco.",
|
||||
@@ -352,13 +428,55 @@
|
||||
"record_shortcut": "Atalho de gravação",
|
||||
"recording": "Gravando…",
|
||||
"save": "Salvar",
|
||||
"reset_default": "Redefinir para o padrão"
|
||||
"reset_default": "Redefinir para o padrão",
|
||||
"listening_label": "Ouvindo…",
|
||||
"transcribing_label": "Transcrevendo…",
|
||||
"pasted": "Colado",
|
||||
"no_speech": "Nenhuma fala detectada",
|
||||
"mic_denied": "Acesso ao microfone negado",
|
||||
"mic_denied_toast": "Acesso ao microfone negado. {{hint}}",
|
||||
"mic_hint_mac": "macOS: abra Configurações do sistema → Privacidade e segurança → Microfone e ative OmniVoice.",
|
||||
"mic_hint_windows": "Windows: abra Configurações → Privacidade e segurança → Microfone e permita OmniVoice.",
|
||||
"mic_hint_linux": "Linux: verifique se seu usuário está no grupo de áudio e se o WebView tem acesso ao microfone.",
|
||||
"transcription_failed": "Falha na transcrição: {{message}}"
|
||||
},
|
||||
"logs": {
|
||||
"no_tauri_log": "Nenhum log do Tauri no disco ainda – inicie através da versão desktop para produzir um",
|
||||
"empty_frontend": "Nenhuma entrada do console front-end foi capturada ainda. Interaja com o aplicativo — todos os consoles.* aparecerão aqui.",
|
||||
"empty_tauri": "Nenhum registro Tauri disponível. É executado apenas no shell da área de trabalho.",
|
||||
"empty_backend": "O log de tempo de execução está vazio. A atividade aparecerá aqui conforme o back-end a registra."
|
||||
"empty_backend": "O log de tempo de execução está vazio. A atividade aparecerá aqui conforme o back-end a registra.",
|
||||
"title": "Registros",
|
||||
"source_backend": "Back-end",
|
||||
"source_frontend": "Interface",
|
||||
"source_tauri": "Touro",
|
||||
"expand": "Expandir registros",
|
||||
"collapse": "Recolher registros",
|
||||
"expand_aria": "Expandir painel de registros",
|
||||
"collapse_aria": "Recolher painel de registros",
|
||||
"drag_resize": "Arraste para redimensionar",
|
||||
"refresh": "Atualizar",
|
||||
"refresh_aria": "Atualizar registros",
|
||||
"copy_visible": "Copiar log visível",
|
||||
"copy_visible_aria": "Copiar log visível",
|
||||
"clear": "Limpar",
|
||||
"clear_aria": "Limpar registro",
|
||||
"report_issue": "Reportar problema (copiar diagnóstico)",
|
||||
"report_issue_aria": "Informar problema",
|
||||
"close": "Fechar",
|
||||
"close_aria": "Fechar painel de registros",
|
||||
"join_discord": "Junte-se ao nosso Discord",
|
||||
"join_discord_aria": "Junte-se à nossa comunidade Discord",
|
||||
"support_project": "Apoie este projeto",
|
||||
"support_project_aria": "Apoie este projeto",
|
||||
"empty_frontend_short": "Nenhuma saída do console frontend ainda.",
|
||||
"empty_lines": "Sem filas.",
|
||||
"all_clear": "✅ Tudo limpo – nenhum problema detectado",
|
||||
"log_cleared": "{{source}} registro limpo",
|
||||
"clear_failed": "Falha na limpeza: {{message}}",
|
||||
"log_copied": "Registro {{source}} copiado",
|
||||
"copy_failed": "Falha na cópia: {{message}}",
|
||||
"report_copied": "Relatório de diagnóstico copiado – cole-o em um problema do GitHub.",
|
||||
"report_failed": "Falha no relatório: {{message}}"
|
||||
},
|
||||
"voice_profile": {
|
||||
"test_text": "Olá - este é um teste desta voz.",
|
||||
@@ -526,7 +644,17 @@
|
||||
"install_already": "{{engine}} já estava instalado",
|
||||
"install_ok": "{{engine}} instalado",
|
||||
"install_failed": "Falha na instalação: {{message}}",
|
||||
"prep_elapsed": "{{time}} decorrido"
|
||||
"prep_elapsed": "{{time}} decorrido",
|
||||
"add_language": "Adicionar idioma",
|
||||
"search_languages": "Pesquisar idiomas…",
|
||||
"languages_selected_one": "{{count}} idioma selecionado",
|
||||
"languages_selected_other": "{{count}} idiomas selecionados",
|
||||
"more_to_narrow": "+{{count}} mais — digite para restringir",
|
||||
"no_matches": "Nenhuma correspondência",
|
||||
"diagnostic_copied": "Diagnóstico copiado",
|
||||
"copy_failed": "Falha na cópia",
|
||||
"open_docs": "Abrir documentos",
|
||||
"copy_diagnostic": "Copiar diagnóstico"
|
||||
},
|
||||
"glossary": {
|
||||
"title": "Glossário",
|
||||
@@ -601,7 +729,17 @@
|
||||
"more_actions_title": "Mais ações",
|
||||
"speaker_pick": "Escolha…",
|
||||
"speaker_title_detected": "Alto-falante – escolha entre detectado ou digite um nome personalizado",
|
||||
"speaker_title_custom": "Palestrante — digite um nome (nenhum clone de diarização detectado)"
|
||||
"speaker_title_custom": "Palestrante — digite um nome (nenhum clone de diarização detectado)",
|
||||
"time_edit_title": "Clique para editar a hora de início (m:ss.s). Enter para confirmar, Esc para cancelar.",
|
||||
"fit_fits": "Serve",
|
||||
"fit_fits_title": "Áudio de taxa natural cabe dentro do slot.",
|
||||
"fit_overflows": "Estouro +{{seconds}}s",
|
||||
"fit_overflows_title": "O texto traduzido era maior que o espaço original em {{seconds}}s. O áudio foi cortado; encurte o texto ou mude o tempo para \"Alongar vídeo\".",
|
||||
"fit_stretched": "Vídeo {{ratio}}×",
|
||||
"fit_stretched_title": "Modo Stretch Video: o vídeo deste segmento foi desacelerado para {{ratio}}× para se ajustar ao áudio de dublagem natural.",
|
||||
"fit_compressed_title": "O áudio TTS é {{pct}}% do slot – fortemente compactado.",
|
||||
"fit_audio_title": "O áudio cabe dentro do slot.",
|
||||
"fit_ratio_title": "O áudio TTS é {{pct}}% do slot."
|
||||
},
|
||||
"voice": {
|
||||
"personality": "Personalidade",
|
||||
@@ -696,10 +834,24 @@
|
||||
"status_running": "correndo",
|
||||
"status_done": "feito",
|
||||
"status_failed": "falhou",
|
||||
"status_cancelled": "cancelado"
|
||||
"status_cancelled": "cancelado",
|
||||
"add_to_queue_title": "Adicionar vídeos à fila",
|
||||
"drop_hint_text": "Solte os arquivos de vídeo aqui ou clique para navegar",
|
||||
"drop_formats": "MP4 · MOV · MKV · WEBM",
|
||||
"files_kicker": "ARQUIVOS ({{count}})",
|
||||
"file_size_mb": "{{size}}MB",
|
||||
"target_languages": "IDIOMAS ALVO",
|
||||
"voice_kicker": "VOZ",
|
||||
"default_option": "Padrão",
|
||||
"clone_profiles": "Clonar perfis",
|
||||
"presets": "Predefinições",
|
||||
"preserve_bg": "Preservar o áudio de fundo (música/FX)",
|
||||
"estimate": "{{videos}} vídeo(s) × {{langs}} idioma(s) = {{jobs}} trabalho(s)",
|
||||
"select_files_langs": "Selecione arquivos e idiomas",
|
||||
"add_to_queue": "Adicionar à fila"
|
||||
},
|
||||
"gallery": {
|
||||
"title": "Galeria",
|
||||
"title": "OmniVoice Galeria",
|
||||
"search_placeholder": "Pesquisar no YouTube…",
|
||||
"all_voices": "Todas as vozes ({{count}})",
|
||||
"no_voices": "Ainda não há vozes",
|
||||
@@ -714,6 +866,14 @@
|
||||
"youtube_results": "Resultados do YouTube ({{count}})",
|
||||
"clone_profile": "Clonar perfil",
|
||||
"crop_audio": "Cortar áudio",
|
||||
"cat_disney": "Disney",
|
||||
"cat_anime": "Anime",
|
||||
"cat_marvel": "Marvel/DC",
|
||||
"cat_celebs": "Celebridades",
|
||||
"cat_politicians": "Políticos",
|
||||
"cat_news": "Âncoras de Notícias",
|
||||
"cat_gaming": "Jogos",
|
||||
"cat_books": "Livros/Filmes",
|
||||
"subtitle": "Centenas de vozes projetadas e prontas - escolha uma e pronto.",
|
||||
"zone_archetypes": "Arquétipos",
|
||||
"zone_imports": "Minhas importações",
|
||||
@@ -818,7 +978,6 @@
|
||||
"badge": "Licença Comercial",
|
||||
"hero_title": "Envie vozes de IA em produção",
|
||||
"hero_desc": "OmniVoice Studio está disponível sob a Licença de Fonte Funcional (FSL). A maioria dos usuários pode avaliar, criar protótipos e até implantar internamente sem um acordo comercial. Você só precisa de uma licença comercial se estiver construindo um produto ou serviço concorrente ou se seu caso de uso estiver fora dos limites do FSL.",
|
||||
"hero_note": "Construir um produto ou serviço concorrente ou implantá-lo em escala (por exemplo, servir API paga conforme o uso) requer uma licença comercial. Os níveis de preços estarão disponíveis em breve. Entre em contato enquanto isso.",
|
||||
"why_title": "Por que as empresas escolhem OmniVoice",
|
||||
"pricing_title": "Preços",
|
||||
"faq_title": "Perguntas comuns",
|
||||
@@ -838,7 +997,8 @@
|
||||
"benefit_source": "Núcleo disponível na fonte",
|
||||
"benefit_source_desc": "Visibilidade total da pilha. Audite, bifurque e adapte-se aos termos da licença.",
|
||||
"benefit_lang": "646 idiomas",
|
||||
"benefit_lang_desc": "Transcreva, traduza e duble em 646 idiomas com qualidade de nível humano."
|
||||
"benefit_lang_desc": "Transcreva, traduza e duble em 646 idiomas com qualidade de nível humano.",
|
||||
"hero_note": "O uso interno – mesmo em grande escala – é gratuito sob o FSL; uma licença comercial só é necessária para oferecer o OmniVoice a terceiros como um produto ou serviço concorrente (uma API hospedada ou paga por uso, um aplicativo revendido ou com etiqueta branca). Os níveis de preços estarão disponíveis em breve. Entre em contato enquanto isso."
|
||||
},
|
||||
"exportModal": {
|
||||
"export": "Exportar",
|
||||
@@ -927,7 +1087,479 @@
|
||||
"dubbing_title": "Veja a dublagem em ação",
|
||||
"dubbing_sync": "Reprodução sincronizada",
|
||||
"dubbing_picker": "Tente outro idioma:",
|
||||
"dubbing_cta": "Execute isso em seu próprio vídeo →"
|
||||
"dubbing_cta": "Execute isso em seu próprio vídeo →",
|
||||
"dubbing_loading": "Carregando demonstração de dublagem…",
|
||||
"dubbing_dismiss": "Ignorar demonstração de dublagem",
|
||||
"original_tag": "originais",
|
||||
"dubbed_tag": "dublado",
|
||||
"script_conversational": "Conversacional",
|
||||
"script_technical": "Vocabulário técnico",
|
||||
"script_french": "Não Inglês (Francês)",
|
||||
"aria_pause": "Pausa {{label}}",
|
||||
"aria_hear": "Ouça {{label}}",
|
||||
"aria_replay": "Reproduzir {{label}} através do transcritor"
|
||||
},
|
||||
"direction": {
|
||||
"title": "Direção para o segmento #{{id}}",
|
||||
"desc": "Diga ao pipeline como essa linha deve ser. O inglês simples funciona – o sistema mapeia suas palavras em uma taxonomia estável (energia/emoção/ritmo/intimidade/formalidade) e, em seguida, encadeia a taxonomia por meio de tradução cinematográfica, TTS e ajuste de slot.",
|
||||
"label": "Direção",
|
||||
"lineHint": "Linha: \"{{text}}\"",
|
||||
"placeholder": "por exemplo urgente e surpreso / caloroso, esperançoso / sussurrado, íntimo",
|
||||
"previewParse": "Análise de visualização",
|
||||
"previewFailed": "Falha na visualização: {{message}}",
|
||||
"clear": "Limpar",
|
||||
"cancel": "Cancelar",
|
||||
"saveDirection": "Salvar direção",
|
||||
"ttsInstruct": "Instrução TTS:",
|
||||
"nothingParsed": "- (nada analisado)",
|
||||
"translateHint": "Dica de tradução:",
|
||||
"rateBias": "Viés de taxa:",
|
||||
"speedsUp": "acelera",
|
||||
"slowsDown": "desacelera",
|
||||
"taxonomyTokens": "tokens de taxonomia"
|
||||
},
|
||||
"errors": {
|
||||
"title": "Esta guia encontrou um obstáculo.",
|
||||
"desc": "Não se preocupe – o resto do aplicativo ainda funciona. Você pode alternar as guias ou tentar novamente abaixo.",
|
||||
"tryAgain": "Tente novamente",
|
||||
"openDocs": "Abra documentos para este erro"
|
||||
},
|
||||
"keyboard": {
|
||||
"title": "Atalhos de teclado",
|
||||
"footer": "Pressione <1>?</1> a qualquer momento para abrir.",
|
||||
"or": "ou",
|
||||
"nav": "Navegação",
|
||||
"nav_cheatsheet": "Mostrar esta folha de dicas",
|
||||
"nav_closeModal": "Fechar modal/cancelar",
|
||||
"nav_save": "Salvar projeto/comprometer corte",
|
||||
"segmentEditor": "Editor de segmento",
|
||||
"seg_split": "Dividir segmento no cursor",
|
||||
"seg_merge": "Mesclar com o próximo segmento",
|
||||
"seg_undo": "Desfazer",
|
||||
"seg_redo": "Refazer",
|
||||
"seg_click": "Ação primária",
|
||||
"seg_shiftClick": "Seleção de intervalo",
|
||||
"trimmer": "Aparador de áudio",
|
||||
"trim_playPause": "Visualizar reprodução/pausa",
|
||||
"trim_nudgeStart": "Alavanca de partida",
|
||||
"trim_nudgeEnd": "Empurre a alça final",
|
||||
"trim_fineNudge": "Bela cutucada",
|
||||
"trim_coarseNudge": "Empurrão grosseiro",
|
||||
"trim_zoomIn": "Aumentar/diminuir zoom",
|
||||
"trim_fitAll": "Ajustar tudo / Ajustar seleção",
|
||||
"trim_confirm": "Confirmar corte",
|
||||
"dub": "Duplicar",
|
||||
"dub_generate": "Gerar dublagem",
|
||||
"dub_sidebar": "Alternar barra lateral"
|
||||
},
|
||||
"network": {
|
||||
"sharing_on_title": "Compartilhamento ativado – clique para obter detalhes",
|
||||
"share_on_network": "Compartilhe na sua rede",
|
||||
"switching": "Trocando…",
|
||||
"network": "Rede",
|
||||
"local": "locais",
|
||||
"share_confirm_title": "Compartilhar na sua rede?",
|
||||
"share_confirm_hint": "Outros dispositivos no seu Wi-Fi/Ethernet poderão acessar o OmniVoice usando o PIN de acesso mostrado quando estiver ligado.",
|
||||
"enabling": "Habilitando…",
|
||||
"enable": "Habilitar",
|
||||
"shared_title": "Compartilhado em sua rede",
|
||||
"no_interface": "Nenhuma interface de rede acessível – conecte-se a Wi-Fi/Ethernet.",
|
||||
"copy_link": "Copiar link",
|
||||
"open_in_browser": "Abrir no navegador",
|
||||
"qr_alt": "QR para {{ip}}",
|
||||
"pin": "PIN:",
|
||||
"stop_sharing": "Pare de compartilhar",
|
||||
"copied": "Copiado",
|
||||
"enable_error": "Não foi possível ativar o compartilhamento: {{message}}",
|
||||
"disable_error": "Não foi possível desativar: {{message}}"
|
||||
},
|
||||
"readiness": {
|
||||
"checking_system": "Verificando sistema…",
|
||||
"all_ready": "Todos os sistemas prontos",
|
||||
"system_readiness": "Preparação do sistema",
|
||||
"asr_model": "Modelo ASR",
|
||||
"loaded_ready": "Carregado e pronto",
|
||||
"loading_first_run": "Carregando… (isso pode levar de 1 a 2 minutos na primeira execução)",
|
||||
"failed_to_load": "Falha ao carregar",
|
||||
"not_loaded_yet": "Ainda não carregado — será carregado na primeira transcrição",
|
||||
"error_check_logs": "Erro: {{error}}. Verifique os logs e tente reiniciar.",
|
||||
"check_logs_restart": "Verifique os logs para erros de carregamento do modelo. Tente reiniciar.",
|
||||
"llm_cinematic": "LLM (Cinemático)",
|
||||
"llm_configure": "Configure TRANSLATE_BASE_URL para qualidade de tradução cinematográfica",
|
||||
"llm_set_env": "Defina as variáveis de ambiente TRANSLATE_BASE_URL e TRANSLATE_API_KEY. Funciona com Ollama, OpenAI, LM Studio, etc.",
|
||||
"llm_optional": "Opcional — defina TRANSLATE_BASE_URL para qualidade cinematográfica"
|
||||
},
|
||||
"license": {
|
||||
"title": "Supertonic-3 – Aceitação de licença",
|
||||
"intro": "O Supertonic-3 é enviado sob duas licenças distintas. Revise ambos antes de ativar o mecanismo.",
|
||||
"sdk_heading": "Código SDK · MIT",
|
||||
"sdk_desc": "O SDK de inferência Python (supertonic) é licenciado pelo MIT. Uso permissivo, inclusive comercial.",
|
||||
"read_mit": "Leia a licença do MIT →",
|
||||
"model_heading": "Pesos do modelo · OpenRAIL-M",
|
||||
"model_desc": "Os pesos do modelo Supertonic-3 são lançados sob a licença OpenRAIL-M. Esta licença restringe o uso a fins não maliciosos — consulte a licença vinculada para obter o conjunto completo de restrições baseadas no uso.",
|
||||
"read_openrail": "Leia a licença OpenRAIL-M →",
|
||||
"footer": "Clicar em Aceitar registra sua aceitação nas configurações locais do OmniVoice e ativa o mecanismo. Sua aceitação é armazenada apenas nesta máquina – nada é relatado à Supertone Inc. ou a terceiros.",
|
||||
"saving": "Salvando…",
|
||||
"accept": "Aceitar",
|
||||
"accepted_toast": "Licença Supertonic-3 aceita.",
|
||||
"accept_error": "Falha ao registrar a aceitação da licença: {{message}}"
|
||||
},
|
||||
"voicePreview": {
|
||||
"title": "Visualização de voz",
|
||||
"close": "Fechar visualização",
|
||||
"default_text": "Olá! Esta é uma prévia de como soo nesta voz.",
|
||||
"default_voice": "Voz padrão",
|
||||
"clone_profiles": "Clonar perfis",
|
||||
"designed_voices": "Vozes projetadas",
|
||||
"presets": "Predefinições",
|
||||
"placeholder": "Digite algo para ouvir…",
|
||||
"stop": "Pare",
|
||||
"regenerate": "Regenerar",
|
||||
"preview": "Visualização",
|
||||
"hint": "8 etapas · visualização rápida"
|
||||
},
|
||||
"header": {
|
||||
"kicker_studio": "Estúdio",
|
||||
"kicker_library": "Biblioteca",
|
||||
"kicker_preferences": "Preferências",
|
||||
"kicker_licensing": "Licenciamento",
|
||||
"label_launchpad": "Barra de lançamento",
|
||||
"label_clone": "Clone de voz",
|
||||
"label_design": "Design de voz",
|
||||
"label_dub": "Dublagem",
|
||||
"label_projects": "OmniDrive",
|
||||
"label_gallery": "Galeria",
|
||||
"label_transcriptions": "Transcrições",
|
||||
"label_settings": "Configurações",
|
||||
"label_enterprise": "Licença Comercial",
|
||||
"status_ready": "Pronto",
|
||||
"status_loading": "Carregando…",
|
||||
"status_idle": "Inativo",
|
||||
"memory_management": "Gerenciamento de memória",
|
||||
"flush": "Lavar",
|
||||
"loaded_models": "Modelos carregados",
|
||||
"no_models": "Nenhum modelo carregado",
|
||||
"unload": "Descarregar",
|
||||
"flush_caches": "Liberar caches",
|
||||
"unload_all_flush": "Descarregar tudo + liberar"
|
||||
},
|
||||
"sidebar": {
|
||||
"tab_drive": "Dirigir",
|
||||
"tab_history": "História",
|
||||
"tab_exports": "Exportações",
|
||||
"save_project": "Salvar projeto de dublagem",
|
||||
"save_new_project": "Salvar como novo projeto de dublagem",
|
||||
"dub_projects": "Projetos de dublagem",
|
||||
"voice_clones": "Clones de voz",
|
||||
"designed_voices": "Vozes projetadas",
|
||||
"no_dub_projects": "Nenhum projeto de dublagem salvo",
|
||||
"no_dub_hint": "Carregue um vídeo e clique em Salvar para manter seu trabalho.",
|
||||
"no_clones": "Ainda não há clones de voz",
|
||||
"no_clones_hint": "Grave ou carregue áudio e clique em Salvar como perfil de voz.",
|
||||
"no_designs": "Nenhuma voz projetada ainda",
|
||||
"no_designs_hint": "Gere uma voz e salve-a no Histórico.",
|
||||
"history_subtitle": "Histórico de geração · Armazenado em SQLite",
|
||||
"no_history": "Sem histórico de geração",
|
||||
"no_history_hint": "Sintetize áudio ou duble um vídeo – os resultados aparecerão aqui.",
|
||||
"clear_history": "Limpar histórico",
|
||||
"clear_confirm": "Limpar todos os itens do histórico {{count}}? Isto não pode ser desfeito.",
|
||||
"history_cleared": "Histórico apagado",
|
||||
"recent_exports": "Exportações recentes",
|
||||
"no_exports": "Nenhuma saída baixada",
|
||||
"no_exports_hint": "Exporte um arquivo via Tauri para vê-lo rastreado aqui.",
|
||||
"show_in_folder": "Mostrar na pasta",
|
||||
"open": "Abrir",
|
||||
"select": "Selecione",
|
||||
"try_voice": "Experimente",
|
||||
"consistent": "consistente",
|
||||
"locked": "Bloqueado",
|
||||
"clone_label": "Clonar",
|
||||
"design_label": "Projeto",
|
||||
"dub_label": "Duplicar",
|
||||
"save_label": "Salvar",
|
||||
"lock_identity": "Bloquear identidade de voz",
|
||||
"in_folder": "em {{folder}}"
|
||||
},
|
||||
"trimmer": {
|
||||
"title": "Cortar áudio de referência",
|
||||
"decoding": "Decodificando áudio…",
|
||||
"meta_length": "Comprimento {{duration}} · {{sampleRate}} Hz",
|
||||
"meta_rendering": "renderizando forma de onda {{percent}}%",
|
||||
"keyboard_hint": "scroll = zoom · shift+scroll = pan · alt+drag = pan · ⏐ ⟵ ⟶ ⏐ teclas ajustam as alças",
|
||||
"zoom_in": "Ampliar (+)",
|
||||
"zoom_out": "Diminuir zoom (-)",
|
||||
"fit_all": "Caber tudo (casa)",
|
||||
"fit_selection": "Seleção de ajuste (Fim)",
|
||||
"fit_sel_btn": "AJUSTE SEL",
|
||||
"view_range": "Visualizar {{start}} → {{end}} ({{duration}})",
|
||||
"start_label": "Começar",
|
||||
"end_label": "Fim",
|
||||
"length_label": "Comprimento",
|
||||
"too_long": ">{{max}}s",
|
||||
"too_short": "muito curto",
|
||||
"length_ok": "ok",
|
||||
"loop_preview": "Pré-visualização do loop",
|
||||
"pause": "Pausa",
|
||||
"preview_selection": "Seleção de visualização",
|
||||
"play_hint": "Espaço para jogar · Enter para confirmar · Esc para cancelar",
|
||||
"cancel": "Cancelar",
|
||||
"use_trimmed": "Use aparado",
|
||||
"decode_failed": "Falha na decodificação: {{message}}",
|
||||
"playback_failed": "Falha na reprodução: {{message}}",
|
||||
"audio_load_failed": "Falha ao carregar áudio",
|
||||
"unit_seconds": "é"
|
||||
},
|
||||
"casting": {
|
||||
"title": "Elenco de alto-falante",
|
||||
"auto_assign_title": "Atribuir automaticamente vozes de clones de alto-falantes extraídos",
|
||||
"auto_cast": "Transmissão automática",
|
||||
"all_cast": "Todo o elenco",
|
||||
"segments_count": "{{count}} segmentos",
|
||||
"assign_voice": "Atribuir voz…",
|
||||
"from_video": "🎤 Do vídeo ({{name}})",
|
||||
"no_profiles": "Nenhum perfil de voz salvo ainda.",
|
||||
"preview_voice": "Visualizar voz"
|
||||
},
|
||||
"checkpoint": {
|
||||
"asr_title": "Transcrições prontas",
|
||||
"asr_cta": "Traduzir",
|
||||
"asr_hint": "Corrija quaisquer erros de ASR agora – a dicção precisa salva tentativas de TTS posteriormente.",
|
||||
"translate_title": "Traduções prontas",
|
||||
"translate_cta": "Gerar dublagem",
|
||||
"translate_hint": "Dê uma olhada no texto de destino. Linhas longas aumentam a velocidade; você também pode editar diretamente.",
|
||||
"done_title": "Dublagem concluída",
|
||||
"done_hint": "Revise as proporções de tempo e sincronização. Ajuste qualquer linha e clique em \"Regen alterado\" para um refazer parcial rápido.",
|
||||
"segment_one": "segmento {{count}}",
|
||||
"segment_other": "{{count}} segmentos",
|
||||
"dismiss_title": "Dispensar – não reaparecerá nesta fase até recarregar"
|
||||
},
|
||||
"compare": {
|
||||
"title": "Comparação de voz A/B",
|
||||
"close": "Comparação aproximada",
|
||||
"desc": "Compare duas vozes lado a lado para tomar decisões de elenco. O aplicativo permanece interativo por trás.",
|
||||
"test_phrase": "Frase de teste",
|
||||
"voice_a": "Voz A",
|
||||
"voice_b": "Voz B",
|
||||
"select_voice": "— Selecione a voz —",
|
||||
"preset_suffix": "(Predefinido)",
|
||||
"no_audio": "Ainda não há áudio",
|
||||
"close_btn": "Fechar",
|
||||
"comparing": "Comparando…",
|
||||
"compare_btn": "Comparar",
|
||||
"preparing_voice": "Preparando voz...",
|
||||
"generating_voice_a": "Gerando Voz A...",
|
||||
"generating_voice_b": "Gerando Voz B...",
|
||||
"comparison_complete": "Comparação concluída!",
|
||||
"play_failed": "Falha na reprodução: {{message}}"
|
||||
},
|
||||
"models": {
|
||||
"hf_token_set_toast": "Conjunto de tokens HuggingFace – downloads mais rápidos habilitados",
|
||||
"hf_token_save_failed": "Falha ao salvar o token",
|
||||
"install_started": "Instalação iniciada — progresso na linha",
|
||||
"delete_confirm": "Excluir {{repoId}}? Você pode reinstalá-lo mais tarde.",
|
||||
"delete_confirm_title": "Excluir modelo",
|
||||
"deleted": "Excluído {{repoId}}",
|
||||
"reinstall_confirm": "Reinstalar {{repoId}}? Isso excluirá a cópia atual e fará o download novamente.",
|
||||
"reinstall_confirm_title": "Reinstale o modelo",
|
||||
"reinstalling": "Reinstalando",
|
||||
"recommended_installed": "Os modelos recomendados já estão instalados.",
|
||||
"started_downloading_one": "Iniciou o download do modelo {{count}}",
|
||||
"started_downloading_other": "Iniciou o download dos modelos {{count}}",
|
||||
"install_failed": "Falha na instalação: {{message}}",
|
||||
"removing_cached": "Removendo revisões em cache…",
|
||||
"resolving_metadata": "Resolvendo metadados do repositório",
|
||||
"retry_attempt": "Nova tentativa {{attempt}} — {{error}}",
|
||||
"connecting_hf": "Conectando-se ao HuggingFace…",
|
||||
"resolving_files_one": "Resolvendo arquivo {{count}}…",
|
||||
"resolving_files_other": "Resolvendo arquivos {{count}}…",
|
||||
"resolving_files_active": "Resolvendo arquivo(s) {{count}}… · {{file}}",
|
||||
"measuring": "medindo…",
|
||||
"files_progress": "Arquivos {{done}}/{{total}}",
|
||||
"install_error": "Falha na instalação: {{error}}",
|
||||
"view_on_hf": "Ver no HuggingFace",
|
||||
"install_btn": "Instalar",
|
||||
"reinstall_btn": "Reinstale",
|
||||
"downloading": "baixando",
|
||||
"deleting": "excluindo",
|
||||
"working": "trabalhando",
|
||||
"installed": "instalado",
|
||||
"not_installed": "não instalado",
|
||||
"required_tag": "obrigatório",
|
||||
"delete_btn": "Excluir",
|
||||
"hf_token_btn": "Token HF",
|
||||
"hf_set_title": "Defina o token HuggingFace para downloads mais rápidos",
|
||||
"get_token": "Obter token →",
|
||||
"reco_installed_for": "Pacote recomendado instalado para {{device}}",
|
||||
"reco_for": "Recomendado para {{device}}",
|
||||
"starting": "Começando…",
|
||||
"required_size": "Obrigatório ~{{size}} GB",
|
||||
"all_size": "Todos ~{{size}} GB",
|
||||
"req_tag": "solicitação",
|
||||
"search_placeholder": "Pesquisar modelos…",
|
||||
"search_label": "Pesquisar modelos",
|
||||
"no_matches": "Nenhum modelo corresponde aos seus filtros.",
|
||||
"sort_by": "Classificar por {{column}}",
|
||||
"column_model": "Modelo",
|
||||
"column_role": "Função",
|
||||
"column_size": "Tamanho",
|
||||
"column_status": "Estado",
|
||||
"ready_badge": "Pronto",
|
||||
"loading_badge": "Carregando…",
|
||||
"idle_badge": "Inativo",
|
||||
"started_downloading_required_one": "Iniciou o download do modelo necessário {{count}}",
|
||||
"started_downloading_required_other": "Iniciou o download de {{count}} modelos necessários"
|
||||
},
|
||||
"enterprise_faq": {
|
||||
"q_internal_tools": "Preciso de uma licença para ferramentas internas?",
|
||||
"a_internal_tools": "O uso interno por seus funcionários e contratados é uma Finalidade Permitida pela FSL — não é necessária licença. Uma licença comercial é necessária quando você disponibiliza o OmniVoice para terceiros como parte de um produto ou serviço concorrente (revenda, SaaS hospedado, marca branca).",
|
||||
"q_try_before": "Posso tentar antes de me comprometer?",
|
||||
"a_try_before": "Sim. O aplicativo completo pode ser baixado gratuitamente e executado localmente para avaliação no FSL. Quando você estiver pronto para discutir uma implantação comercial, envie-nos um e-mail e trabalharemos juntos nos detalhes.",
|
||||
"q_watermark": "E a marca d’água?",
|
||||
"a_watermark": "A marca d’água invisível AudioSeal é incorporada por padrão. Licenciados comerciais podem desativá-lo em Configurações → Privacidade. O uso gratuito/pessoal sempre inclui a marca d'água.",
|
||||
"q_apache": "A fonte alguma vez se torna Apache 2.0?",
|
||||
"a_apache": "Sim. Cada versão é convertida automaticamente para a Licença Apache, Versão 2.0, no segundo aniversário de sua publicação. Isso significa que o lançamento de hoje será o Apache 2.0 em dois anos, nenhuma ação será necessária de nossa parte — a FSL garante isso de forma irrevogável."
|
||||
},
|
||||
"gallery_extra": {
|
||||
"save_prompt": "Digite um nome para este perfil de voz:",
|
||||
"saved_as_profile": "Voz salva como perfil!",
|
||||
"save_profile_failed": "Falha ao salvar o perfil",
|
||||
"download_failed": "Falha no download: {{message}}",
|
||||
"trim_load_failed": "Falha ao carregar áudio para corte: {{message}}",
|
||||
"upload_crop_failed": "Falha ao enviar voz cortada: {{message}}"
|
||||
},
|
||||
"dub_workflow": {
|
||||
"preparing_audio": "Preparando áudio…",
|
||||
"preparing_video": "Preparing video…",
|
||||
"extracting_audio_scenes": "Extraindo áudio e cenas…",
|
||||
"transcribing_audio": "Transcribing audio…",
|
||||
"transcription_complete": "Transcription complete",
|
||||
"upload_cancelled": "Upload cancelled",
|
||||
"upload_failed": "Falha no upload: {{message}}",
|
||||
"downloading_video": "Baixando vídeo…",
|
||||
"ingested": "Ingested {{url}}",
|
||||
"ingest_cancelled": "Ingestão cancelada",
|
||||
"ingest_failed": "Falha na ingestão de URL: {{message}}",
|
||||
"retry_cancelled": "Retry cancelled",
|
||||
"transcription_failed": "Falha na transcrição: {{message}}",
|
||||
"import_srt_no_job": "Carregue ou ingira um vídeo primeiro – não há trabalho para anexar legendas.",
|
||||
"imported_cues": "Sugestão(s) {{count}} importada(s) de {{file}}",
|
||||
"skipped_malformed": "{{count}} ignorado (malformado)",
|
||||
"dropped_overlap": "{{count}} caiu (sobreposição)",
|
||||
"clamped_to_duration": "{{count}} preso ao comprimento da mídia",
|
||||
"srt_import_failed": "Falha na importação de SRT",
|
||||
"cleaned_one": "Cleaned {{count}} fragment",
|
||||
"cleaned_other": "Fragmentos {{count}} limpos",
|
||||
"segments_clean": "Segments already clean",
|
||||
"cleanup_failed": "Clean up failed: {{message}}",
|
||||
"cinematic_no_llm": "A qualidade cinematográfica precisa de um LLM - defina TRANSLATE_BASE_URL + TRANSLATE_API_KEY (Ollama funciona localmente). Voltando ao Fast.",
|
||||
"translate_errors": "{{errorCount}}/{{totalCount}} segmento(s) falhou: {{firstError}}",
|
||||
"translated_segments": "Segmento(s) {{count}} traduzido(s) → {{lang}}",
|
||||
"translated_cinematic_suffix": "(Cinemático)",
|
||||
"translation_failed": "Falha na tradução: {{message}}",
|
||||
"regenerating": "Regenerando segmento(s) {{count}}…",
|
||||
"generating_dub": "Gerando dublagem…",
|
||||
"generating_progress": "Gerando dublagem… {{current}}/{{total}}",
|
||||
"generation_aborted": "Geração abortada.",
|
||||
"dubbing_aborted": "Dubbing aborted",
|
||||
"generation_stream_ended": "O fluxo de geração terminou antes da conclusão",
|
||||
"dub_complete": "Dublagem concluída",
|
||||
"stop_failed": "Failed to stop",
|
||||
"save_first": "Clique primeiro em 'Carregar e transcrever' para que o vídeo seja processado no servidor antes de salvá-lo.",
|
||||
"project_saved": "Projeto salvo",
|
||||
"project_created": "Projeto criado",
|
||||
"save_failed": "Falha ao salvar: {{message}}",
|
||||
"opened_project": "Aberto: {{name}}",
|
||||
"delete_project_confirm": "Excluir este projeto? Isto não pode ser desfeito.",
|
||||
"project_deleted": "Projeto excluído",
|
||||
"delete_history_confirm": "Excluir este item do histórico?",
|
||||
"history_deleted": "Item do histórico excluído",
|
||||
"restored_state": "Estado restaurado da geração anterior",
|
||||
"upgrading_preview": "Atualizando segmento(s) de qualidade de visualização {{count}} para qualidade total…"
|
||||
},
|
||||
"tts_errors": {
|
||||
"enter_text": "Por favor insira o texto",
|
||||
"upload_or_select": "Carregue um áudio ou selecione um perfil de voz",
|
||||
"trim_hint": "O áudio é {{duration}}s – ajuste para ≤{{max}}s para melhor clonagem",
|
||||
"timeout": "O tempo de geração expirou — o modelo ainda pode estar sendo baixado. Verifique Configurações → Registros e tente novamente.",
|
||||
"error_prefix": "Erro: {{message}}",
|
||||
"ignored_unsupported": "Instrução não suportada ignorada: {{items}}",
|
||||
"ignored_duplicate": "Ignorado (categoria já definida): {{items}}"
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Compartilhamento e acesso remoto",
|
||||
"help": "Exponha esta instância do OmniVoice em execução às outras máquinas sem reiniciá-la. Somente loopback é o padrão – nada é compartilhado até que você o ative aqui.",
|
||||
"local_network": "Rede local",
|
||||
"local_help": "Compartilhe no seu Wi-Fi/Ethernet com um PIN de acesso único. Outros dispositivos escaneiam o código QR ou abrem o link.",
|
||||
"ports_title": "Portas",
|
||||
"ports_help": "Eles são definidos por meio de variáveis de ambiente lidas na inicialização. Altere o back-end ou a porta da UI definindo a variável e reiniciando o OmniVoice.",
|
||||
"backend_port": "Porta de back-end",
|
||||
"ui_port": "Porta da IU",
|
||||
"lan_share_port": "Porta de compartilhamento LAN",
|
||||
"port_error": "Insira uma porta entre 1024 e 65535",
|
||||
"port_saved": "Porta de compartilhamento LAN salva — aplica-se na próxima vez que você ativar o compartilhamento",
|
||||
"port_save_failed": "Não foi possível salvar a porta: {{message}}",
|
||||
"saving": "Salvando…",
|
||||
"ports_note": "As portas de back-end e UI se aplicam na reinicialização. A porta de compartilhamento LAN será aplicada na próxima vez que você ativar o compartilhamento.",
|
||||
"tailscale_title": "Tailscale (acesso remoto privado)",
|
||||
"tailscale_checking": "Verificando Tailscale…",
|
||||
"tailscale_absent": "Tailscale não detectado. Instale-o para acessar o OmniVoice com segurança de qualquer lugar em sua rede privada.",
|
||||
"tailscale_install": "Instale o Tailscale",
|
||||
"tailscale_running": "Tailscale está em execução. Sirva OmniVoice em sua rede privada.",
|
||||
"tailscale_not_logged_in": "Tailscale está instalado, mas não conectado. Inicie e faça login no Tailscale primeiro.",
|
||||
"tailscale_enabled": "Serviço Tailscale ativado",
|
||||
"tailscale_enable_failed": "Não foi possível ativar o Tailscale",
|
||||
"tailscale_enable_error": "Não foi possível ativar o Tailscale: {{message}}",
|
||||
"tailscale_disabled": "Serviço Tailscale desativado",
|
||||
"tailscale_disable_failed": "Não foi possível desativar o Tailscale",
|
||||
"tailscale_disable_error": "Não foi possível desativar o Tailscale: {{message}}",
|
||||
"tailscale_enabling": "Habilitando…",
|
||||
"tailscale_enable_btn": "Ativar veiculação Tailscale",
|
||||
"tailscale_disabling": "Desativando…",
|
||||
"tailscale_disable_btn": "Parar o serviço Tailscale",
|
||||
"tailscale_copy": "Copiar link",
|
||||
"tailscale_open": "Abrir no navegador",
|
||||
"copied": "Copiado",
|
||||
"tailscale_qr_alt": "Código QR para o URL Tailscale"
|
||||
},
|
||||
"reportBug": {
|
||||
"label": "Reportar um bug",
|
||||
"title": "Abre uma página pré-preenchida de problemas do GitHub em seu navegador. Nada é enviado até você clicar em Enviar."
|
||||
},
|
||||
"app": {
|
||||
"loading": "Carregando…",
|
||||
"trimmed_loaded": "Áudio cortado carregado",
|
||||
"toast_exported": "Exportado: {{name}}",
|
||||
"toast_export_failed": "Falha na exportação: {{message}}",
|
||||
"toast_open_folder_failed": "Não foi possível abrir a pasta: {{message}}",
|
||||
"toast_saving": "Salvando {{name}}...",
|
||||
"toast_saved": "Salvo: {{path}}",
|
||||
"toast_save_error": "Erro ao salvar: {{message}}",
|
||||
"toast_processing": "Processando {{name}}...",
|
||||
"toast_downloaded": "Baixado {{name}}",
|
||||
"toast_download_error": "Erro de download: {{message}}",
|
||||
"toast_upload_first": "Clique primeiro em 'Carregar e transcrever' para que o vídeo seja processado no servidor antes de salvá-lo.",
|
||||
"toast_save_failed": "Falha ao salvar: {{message}}",
|
||||
"toast_opened": "Aberto: {{name}}",
|
||||
"toast_project_deleted": "Projeto excluído",
|
||||
"toast_restored_state": "Estado restaurado da geração anterior",
|
||||
"toast_history_deleted": "Item do histórico excluído",
|
||||
"toast_flushed": "Liberado — RAM {{ram}}G · VRAM {{vram}}G{{unloaded}}",
|
||||
"toast_model_unloaded": "· modelo descarregado",
|
||||
"toast_flush_failed": "Falha na liberação: {{message}}",
|
||||
"toast_project_saved": "Projeto salvo",
|
||||
"toast_project_created": "Projeto criado"
|
||||
},
|
||||
"update": {
|
||||
"available": "Atualização {{version}} disponível",
|
||||
"install": "Instalar e reiniciar",
|
||||
"install_hint": "Baixe a atualização e reinicie na nova versão",
|
||||
"downloading": "Atualizando… {{pct}}%",
|
||||
"restart": "Reiniciar para atualizar",
|
||||
"busy": "Termine sua dublagem primeiro — depois instale a atualização.",
|
||||
"whats_new": "O que há de novo",
|
||||
"failed": "Falha na atualização",
|
||||
"retry": "Tentar novamente",
|
||||
"dismiss": "Dispensar"
|
||||
},
|
||||
"archetypes": {
|
||||
"featured": "Destaque",
|
||||
@@ -945,5 +1577,25 @@
|
||||
"facet_accent": "Sotaque",
|
||||
"facet_lang": "Idioma",
|
||||
"facet_whisper": "Sussurro"
|
||||
},
|
||||
"support": {
|
||||
"tab_support": "Suporte",
|
||||
"tab_license": "Licença Comercial",
|
||||
"toggle_label": "Suporte ou licença comercial",
|
||||
"other_ways": "Outras maneiras de ajudar",
|
||||
"star_github": "Estrela no GitHub",
|
||||
"join_discord": "Junte-se à discórdia"
|
||||
},
|
||||
"updates": {
|
||||
"tab": "Atualizações",
|
||||
"up_to_date": "Atualizado · v{{version}}",
|
||||
"check_now": "Verifique agora",
|
||||
"releases": "Lançamentos",
|
||||
"current": "atual",
|
||||
"prerelease": "visualização",
|
||||
"loading": "Carregando lançamentos…",
|
||||
"none": "Nenhum lançamento encontrado",
|
||||
"load_error": "Não foi possível carregar as versões (off-line?)",
|
||||
"retry_load": "Tentar novamente"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,4 @@
|
||||
{
|
||||
"update": {
|
||||
"available": "Доступно обновление {{version}}",
|
||||
"install": "Установить и перезапустить",
|
||||
"install_hint": "Загрузить обновление и перезапустить в новой версии",
|
||||
"downloading": "Обновление… {{pct}}%",
|
||||
"restart": "Перезапустить для обновления",
|
||||
"busy": "Сначала завершите дубляж — затем установите обновление."
|
||||
},
|
||||
"nav": {
|
||||
"clone": "Клонировать",
|
||||
"design": "Создать",
|
||||
@@ -16,7 +8,10 @@
|
||||
"launchpad": "Панель запуска",
|
||||
"gallery": "Галерея",
|
||||
"transcripts": "Стенограммы",
|
||||
"omnidrive": "ОмниДрайв"
|
||||
"omnidrive": "ОмниДрайв",
|
||||
"move_rail_right": "Сдвинуть рельс вправо",
|
||||
"move_rail_left": "Сдвинуть рельс влево",
|
||||
"flip_rail": "Перевернуть сторону рельса"
|
||||
},
|
||||
"settings": {
|
||||
"general": "Общие",
|
||||
@@ -45,7 +40,40 @@
|
||||
"ffmpeg_missing": "Не найдено",
|
||||
"ffmpeg_current": "Текущий путь",
|
||||
"ffmpeg_desc": "Установите собственный путь ffmpeg, если автоматическое обнаружение не удалось.",
|
||||
"ffmpeg_saved": "Установлен путь FFmpeg — перезапустите серверную часть, чтобы применить."
|
||||
"ffmpeg_saved": "Установлен путь FFmpeg — перезапустите серверную часть, чтобы применить.",
|
||||
"diagnostics_copied": "Диагностические данные скопированы. Вставьте их в отчет о проблеме.",
|
||||
"updater_desktop": "Программа обновлений работает только в настольном приложении.",
|
||||
"latest_version": "У вас последняя версия.",
|
||||
"logs_load_failed": "Не удалось загрузить журналы: {{message}}.",
|
||||
"clear_frontend_confirm": "Очистить буфер журнала внешнего интерфейса в памяти?",
|
||||
"clear_frontend_title": "Очистить журналы",
|
||||
"frontend_logs_cleared": "Журналы внешнего интерфейса очищены",
|
||||
"clear_tauri_confirm": "Обрезать файлы журналов на стороне Таури? ОС продолжит записывать новые записи.",
|
||||
"clear_tauri_title": "Очистить журналы Таури",
|
||||
"nothing_to_clear": "Очищать нечего — на диске пока нет файла журнала Tauri.",
|
||||
"cleared_tauri_one": "Очищен файл журнала {{count}} Tauri.",
|
||||
"cleared_tauri_other": "Очищены файлы журналов {{count}} Tauri.",
|
||||
"clear_tauri_failed": "Не удалось очистить журналы Таури: {{message}}.",
|
||||
"clear_backend_confirm": "Очистить серверную среду выполнения и журналы сбоев? Это невозможно отменить.",
|
||||
"clear_backend_title": "Очистить журналы",
|
||||
"backend_logs_cleared": "Серверные журналы очищены",
|
||||
"clear_backend_failed": "Не удалось очистить журналы",
|
||||
"copy_failed": "Не удалось скопировать: {{message}}",
|
||||
"update_check_failed": "Проверка обновлений не удалась: {{message}}",
|
||||
"save_failed": "Не удалось сохранить: {{message}}",
|
||||
"clear_failed": "Очистить не удалось: {{message}}",
|
||||
"engine_switched": "{{family}} → {{engine}}",
|
||||
"channel_set_failed": "Не удалось установить канал: {{message}}.",
|
||||
"updater_downloading": "Загрузка {{version}}…",
|
||||
"updater_installed": "Установил — перезапускаю.",
|
||||
"updater_available_title": "Доступно обновление",
|
||||
"updater_available_body": "Доступна версия {{version}}.\n\n{{notes}}\n\nСкачать и установить сейчас?",
|
||||
"updater_notes_fallback": "См. примечания к выпуску на GitHub.",
|
||||
"shortcut_load_failed": "Не удалось загрузить ярлык: {{message}}.",
|
||||
"shortcut_set": "Ярлык диктовки установлен на {{shortcut}}.",
|
||||
"shortcut_register_failed": "Не удалось зарегистрироваться: {{message}}",
|
||||
"shortcut_reset": "Сбросить настройки по умолчанию",
|
||||
"shortcut_reset_failed": "Сброс не удался: {{message}}"
|
||||
},
|
||||
"bootstrap": {
|
||||
"title": "OmniVoice Studio",
|
||||
@@ -69,7 +97,13 @@
|
||||
"waiting_output": "Ожидание вывода…",
|
||||
"auto_detect": "Автоопределение",
|
||||
"suggest_lang": "Переключить на Русский?",
|
||||
"select_lang": "Язык:"
|
||||
"select_lang": "Язык:",
|
||||
"region_global": "Глобальный (прямой)",
|
||||
"region_china": "Китай (зеркало)",
|
||||
"region_russia": "Россия (зеркало)",
|
||||
"region_restricted": "Ограничено (зеркало)",
|
||||
"unknown_error": "Неизвестная ошибка",
|
||||
"retrying": "Повторная попытка…"
|
||||
},
|
||||
"stories": {
|
||||
"title": "Редактор историй",
|
||||
@@ -158,7 +192,17 @@
|
||||
"reload": "Принудительная перезагрузка пользовательского интерфейса",
|
||||
"backend": "Бэкэнд",
|
||||
"frontend": "Внешний интерфейс",
|
||||
"tauri": "Тавр"
|
||||
"tauri": "Тавр",
|
||||
"cancelOp": "Отменить операцию",
|
||||
"dismiss": "Увольнять",
|
||||
"dismissStatus": "Отклонить статус",
|
||||
"search": "Поиск…",
|
||||
"no_matches": "Нет совпадений",
|
||||
"recent_and_popular": "Последние и популярные",
|
||||
"popular_label": "Популярный",
|
||||
"showing_of": "Показаны {{shown}} из {{total}}. Введите для поиска…",
|
||||
"yes": "Да",
|
||||
"no": "Нет"
|
||||
},
|
||||
"launchpad": {
|
||||
"greeting": "привет",
|
||||
@@ -180,7 +224,16 @@
|
||||
"locked": "ЗАБЛОКИРОВАНО",
|
||||
"open": "Открыть",
|
||||
"try_it": "Попробуйте это",
|
||||
"audio_only": "Только аудио"
|
||||
"audio_only": "Только аудио",
|
||||
"stories_title": "Истории",
|
||||
"stories_desc": "Многоголосые аудиокниги — выбирайте своих персонажей, добавляйте сценарий, экспортируйте по главам.",
|
||||
"gallery_title": "Голосовая галерея",
|
||||
"gallery_desc": "Просматривайте готовые голоса по акценту, возрасту и стилю — без настройки.",
|
||||
"transcripts_title": "Стенограммы",
|
||||
"transcripts_desc": "Превратите аудио или видео в редактируемый текст с возможностью поиска — на 646 языках.",
|
||||
"recent_files": "Последние файлы",
|
||||
"view_all_files": "Просмотреть все файлы",
|
||||
"file": "Файл"
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "Быстрый",
|
||||
@@ -271,11 +324,6 @@
|
||||
"outputs": "Выходы",
|
||||
"crash_log": "Журнал сбоев",
|
||||
"update_endpoint": "Обновить конечную точку",
|
||||
"update_channel": "Канал обновлений",
|
||||
"channel_stable": "Стабильный",
|
||||
"channel_preview": "Предварительный",
|
||||
"channel_set": "Канал обновлений установлен на {{channel}}",
|
||||
"channel_preview_hint": "Предварительный канал отслеживает последнюю сборку main — новые функции, меньше тестирования. Возвращается к стабильному, если стабильный выпуск новее.",
|
||||
"yes": "да",
|
||||
"no": "нет",
|
||||
"web_preview": "веб-предварительный просмотр",
|
||||
@@ -284,7 +332,12 @@
|
||||
"copy_diagnostics": "Копировать диагностику",
|
||||
"github": "OmniVoice на GitHub",
|
||||
"model_card": "Модель карты",
|
||||
"commercial_license": "Коммерческая лицензия"
|
||||
"commercial_license": "Коммерческая лицензия",
|
||||
"update_channel": "Канал обновлений",
|
||||
"channel_stable": "Стабильный",
|
||||
"channel_preview": "Предварительный",
|
||||
"channel_set": "Канал обновлений установлен на {{channel}}",
|
||||
"channel_preview_hint": "Предварительный канал отслеживает последнюю сборку main — новые функции, меньше тестирования. Возвращается к стабильному, если стабильный выпуск новее."
|
||||
},
|
||||
"privacy": {
|
||||
"desc": "Все работает на <1>этой машине</1>. Ваши аудио, видео и расшифровки никогда не покидают ваш компьютер, если вы явно не используете онлайн-переводчик (Google, DeepL и т. д.) или не отправляете их на HuggingFace.",
|
||||
@@ -340,7 +393,30 @@
|
||||
"unavailable": "недоступен",
|
||||
"use": "Использовать",
|
||||
"loading": "Загрузка двигателей…",
|
||||
"refresh": "Обновить"
|
||||
"refresh": "Обновить",
|
||||
"matrixTitle": "Матрица совместимости двигателей",
|
||||
"loadFailed": "Не удалось загрузить двигатели: {{message}}.",
|
||||
"couldNotLoad": "Не удалось загрузить двигатели: {{message}}",
|
||||
"retry": "Повторить попытку",
|
||||
"activeEngine": "Активен {{family}}: {{engine}}",
|
||||
"engineCompatLabel": "Совместимость с двигателем {{family}}",
|
||||
"active": "активный",
|
||||
"whyUnavailable": "Почему недоступен?",
|
||||
"lastError": "Последняя ошибка: {{error}}",
|
||||
"installedAndReady": "Установлен и готов",
|
||||
"notInstalled": "Не установлено",
|
||||
"available": "Доступный",
|
||||
"subprocessTitle": "Запускается в собственном подпроцессе + venv",
|
||||
"inProcessTitle": "Запускается в процессе OmniVoice Python.",
|
||||
"testEngine": "Тестовый двигатель",
|
||||
"testing": "Тестирование…",
|
||||
"recheck": "Перепроверить",
|
||||
"rechecking": "Повторная проверка…",
|
||||
"latencyMs": "{{ms}} мс",
|
||||
"failed": "неуспешный",
|
||||
"acceptLicense": "Принять лицензию",
|
||||
"noBackends": "Серверные части не зарегистрированы.",
|
||||
"switch_failed": "Не удалось переключить двигатель"
|
||||
},
|
||||
"capture": {
|
||||
"desc": "Глобальные горячие клавиши работают только в настольном приложении. Веб-интерфейс использует внутристраничный ярлык <1>Ctrl+Shift+Space</1>, пока окно находится в фокусе.",
|
||||
@@ -352,13 +428,55 @@
|
||||
"record_shortcut": "Ярлык записи",
|
||||
"recording": "Запись…",
|
||||
"save": "Сохранять",
|
||||
"reset_default": "Сбросить настройки по умолчанию"
|
||||
"reset_default": "Сбросить настройки по умолчанию",
|
||||
"listening_label": "Слушаю…",
|
||||
"transcribing_label": "Расшифровка…",
|
||||
"pasted": "Вставлено",
|
||||
"no_speech": "Речь не обнаружена",
|
||||
"mic_denied": "Доступ к микрофону запрещен",
|
||||
"mic_denied_toast": "Доступ к микрофону запрещен. {{hint}}",
|
||||
"mic_hint_mac": "macOS: откройте «Настройки системы» → «Конфиденциальность и безопасность» → «Микрофон» и включите OmniVoice.",
|
||||
"mic_hint_windows": "Windows: откройте «Настройки» → «Конфиденциальность и безопасность» → «Микрофон» и разрешите OmniVoice.",
|
||||
"mic_hint_linux": "Linux: убедитесь, что ваш пользователь находится в группе аудио и у WebView есть доступ к микрофону.",
|
||||
"transcription_failed": "Транскрипция не удалась: {{message}}"
|
||||
},
|
||||
"logs": {
|
||||
"no_tauri_log": "На диске пока нет журнала Tauri — запустите его через сборку рабочего стола, чтобы создать его.",
|
||||
"empty_frontend": "Записи консоли внешнего интерфейса пока не сохранены. Взаимодействуйте с приложением — здесь появится каждая консоль.*.",
|
||||
"empty_tauri": "Журнал Таури недоступен. Запускается только в оболочке рабочего стола.",
|
||||
"empty_backend": "Журнал выполнения пуст. Действия появятся здесь, когда серверная часть зарегистрирует их."
|
||||
"empty_backend": "Журнал выполнения пуст. Действия появятся здесь, когда серверная часть зарегистрирует их.",
|
||||
"title": "Журналы",
|
||||
"source_backend": "Бэкэнд",
|
||||
"source_frontend": "Внешний интерфейс",
|
||||
"source_tauri": "Тавр",
|
||||
"expand": "Развернуть журналы",
|
||||
"collapse": "Свернуть журналы",
|
||||
"expand_aria": "Развернуть панель журналов",
|
||||
"collapse_aria": "Свернуть панель журналов",
|
||||
"drag_resize": "Перетащите, чтобы изменить размер",
|
||||
"refresh": "Обновить",
|
||||
"refresh_aria": "Обновить журналы",
|
||||
"copy_visible": "Копировать видимый журнал",
|
||||
"copy_visible_aria": "Копировать видимый журнал",
|
||||
"clear": "Прозрачный",
|
||||
"clear_aria": "Очистить журнал",
|
||||
"report_issue": "Сообщить о проблеме (копировать диагностику)",
|
||||
"report_issue_aria": "Сообщить о проблеме",
|
||||
"close": "Закрывать",
|
||||
"close_aria": "Закрыть панель журналов",
|
||||
"join_discord": "Присоединяйтесь к нашему Discord",
|
||||
"join_discord_aria": "Присоединяйтесь к нашему сообществу Discord",
|
||||
"support_project": "Поддержите этот проект",
|
||||
"support_project_aria": "Поддержите этот проект",
|
||||
"empty_frontend_short": "Пока нет вывода на консоль внешнего интерфейса.",
|
||||
"empty_lines": "Никаких линий.",
|
||||
"all_clear": "✅ Все четко — проблем не обнаружено",
|
||||
"log_cleared": "Журнал {{source}} очищен",
|
||||
"clear_failed": "Очистить не удалось: {{message}}",
|
||||
"log_copied": "Скопирован журнал {{source}}.",
|
||||
"copy_failed": "Не удалось скопировать: {{message}}",
|
||||
"report_copied": "Диагностический отчет скопирован — вставьте его в задачу GitHub.",
|
||||
"report_failed": "Отчет не выполнен: {{message}}"
|
||||
},
|
||||
"voice_profile": {
|
||||
"test_text": "Привет — это тест этого голоса.",
|
||||
@@ -526,7 +644,17 @@
|
||||
"install_already": "{{engine}} уже установлен",
|
||||
"install_ok": "{{engine}} установлено",
|
||||
"install_failed": "Не удалось установить: {{message}}",
|
||||
"prep_elapsed": "{{time}} прошло"
|
||||
"prep_elapsed": "{{time}} прошло",
|
||||
"add_language": "Добавить язык",
|
||||
"search_languages": "Поиск языков…",
|
||||
"languages_selected_one": "{{count}} выбран язык",
|
||||
"languages_selected_other": "Выбрано {{count}} языков",
|
||||
"more_to_narrow": "+{{count}} еще — введите текст, чтобы сузить",
|
||||
"no_matches": "Нет совпадений",
|
||||
"diagnostic_copied": "Диагностика скопирована",
|
||||
"copy_failed": "Не удалось скопировать",
|
||||
"open_docs": "Открыть документы",
|
||||
"copy_diagnostic": "Копирование диагностики"
|
||||
},
|
||||
"glossary": {
|
||||
"title": "Глоссарий",
|
||||
@@ -601,7 +729,17 @@
|
||||
"more_actions_title": "Дополнительные действия",
|
||||
"speaker_pick": "Выбирать…",
|
||||
"speaker_title_detected": "Динамик — выберите из обнаруженных или введите собственное имя.",
|
||||
"speaker_title_custom": "Спикер — введите имя (клоны диаризизации не обнаружены)"
|
||||
"speaker_title_custom": "Спикер — введите имя (клоны диаризизации не обнаружены)",
|
||||
"time_edit_title": "Нажмите, чтобы изменить время начала (м:сс.с). Enter для фиксации, Esc для отмены.",
|
||||
"fit_fits": "Подходит",
|
||||
"fit_fits_title": "Звук естественной скорости помещается в слот.",
|
||||
"fit_overflows": "Переполнение +{{seconds}}с",
|
||||
"fit_overflows_title": "Переведенный текст был длиннее исходного слота на {{seconds}}s. Звук был жестко обрезан; сократите текст или переключите время на «Растянуть видео».",
|
||||
"fit_stretched": "Видео {{ratio}}×",
|
||||
"fit_stretched_title": "Режим растягивания видео: видео этого сегмента было замедлено до {{ratio}}×, чтобы соответствовать естественному звуку дубляжа.",
|
||||
"fit_compressed_title": "Звук TTS занимает {{pct}}% слота и сильно сжат.",
|
||||
"fit_audio_title": "Аудио поместилось внутри слота.",
|
||||
"fit_ratio_title": "Звук TTS занимает {{pct}}% слота."
|
||||
},
|
||||
"voice": {
|
||||
"personality": "Личность",
|
||||
@@ -696,10 +834,24 @@
|
||||
"status_running": "бег",
|
||||
"status_done": "сделанный",
|
||||
"status_failed": "неуспешный",
|
||||
"status_cancelled": "отменен"
|
||||
"status_cancelled": "отменен",
|
||||
"add_to_queue_title": "Добавить видео в очередь",
|
||||
"drop_hint_text": "Перетащите сюда видеофайлы или нажмите, чтобы просмотреть",
|
||||
"drop_formats": "MP4 · MOV · MKV · WEBM",
|
||||
"files_kicker": "ФАЙЛЫ ({{count}})",
|
||||
"file_size_mb": "{{size}} МБ",
|
||||
"target_languages": "ЦЕЛЕВЫЕ ЯЗЫКИ",
|
||||
"voice_kicker": "ГОЛОС",
|
||||
"default_option": "По умолчанию",
|
||||
"clone_profiles": "Клонировать профили",
|
||||
"presets": "Пресеты",
|
||||
"preserve_bg": "Сохранять фоновый звук (музыка/эффекты)",
|
||||
"estimate": "{{videos}} видео × {{langs}} языков = {{jobs}} заданий",
|
||||
"select_files_langs": "Выбор файлов и языков",
|
||||
"add_to_queue": "Добавить в очередь"
|
||||
},
|
||||
"gallery": {
|
||||
"title": "Галерея",
|
||||
"title": "OmniVoice Галерея",
|
||||
"search_placeholder": "Искать на YouTube…",
|
||||
"all_voices": "Все голоса ({{count}})",
|
||||
"no_voices": "Голосов пока нет",
|
||||
@@ -714,6 +866,14 @@
|
||||
"youtube_results": "Результаты YouTube ({{count}})",
|
||||
"clone_profile": "Клонировать профиль",
|
||||
"crop_audio": "Обрезать аудио",
|
||||
"cat_disney": "Дисней",
|
||||
"cat_anime": "Аниме",
|
||||
"cat_marvel": "Марвел/DC",
|
||||
"cat_celebs": "Знаменитости",
|
||||
"cat_politicians": "Политики",
|
||||
"cat_news": "Ведущие новостей",
|
||||
"cat_gaming": "Игры",
|
||||
"cat_books": "Книги/Фильмы",
|
||||
"subtitle": "Сотни готовых голосов — выберите один и вперед.",
|
||||
"zone_archetypes": "Архетипы",
|
||||
"zone_imports": "Мой импорт",
|
||||
@@ -818,7 +978,6 @@
|
||||
"badge": "Коммерческая лицензия",
|
||||
"hero_title": "Запуск голосов ИИ в производство",
|
||||
"hero_desc": "Исходный код OmniVoice Studio доступен по лицензии Functional Source License (FSL). Большинство пользователей могут оценить, создать прототип и даже развернуть внутри компании без коммерческого соглашения. Коммерческая лицензия вам понадобится только в том случае, если вы создаете конкурирующий продукт или услугу или если ваш вариант использования выходит за рамки FSL.",
|
||||
"hero_note": "Для создания конкурирующего продукта или услуги или масштабного развертывания (например, предоставления API с оплатой по факту использования) требуется коммерческая лицензия. Скоро появятся ценовые уровни, а пока свяжитесь с нами.",
|
||||
"why_title": "Почему компании выбирают OmniVoice",
|
||||
"pricing_title": "Цены",
|
||||
"faq_title": "Общие вопросы",
|
||||
@@ -838,7 +997,8 @@
|
||||
"benefit_source": "Ядро, доступное в исходном коде",
|
||||
"benefit_source_desc": "Полная видимость стека. Аудит, форк и адаптация в рамках условий лицензии.",
|
||||
"benefit_lang": "646 языков",
|
||||
"benefit_lang_desc": "Транскрибируйте, переводите и дублируйте на 646 языках с качеством человеческого уровня."
|
||||
"benefit_lang_desc": "Транскрибируйте, переводите и дублируйте на 646 языках с качеством человеческого уровня.",
|
||||
"hero_note": "Внутреннее использование — даже в больших масштабах — бесплатно в соответствии с FSL; Коммерческая лицензия требуется только для предложения OmniVoice другим лицам в качестве конкурирующего продукта или услуги (размещенный API или API с оплатой по факту использования, перепродаваемое приложение или приложение с белой маркировкой). Ценовые уровни будут объявлены в ближайшее время — свяжитесь с нами."
|
||||
},
|
||||
"exportModal": {
|
||||
"export": "Экспорт",
|
||||
@@ -927,7 +1087,479 @@
|
||||
"dubbing_title": "Посмотрите дубляж в действии",
|
||||
"dubbing_sync": "Синхронизированное воспроизведение",
|
||||
"dubbing_picker": "Попробуйте другой язык:",
|
||||
"dubbing_cta": "Запустите это на своем собственном видео →"
|
||||
"dubbing_cta": "Запустите это на своем собственном видео →",
|
||||
"dubbing_loading": "Загрузка демо-версии дубляжа…",
|
||||
"dubbing_dismiss": "Закрыть демонстрационный вариант дубляжа",
|
||||
"original_tag": "оригинальный",
|
||||
"dubbed_tag": "дублированный",
|
||||
"script_conversational": "Разговорный",
|
||||
"script_technical": "Технический словарь",
|
||||
"script_french": "Неанглийский (французский)",
|
||||
"aria_pause": "Пауза {{label}}",
|
||||
"aria_hear": "Слушайте {{label}}",
|
||||
"aria_replay": "Воспроизвести {{label}} через транскрибатор"
|
||||
},
|
||||
"direction": {
|
||||
"title": "Направление для сегмента №{{id}}",
|
||||
"desc": "Сообщите конвейеру, как должна ощущаться эта линия. Простой английский работает — система отображает ваши слова в стабильную таксономию (энергия / эмоции / темп / близость / формальность), а затем обрабатывает таксономию посредством кинематографического перевода, TTS и подгонки по слотам.",
|
||||
"label": "Направление",
|
||||
"lineHint": "Строка: «{{text}}»",
|
||||
"placeholder": "например настойчиво и удивленно / тепло, обнадеживающе / шепотом, интимно",
|
||||
"previewParse": "Предварительный анализ",
|
||||
"previewFailed": "Предварительный просмотр не удался: {{message}}",
|
||||
"clear": "Прозрачный",
|
||||
"cancel": "Отмена",
|
||||
"saveDirection": "Сохранить направление",
|
||||
"ttsInstruct": "ТТС инструктирует:",
|
||||
"nothingParsed": "— (ничего не разобрано)",
|
||||
"translateHint": "Переведите подсказку:",
|
||||
"rateBias": "Смещение ставки:",
|
||||
"speedsUp": "ускоряется",
|
||||
"slowsDown": "замедляется",
|
||||
"taxonomyTokens": "жетоны таксономии"
|
||||
},
|
||||
"errors": {
|
||||
"title": "Эта вкладка попала в загвоздку.",
|
||||
"desc": "Не волнуйтесь — остальная часть приложения по-прежнему работает. Вы можете переключить вкладки или повторить попытку ниже.",
|
||||
"tryAgain": "Попробуйте еще раз",
|
||||
"openDocs": "Открыть документацию по этой ошибке"
|
||||
},
|
||||
"keyboard": {
|
||||
"title": "Сочетания клавиш",
|
||||
"footer": "Нажмите <1>?</1> в любое время, чтобы открыть это.",
|
||||
"or": "или",
|
||||
"nav": "Навигация",
|
||||
"nav_cheatsheet": "Показать эту шпаргалку",
|
||||
"nav_closeModal": "Закрыть модально/отменить",
|
||||
"nav_save": "Сохранить проект/зафиксировать обрезку",
|
||||
"segmentEditor": "Редактор сегментов",
|
||||
"seg_split": "Разделить сегмент на курсоре",
|
||||
"seg_merge": "Объединить со следующим сегментом",
|
||||
"seg_undo": "Отменить",
|
||||
"seg_redo": "Повторить",
|
||||
"seg_click": "Первичное действие",
|
||||
"seg_shiftClick": "Выбор диапазона",
|
||||
"trimmer": "Аудио триммер",
|
||||
"trim_playPause": "Предварительный просмотр/пауза",
|
||||
"trim_nudgeStart": "Подтолкнуть ручку запуска",
|
||||
"trim_nudgeEnd": "Подтолкнуть концевую ручку",
|
||||
"trim_fineNudge": "Тонкий толчок",
|
||||
"trim_coarseNudge": "Грубое подталкивание",
|
||||
"trim_zoomIn": "Увеличение/уменьшение масштаба",
|
||||
"trim_fitAll": "Подходит всем/Подходит выбранному",
|
||||
"trim_confirm": "Подтвердить обрезку",
|
||||
"dub": "Дублировать",
|
||||
"dub_generate": "Создать дубляж",
|
||||
"dub_sidebar": "Переключить боковую панель"
|
||||
},
|
||||
"network": {
|
||||
"sharing_on_title": "Открыт доступ — нажмите, чтобы узнать подробности",
|
||||
"share_on_network": "Поделитесь в своей сети",
|
||||
"switching": "Переключение…",
|
||||
"network": "Сеть",
|
||||
"local": "Местный",
|
||||
"share_confirm_title": "Поделиться в своей сети?",
|
||||
"share_confirm_hint": "Другие устройства в вашей сети Wi-Fi/Ethernet смогут подключиться к OmniVoice, используя PIN-код доступа, отображаемый после его включения.",
|
||||
"enabling": "Включение…",
|
||||
"enable": "Давать возможность",
|
||||
"shared_title": "Доступно в вашей сети",
|
||||
"no_interface": "Нет доступного сетевого интерфейса — подключитесь к Wi-Fi/Ethernet.",
|
||||
"copy_link": "Копировать ссылку",
|
||||
"open_in_browser": "Открыть в браузере",
|
||||
"qr_alt": "QR для {{ip}}",
|
||||
"pin": "ПРИКОЛОТЬ:",
|
||||
"stop_sharing": "Прекратить делиться",
|
||||
"copied": "Скопировано",
|
||||
"enable_error": "Не удалось включить общий доступ: {{message}}",
|
||||
"disable_error": "Не удалось отключить: {{message}}"
|
||||
},
|
||||
"readiness": {
|
||||
"checking_system": "Проверка системы…",
|
||||
"all_ready": "Все системы готовы",
|
||||
"system_readiness": "Готовность системы",
|
||||
"asr_model": "Модель ASR",
|
||||
"loaded_ready": "Загружено и готово",
|
||||
"loading_first_run": "Загрузка… (при первом запуске это может занять 1–2 минуты)",
|
||||
"failed_to_load": "Не удалось загрузить",
|
||||
"not_loaded_yet": "Еще не загружено — загрузится при первой транскрипции",
|
||||
"error_check_logs": "Ошибка: {{error}}. Проверьте журналы и попробуйте перезагрузить компьютер.",
|
||||
"check_logs_restart": "Проверьте журналы на предмет ошибок загрузки модели. Попробуйте перезапустить.",
|
||||
"llm_cinematic": "LLM (кинематографический)",
|
||||
"llm_configure": "Настройте TRANSLATE_BASE_URL для кинематографического качества перевода.",
|
||||
"llm_set_env": "Установите переменные среды TRANSLATE_BASE_URL и TRANSLATE_API_KEY. Работает с Ollama, OpenAI, LM Studio и т. д.",
|
||||
"llm_optional": "Необязательно — установите TRANSLATE_BASE_URL для кинематографического качества."
|
||||
},
|
||||
"license": {
|
||||
"title": "Супертоник-3 — Принятие лицензии",
|
||||
"intro": "Supertonic-3 поставляется под двумя разными лицензиями. Пожалуйста, просмотрите оба варианта перед включением двигателя.",
|
||||
"sdk_heading": "Код SDK · MIT",
|
||||
"sdk_desc": "SDK вывода Python (супертонический) имеет лицензию MIT. Разрешительное использование, в том числе коммерческое.",
|
||||
"read_mit": "Читать лицензию MIT →",
|
||||
"model_heading": "Вес модели · OpenRAIL-M",
|
||||
"model_desc": "Гири модели Supertonic-3 выпускаются по лицензии OpenRAIL-M. Эта лицензия ограничивает использование незлонамеренными целями — полный набор ограничений на использование см. в связанной лицензии.",
|
||||
"read_openrail": "Читать лицензию OpenRAIL-M →",
|
||||
"footer": "Нажатие кнопки «Принять» зафиксирует ваше согласие в локальных настройках OmniVoice и активирует механизм. Ваше согласие сохраняется только на этом компьютере — Supertone Inc. или какой-либо третьей стороне ни о чем не сообщается.",
|
||||
"saving": "Сохранение…",
|
||||
"accept": "Принимать",
|
||||
"accepted_toast": "Лицензия Supertonic-3 принята.",
|
||||
"accept_error": "Не удалось записать принятие лицензии: {{message}}."
|
||||
},
|
||||
"voicePreview": {
|
||||
"title": "Голосовой предварительный просмотр",
|
||||
"close": "Закрыть предварительный просмотр",
|
||||
"default_text": "Привет! Это предварительный просмотр того, как я звучу этим голосом.",
|
||||
"default_voice": "Голос по умолчанию",
|
||||
"clone_profiles": "Клонировать профили",
|
||||
"designed_voices": "Созданные голоса",
|
||||
"presets": "Пресеты",
|
||||
"placeholder": "Введите что-нибудь, чтобы услышать…",
|
||||
"stop": "Останавливаться",
|
||||
"regenerate": "Регенерировать",
|
||||
"preview": "Предварительный просмотр",
|
||||
"hint": "8 шагов · быстрый просмотр"
|
||||
},
|
||||
"header": {
|
||||
"kicker_studio": "Студия",
|
||||
"kicker_library": "Библиотека",
|
||||
"kicker_preferences": "Предпочтения",
|
||||
"kicker_licensing": "Лицензирование",
|
||||
"label_launchpad": "Панель запуска",
|
||||
"label_clone": "Голосовой клон",
|
||||
"label_design": "Голосовой дизайн",
|
||||
"label_dub": "Дубляж",
|
||||
"label_projects": "ОмниДрайв",
|
||||
"label_gallery": "Галерея",
|
||||
"label_transcriptions": "Транскрипции",
|
||||
"label_settings": "Настройки",
|
||||
"label_enterprise": "Коммерческая лицензия",
|
||||
"status_ready": "Готовый",
|
||||
"status_loading": "Загрузка…",
|
||||
"status_idle": "Праздный",
|
||||
"memory_management": "Управление памятью",
|
||||
"flush": "Румянец",
|
||||
"loaded_models": "Загруженные модели",
|
||||
"no_models": "Ни одна модель не загружена",
|
||||
"unload": "Разгрузить",
|
||||
"flush_caches": "Очистить кеши",
|
||||
"unload_all_flush": "Выгрузить все + сбросить"
|
||||
},
|
||||
"sidebar": {
|
||||
"tab_drive": "Водить машину",
|
||||
"tab_history": "История",
|
||||
"tab_exports": "Экспорт",
|
||||
"save_project": "Сохранить проект дубляжа",
|
||||
"save_new_project": "Сохранить как новый проект дубляжа",
|
||||
"dub_projects": "Дублирующие проекты",
|
||||
"voice_clones": "Голосовые клоны",
|
||||
"designed_voices": "Созданные голоса",
|
||||
"no_dub_projects": "Нет сохраненных проектов дубляжа.",
|
||||
"no_dub_hint": "Загрузите видео и нажмите «Сохранить», чтобы сохранить свою работу.",
|
||||
"no_clones": "Голосовых клонов пока нет",
|
||||
"no_clones_hint": "Запишите или загрузите аудио, затем нажмите «Сохранить как голосовой профиль».",
|
||||
"no_designs": "Созданных голосов пока нет",
|
||||
"no_designs_hint": "Создайте голос и сохраните его из истории.",
|
||||
"history_subtitle": "История генерации · Хранится в SQLite",
|
||||
"no_history": "Нет истории поколений",
|
||||
"no_history_hint": "Синтезируйте аудио или озвучьте видео — здесь появятся результаты.",
|
||||
"clear_history": "Очистить историю",
|
||||
"clear_confirm": "Очистить все элементы истории {{count}}? Это невозможно отменить.",
|
||||
"history_cleared": "История очищена",
|
||||
"recent_exports": "Недавний экспорт",
|
||||
"no_exports": "Нет загруженных результатов",
|
||||
"no_exports_hint": "Экспортируйте файл через Tauri, чтобы увидеть, как он отслеживается здесь.",
|
||||
"show_in_folder": "Показать в папке",
|
||||
"open": "Открыть",
|
||||
"select": "Выбирать",
|
||||
"try_voice": "Пытаться",
|
||||
"consistent": "последовательный",
|
||||
"locked": "Заблокировано",
|
||||
"clone_label": "Клонировать",
|
||||
"design_label": "Дизайн",
|
||||
"dub_label": "Дублировать",
|
||||
"save_label": "Сохранять",
|
||||
"lock_identity": "Заблокировать голосовую идентификацию",
|
||||
"in_folder": "в {{folder}}"
|
||||
},
|
||||
"trimmer": {
|
||||
"title": "Обрезать эталонный звук",
|
||||
"decoding": "Декодирование звука…",
|
||||
"meta_length": "Длина {{duration}} · {{sampleRate}} Гц",
|
||||
"meta_rendering": "рендеринг формы сигнала {{percent}}%",
|
||||
"keyboard_hint": "прокрутка = масштабирование · Shift+прокрутка = панорамирование · alt+перетаскивание = панорамирование · клавиши ⏐ ⟵ ⟶ ⏐ регулировка маркеров",
|
||||
"zoom_in": "Увеличить (+)",
|
||||
"zoom_out": "Уменьшить масштаб (-)",
|
||||
"fit_all": "Подходит всем (Домой)",
|
||||
"fit_selection": "Выбор подгонки (конец)",
|
||||
"fit_sel_btn": "ПОДХОДИТ ВЫБОР",
|
||||
"view_range": "Просмотр {{start}} → {{end}} ({{duration}})",
|
||||
"start_label": "Начинать",
|
||||
"end_label": "Конец",
|
||||
"length_label": "Длина",
|
||||
"too_long": ">{{max}}с",
|
||||
"too_short": "слишком короткий",
|
||||
"length_ok": "хорошо",
|
||||
"loop_preview": "Предварительный просмотр цикла",
|
||||
"pause": "Пауза",
|
||||
"preview_selection": "Предварительный выбор",
|
||||
"play_hint": "Пробел для игры · Enter для подтверждения · Esc для отмены",
|
||||
"cancel": "Отмена",
|
||||
"use_trimmed": "Используйте обрезанные",
|
||||
"decode_failed": "Декодирование не удалось: {{message}}",
|
||||
"playback_failed": "Не удалось воспроизвести: {{message}}",
|
||||
"audio_load_failed": "Не удалось загрузить аудио.",
|
||||
"unit_seconds": "с"
|
||||
},
|
||||
"casting": {
|
||||
"title": "Кастинг спикеров",
|
||||
"auto_assign_title": "Автоматическое назначение голосов из извлеченных клонов динамиков",
|
||||
"auto_cast": "Автотрансляция",
|
||||
"all_cast": "Все актеры",
|
||||
"segments_count": "{{count}} сегментов",
|
||||
"assign_voice": "Назначить голос…",
|
||||
"from_video": "🎤 Из видео ({{name}})",
|
||||
"no_profiles": "Голосовые профили пока не сохранены.",
|
||||
"preview_voice": "Предварительный просмотр голоса"
|
||||
},
|
||||
"checkpoint": {
|
||||
"asr_title": "Стенограммы готовы",
|
||||
"asr_cta": "Переводить",
|
||||
"asr_hint": "Исправьте все ошибки ASR сейчас — четкая дикция сохранит попытки TTS позже.",
|
||||
"translate_title": "Переводы готовы",
|
||||
"translate_cta": "Создать дубляж",
|
||||
"translate_hint": "Просмотрите целевой текст. Слишком длинные линии увеличивают скорость; вы также можете редактировать напрямую.",
|
||||
"done_title": "Дублирование завершено",
|
||||
"done_hint": "Просмотрите тайминг и коэффициенты синхронизации. Измените любую строку и нажмите «Регенерация изменена» для быстрого частичного повтора.",
|
||||
"segment_one": "{{count}} сегмент",
|
||||
"segment_other": "{{count}} сегментов",
|
||||
"dismiss_title": "Пропустить — не появится снова на этом этапе до перезагрузки."
|
||||
},
|
||||
"compare": {
|
||||
"title": "Сравнение голосов A/B",
|
||||
"close": "Близкое сравнение",
|
||||
"desc": "Сравните два голоса рядом, чтобы принять решение о кастинге. Приложение остается интерактивным.",
|
||||
"test_phrase": "Тестовая фраза",
|
||||
"voice_a": "Голос А",
|
||||
"voice_b": "Голос Б",
|
||||
"select_voice": "— Выбор голоса —",
|
||||
"preset_suffix": "(предустановлено)",
|
||||
"no_audio": "Звука пока нет",
|
||||
"close_btn": "Закрывать",
|
||||
"comparing": "Сравнивая…",
|
||||
"compare_btn": "Сравнивать",
|
||||
"preparing_voice": "Готовим голос...",
|
||||
"generating_voice_a": "Генерация голоса А...",
|
||||
"generating_voice_b": "Генерация голоса Б...",
|
||||
"comparison_complete": "Сравнение завершено!",
|
||||
"play_failed": "Игра не удалась: {{message}}"
|
||||
},
|
||||
"models": {
|
||||
"hf_token_set_toast": "Набор токенов HuggingFace — включена более быстрая загрузка",
|
||||
"hf_token_save_failed": "Не удалось сохранить токен",
|
||||
"install_started": "Установка началась — прогресс по строке",
|
||||
"delete_confirm": "Удалить {{repoId}}? Вы можете переустановить его позже.",
|
||||
"delete_confirm_title": "Удалить модель",
|
||||
"deleted": "Удален {{repoId}}",
|
||||
"reinstall_confirm": "Переустановить {{repoId}}? Это приведет к удалению текущей копии и повторной загрузке.",
|
||||
"reinstall_confirm_title": "Переустановить модель",
|
||||
"reinstalling": "Переустановка",
|
||||
"recommended_installed": "Рекомендуемые модели уже установлены.",
|
||||
"started_downloading_one": "Начал скачивание модели {{count}}.",
|
||||
"started_downloading_other": "Начата загрузка моделей {{count}}.",
|
||||
"install_failed": "Не удалось установить: {{message}}",
|
||||
"removing_cached": "Удаление кэшированных версий…",
|
||||
"resolving_metadata": "Разрешение метаданных репо",
|
||||
"retry_attempt": "Повторная попытка {{attempt}} — {{error}}",
|
||||
"connecting_hf": "Подключение к HuggingFace…",
|
||||
"resolving_files_one": "Разрешение файла {{count}}…",
|
||||
"resolving_files_other": "Разрешение файлов {{count}}…",
|
||||
"resolving_files_active": "Разрешение файлов {{count}}… · {{file}}",
|
||||
"measuring": "измерение…",
|
||||
"files_progress": "{{done}}/{{total}} файлов",
|
||||
"install_error": "Не удалось установить: {{error}}",
|
||||
"view_on_hf": "Посмотреть на HuggingFace",
|
||||
"install_btn": "Установить",
|
||||
"reinstall_btn": "Переустановить",
|
||||
"downloading": "загрузка",
|
||||
"deleting": "удаление",
|
||||
"working": "работающий",
|
||||
"installed": "установлен",
|
||||
"not_installed": "не установлен",
|
||||
"required_tag": "необходимый",
|
||||
"delete_btn": "Удалить",
|
||||
"hf_token_btn": "HF-токен",
|
||||
"hf_set_title": "Установите токен HuggingFace для более быстрой загрузки",
|
||||
"get_token": "Получить токен →",
|
||||
"reco_installed_for": "Рекомендуемый пакет установлен для {{device}}",
|
||||
"reco_for": "Рекомендуется для {{device}}",
|
||||
"starting": "Начало…",
|
||||
"required_size": "Требуется ~{{size}} ГБ",
|
||||
"all_size": "Все ~{{size}} ГБ",
|
||||
"req_tag": "запрос",
|
||||
"search_placeholder": "Поиск моделей…",
|
||||
"search_label": "Поиск моделей",
|
||||
"no_matches": "Ни одна модель не соответствует вашим фильтрам.",
|
||||
"sort_by": "Сортировать по {{column}}",
|
||||
"column_model": "Модель",
|
||||
"column_role": "Роль",
|
||||
"column_size": "Размер",
|
||||
"column_status": "Статус",
|
||||
"ready_badge": "Готовый",
|
||||
"loading_badge": "Загрузка…",
|
||||
"idle_badge": "Праздный",
|
||||
"started_downloading_required_one": "Начата загрузка {{count}} необходимой модели.",
|
||||
"started_downloading_required_other": "Начата загрузка {{count}} необходимых моделей."
|
||||
},
|
||||
"enterprise_faq": {
|
||||
"q_internal_tools": "Нужна ли мне лицензия на внутренние инструменты?",
|
||||
"a_internal_tools": "Внутреннее использование вашими сотрудниками и подрядчиками является разрешенной целью согласно FSL — лицензия не требуется. Коммерческая лицензия необходима, когда вы предоставляете OmniVoice другим лицам как часть конкурирующего продукта или услуги (перепродажа, размещение SaaS, белая этикетка).",
|
||||
"q_try_before": "Могу ли я попробовать, прежде чем совершать?",
|
||||
"a_try_before": "Да. Полную версию приложения можно бесплатно загрузить и запустить локально для оценки в соответствии с FSL. Когда вы будете готовы обсудить коммерческое развертывание, напишите нам, и мы вместе обсудим детали.",
|
||||
"q_watermark": "А что насчет водяного знака?",
|
||||
"a_watermark": "Невидимый водяной знак AudioSeal встроен по умолчанию. Обладатели коммерческих лицензий могут отключить его в «Настройки» → «Конфиденциальность». Бесплатное/личное использование всегда включает водяной знак.",
|
||||
"q_apache": "Исходный код когда-нибудь станет Apache 2.0?",
|
||||
"a_apache": "Да. Каждый выпуск автоматически преобразуется в лицензию Apache версии 2.0 во вторую годовщину его публикации. Это означает, что сегодняшний выпуск — это Apache 2.0 через два года, от нас не требуется никаких действий — FSL гарантирует это безотзывно."
|
||||
},
|
||||
"gallery_extra": {
|
||||
"save_prompt": "Введите имя для этого голосового профиля:",
|
||||
"saved_as_profile": "Голос сохранен как профиль!",
|
||||
"save_profile_failed": "Не удалось сохранить профиль",
|
||||
"download_failed": "Загрузка не удалась: {{message}}",
|
||||
"trim_load_failed": "Не удалось загрузить аудио для обрезки: {{message}}.",
|
||||
"upload_crop_failed": "Не удалось загрузить обрезанный голос: {{message}}."
|
||||
},
|
||||
"dub_workflow": {
|
||||
"preparing_audio": "Подготовка аудио…",
|
||||
"preparing_video": "Готовлю видео…",
|
||||
"extracting_audio_scenes": "Извлечение аудио и сцен…",
|
||||
"transcribing_audio": "Расшифровка аудио…",
|
||||
"transcription_complete": "Транскрипция завершена",
|
||||
"upload_cancelled": "Загрузка отменена",
|
||||
"upload_failed": "Не удалось загрузить: {{message}}",
|
||||
"downloading_video": "Загрузка видео…",
|
||||
"ingested": "Загружено {{url}}",
|
||||
"ingest_cancelled": "Загрузка отменена",
|
||||
"ingest_failed": "Не удалось получить URL-адрес: {{message}}",
|
||||
"retry_cancelled": "Повторная попытка отменена",
|
||||
"transcription_failed": "Транскрипция не удалась: {{message}}",
|
||||
"import_srt_no_job": "Сначала загрузите или импортируйте видео — прикрепить субтитры к нему не нужно.",
|
||||
"imported_cues": "Импортированы реплики {{count}} из {{file}}.",
|
||||
"skipped_malformed": "{{count}} пропущен (неверный формат)",
|
||||
"dropped_overlap": "{{count}} удалено (перекрытие)",
|
||||
"clamped_to_duration": "{{count}} зафиксирован по длине носителя",
|
||||
"srt_import_failed": "Импорт SRT не удался",
|
||||
"cleaned_one": "Очищен фрагмент {{count}}.",
|
||||
"cleaned_other": "Очищены фрагменты {{count}}.",
|
||||
"segments_clean": "Сегменты уже чистые",
|
||||
"cleanup_failed": "Очистка не удалась: {{message}}",
|
||||
"cinematic_no_llm": "Для кинематографического качества требуется LLM — установите TRANSLATE_BASE_URL + TRANSLATE_API_KEY (Ollama работает локально). Возвращаемся к Фасту.",
|
||||
"translate_errors": "Сегмент(ы) {{errorCount}}/{{totalCount}} не удалось: {{firstError}}",
|
||||
"translated_segments": "Переведено сегмент(ов) {{count}} → {{lang}}",
|
||||
"translated_cinematic_suffix": "(Кинематографический)",
|
||||
"translation_failed": "Перевод не выполнен: {{message}}",
|
||||
"regenerating": "Регенерация сегментов {{count}}…",
|
||||
"generating_dub": "Создание дубляжа…",
|
||||
"generating_progress": "Создание дубляжа… {{current}}/{{total}}",
|
||||
"generation_aborted": "Генерация прервана.",
|
||||
"dubbing_aborted": "Дублирование прервано",
|
||||
"generation_stream_ended": "Поток генерации завершился до завершения",
|
||||
"dub_complete": "Дублирование завершено",
|
||||
"stop_failed": "Не удалось остановить",
|
||||
"save_first": "Сначала нажмите «Загрузить и расшифровать», чтобы видео обработалось на сервере перед сохранением.",
|
||||
"project_saved": "Проект сохранен.",
|
||||
"project_created": "Проект создан",
|
||||
"save_failed": "Не удалось сохранить: {{message}}",
|
||||
"opened_project": "Открыто: {{name}}",
|
||||
"delete_project_confirm": "Удалить этот проект? Это невозможно отменить.",
|
||||
"project_deleted": "Проект удален",
|
||||
"delete_history_confirm": "Удалить этот элемент истории?",
|
||||
"history_deleted": "Элемент истории удален.",
|
||||
"restored_state": "Восстановлено состояние предыдущего поколения",
|
||||
"upgrading_preview": "Обновление сегментов предварительного просмотра {{count}} до полного качества…"
|
||||
},
|
||||
"tts_errors": {
|
||||
"enter_text": "Пожалуйста, введите текст",
|
||||
"upload_or_select": "Загрузите аудио или выберите голосовой профиль",
|
||||
"trim_hint": "Звук составляет {{duration}}s — обрезайте до ≤{{max}}s для лучшего клонирования",
|
||||
"timeout": "Время генерации истекло — возможно, модель все еще загружается. Откройте «Настройки» → «Журналы» и повторите попытку.",
|
||||
"error_prefix": "Ошибка: {{message}}",
|
||||
"ignored_unsupported": "Игнорируется неподдерживаемая инструкция: {{items}}",
|
||||
"ignored_duplicate": "Игнорируется (категория уже установлена): {{items}}"
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Совместное использование и удаленный доступ",
|
||||
"help": "Откройте доступ к этому работающему экземпляру OmniVoice другим компьютерам, не перезапуская его. По умолчанию используется только Loopback — ничего не передается, пока вы не включите его здесь.",
|
||||
"local_network": "Локальная сеть",
|
||||
"local_help": "Поделитесь своим Wi-Fi / Ethernet с помощью одноразового PIN-кода доступа. Другие устройства сканируют QR-код или открывают ссылку.",
|
||||
"ports_title": "Порты",
|
||||
"ports_help": "Они устанавливаются через переменные среды, считываемые при запуске. Измените внутренний порт или порт пользовательского интерфейса, задав переменную и перезапустив OmniVoice.",
|
||||
"backend_port": "Внутренний порт",
|
||||
"ui_port": "Порт пользовательского интерфейса",
|
||||
"lan_share_port": "Общий порт LAN",
|
||||
"port_error": "Введите порт между 1024 и 65535.",
|
||||
"port_saved": "Общий порт локальной сети сохранен — применяется при следующем включении общего доступа.",
|
||||
"port_save_failed": "Не удалось сохранить порт: {{message}}.",
|
||||
"saving": "Сохранение…",
|
||||
"ports_note": "Серверные порты и порты пользовательского интерфейса применяются при перезапуске. Порт LAN-share применяется при следующем включении общего доступа.",
|
||||
"tailscale_title": "Tailscale (частный удаленный доступ)",
|
||||
"tailscale_checking": "Проверка наличия накипи…",
|
||||
"tailscale_absent": "Хвостовая накипь не обнаружена. Установите его для безопасного доступа к OmniVoice из любой точки вашей частной сети.",
|
||||
"tailscale_install": "Установить хвостовую шкалу",
|
||||
"tailscale_running": "Tailscale работает. Обслуживайте OmniVoice через вашу частную сеть.",
|
||||
"tailscale_not_logged_in": "Tailscale установлен, но вход в систему не выполнен. Сначала запустите и войдите в Tailscale.",
|
||||
"tailscale_enabled": "Подача Tailscale включена",
|
||||
"tailscale_enable_failed": "Не удалось включить Tailscale",
|
||||
"tailscale_enable_error": "Не удалось включить Tailscale: {{message}}",
|
||||
"tailscale_disabled": "Подача Tailscale отключена",
|
||||
"tailscale_disable_failed": "Не удалось отключить Tailscale",
|
||||
"tailscale_disable_error": "Не удалось отключить Tailscale: {{message}}",
|
||||
"tailscale_enabling": "Включение…",
|
||||
"tailscale_enable_btn": "Включить обслуживание Tailscale",
|
||||
"tailscale_disabling": "Отключение…",
|
||||
"tailscale_disable_btn": "Остановить подачу Tailscale",
|
||||
"tailscale_copy": "Копировать ссылку",
|
||||
"tailscale_open": "Открыть в браузере",
|
||||
"copied": "Скопировано",
|
||||
"tailscale_qr_alt": "QR-код для URL-адреса Tailscale"
|
||||
},
|
||||
"reportBug": {
|
||||
"label": "Сообщить об ошибке",
|
||||
"title": "Открывает предварительно заполненную страницу проблем GitHub в вашем браузере. Ничего не будет отправлено, пока вы не нажмете «Отправить»."
|
||||
},
|
||||
"app": {
|
||||
"loading": "Загрузка…",
|
||||
"trimmed_loaded": "Обрезанный звук загружен.",
|
||||
"toast_exported": "Экспортировано: {{name}}",
|
||||
"toast_export_failed": "Не удалось экспортировать: {{message}}",
|
||||
"toast_open_folder_failed": "Не удалось открыть папку: {{message}}",
|
||||
"toast_saving": "Сохранение {{name}}...",
|
||||
"toast_saved": "Сохранено: {{path}}",
|
||||
"toast_save_error": "Ошибка сохранения: {{message}}",
|
||||
"toast_processing": "Обработка {{name}}...",
|
||||
"toast_downloaded": "Скачано {{name}}",
|
||||
"toast_download_error": "Ошибка загрузки: {{message}}",
|
||||
"toast_upload_first": "Сначала нажмите «Загрузить и расшифровать», чтобы видео обработалось на сервере перед сохранением.",
|
||||
"toast_save_failed": "Не удалось сохранить: {{message}}",
|
||||
"toast_opened": "Открыто: {{name}}",
|
||||
"toast_project_deleted": "Проект удален",
|
||||
"toast_restored_state": "Восстановлено состояние предыдущего поколения",
|
||||
"toast_history_deleted": "Элемент истории удален.",
|
||||
"toast_flushed": "Сброшено — ОЗУ {{ram}}G · VRAM {{vram}}G{{unloaded}}",
|
||||
"toast_model_unloaded": "· модель выгружена",
|
||||
"toast_flush_failed": "Сброс не выполнен: {{message}}",
|
||||
"toast_project_saved": "Проект сохранен.",
|
||||
"toast_project_created": "Проект создан"
|
||||
},
|
||||
"update": {
|
||||
"available": "Доступно обновление {{version}}",
|
||||
"install": "Установить и перезапустить",
|
||||
"install_hint": "Загрузить обновление и перезапустить в новой версии",
|
||||
"downloading": "Обновление… {{pct}}%",
|
||||
"restart": "Перезапустить для обновления",
|
||||
"busy": "Сначала завершите дубляж — затем установите обновление.",
|
||||
"whats_new": "Что нового",
|
||||
"failed": "Обновление не удалось",
|
||||
"retry": "Повторить попытку",
|
||||
"dismiss": "Увольнять"
|
||||
},
|
||||
"archetypes": {
|
||||
"featured": "Рекомендуемые",
|
||||
@@ -945,5 +1577,25 @@
|
||||
"facet_accent": "Акцент",
|
||||
"facet_lang": "Язык",
|
||||
"facet_whisper": "Шепот"
|
||||
},
|
||||
"support": {
|
||||
"tab_support": "Поддерживать",
|
||||
"tab_license": "Коммерческая лицензия",
|
||||
"toggle_label": "Поддержка или коммерческая лицензия",
|
||||
"other_ways": "Другие способы помочь",
|
||||
"star_github": "Звезда на GitHub",
|
||||
"join_discord": "Присоединяйтесь к Discord"
|
||||
},
|
||||
"updates": {
|
||||
"tab": "Обновления",
|
||||
"up_to_date": "Актуально · v{{version}}",
|
||||
"check_now": "Проверьте сейчас",
|
||||
"releases": "Релизы",
|
||||
"current": "текущий",
|
||||
"prerelease": "предварительный просмотр",
|
||||
"loading": "Загрузка выпусков…",
|
||||
"none": "Релизов не найдено",
|
||||
"load_error": "Не удалось загрузить выпуски (офлайн?)",
|
||||
"retry_load": "Повторить попытку"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,4 @@
|
||||
{
|
||||
"update": {
|
||||
"available": "Uppdatering {{version}} tillgänglig",
|
||||
"install": "Installera och starta om",
|
||||
"install_hint": "Ladda ner uppdateringen och starta om i den nya versionen",
|
||||
"downloading": "Uppdaterar… {{pct}} %",
|
||||
"restart": "Starta om för att uppdatera",
|
||||
"busy": "Slutför din dubbning först – installera sedan uppdateringen."
|
||||
},
|
||||
"nav": {
|
||||
"clone": "Klona",
|
||||
"design": "Designa",
|
||||
@@ -16,7 +8,10 @@
|
||||
"launchpad": "Launchpad",
|
||||
"gallery": "Galleri",
|
||||
"transcripts": "Avskrifter",
|
||||
"omnidrive": "OmniDrive"
|
||||
"omnidrive": "OmniDrive",
|
||||
"move_rail_right": "Flytta skenan till höger",
|
||||
"move_rail_left": "Flytta skenan till vänster",
|
||||
"flip_rail": "Flip rail sida"
|
||||
},
|
||||
"settings": {
|
||||
"general": "Allmänt",
|
||||
@@ -45,7 +40,40 @@
|
||||
"ffmpeg_missing": "Hittade inte",
|
||||
"ffmpeg_current": "Aktuell väg",
|
||||
"ffmpeg_desc": "Ställ in en anpassad ffmpeg-sökväg om automatisk upptäckt misslyckas.",
|
||||
"ffmpeg_saved": "FFmpeg sökväg set — starta om backend för att tillämpa."
|
||||
"ffmpeg_saved": "FFmpeg sökväg set — starta om backend för att tillämpa.",
|
||||
"diagnostics_copied": "Diagnostik kopierad – klistra in i din problemrapport.",
|
||||
"updater_desktop": "Updater körs bara i skrivbordsappen.",
|
||||
"latest_version": "Du har den senaste versionen.",
|
||||
"logs_load_failed": "Det gick inte att ladda loggar: {{message}}",
|
||||
"clear_frontend_confirm": "Rensa gränssnittsloggbufferten i minnet?",
|
||||
"clear_frontend_title": "Rensa loggar",
|
||||
"frontend_logs_cleared": "Frontend-loggar rensade",
|
||||
"clear_tauri_confirm": "Trunkera loggfilerna på Tauri-sidan? OS kommer att fortsätta att skriva nya poster.",
|
||||
"clear_tauri_title": "Rensa Tauri-loggar",
|
||||
"nothing_to_clear": "Inget att rensa — ingen Tauri-loggfil på disken ännu.",
|
||||
"cleared_tauri_one": "Rensad {{count}} Tauri-loggfil",
|
||||
"cleared_tauri_other": "Rensade {{count}} Tauri-loggfiler",
|
||||
"clear_tauri_failed": "Det gick inte att rensa Tauri-loggar: {{message}}",
|
||||
"clear_backend_confirm": "Rensa backend-runtime + kraschloggar? Detta kan inte ångras.",
|
||||
"clear_backend_title": "Rensa loggar",
|
||||
"backend_logs_cleared": "Backend-loggarna rensades",
|
||||
"clear_backend_failed": "Det gick inte att rensa loggar",
|
||||
"copy_failed": "Kopieringen misslyckades: {{message}}",
|
||||
"update_check_failed": "Uppdateringskontrollen misslyckades: {{message}}",
|
||||
"save_failed": "Det gick inte att spara: {{message}}",
|
||||
"clear_failed": "Rensa misslyckades: {{message}}",
|
||||
"engine_switched": "{{family}} → {{engine}}",
|
||||
"channel_set_failed": "Det gick inte att ställa in kanal: {{message}}",
|
||||
"updater_downloading": "Laddar ner {{version}}...",
|
||||
"updater_installed": "Installerad — omstart.",
|
||||
"updater_available_title": "Uppdatering tillgänglig",
|
||||
"updater_available_body": "Version {{version}} är tillgänglig.\n\n{{notes}}\n\nLadda ner och installera nu?",
|
||||
"updater_notes_fallback": "Se release notes på GitHub.",
|
||||
"shortcut_load_failed": "Det gick inte att läsa in genväg: {{message}}",
|
||||
"shortcut_set": "Dikteringsgenväg inställd på {{shortcut}}",
|
||||
"shortcut_register_failed": "Kunde inte registrera: {{message}}",
|
||||
"shortcut_reset": "Återställ till standard",
|
||||
"shortcut_reset_failed": "Återställning misslyckades: {{message}}"
|
||||
},
|
||||
"bootstrap": {
|
||||
"title": "OmniVoice Studio",
|
||||
@@ -69,7 +97,13 @@
|
||||
"waiting_output": "Väntar på utdata…",
|
||||
"auto_detect": "Identifiera automatiskt",
|
||||
"suggest_lang": "Byta till Svenska?",
|
||||
"select_lang": "Språk:"
|
||||
"select_lang": "Språk:",
|
||||
"region_global": "Global (direkt)",
|
||||
"region_china": "Kina (spegel)",
|
||||
"region_russia": "Ryssland (spegel)",
|
||||
"region_restricted": "Begränsad (spegel)",
|
||||
"unknown_error": "Okänt fel",
|
||||
"retrying": "Försöker igen..."
|
||||
},
|
||||
"stories": {
|
||||
"title": "Redaktör för berättelser",
|
||||
@@ -158,7 +192,17 @@
|
||||
"reload": "Tvinga gränssnittet att ladda om",
|
||||
"backend": "Backend",
|
||||
"frontend": "Frontend",
|
||||
"tauri": "Tauri"
|
||||
"tauri": "Tauri",
|
||||
"cancelOp": "Avbryt operation",
|
||||
"dismiss": "Avvisa",
|
||||
"dismissStatus": "Avvisa status",
|
||||
"search": "Sök...",
|
||||
"no_matches": "Inga matchningar",
|
||||
"recent_and_popular": "Senaste och populära",
|
||||
"popular_label": "Populärt",
|
||||
"showing_of": "Visar {{shown}} av {{total}}. Skriv för att söka...",
|
||||
"yes": "Ja",
|
||||
"no": "Nej"
|
||||
},
|
||||
"launchpad": {
|
||||
"greeting": "hej då",
|
||||
@@ -180,7 +224,16 @@
|
||||
"locked": "LÅST",
|
||||
"open": "Öppna",
|
||||
"try_it": "Prova det",
|
||||
"audio_only": "Endast ljud"
|
||||
"audio_only": "Endast ljud",
|
||||
"stories_title": "Berättelser",
|
||||
"stories_desc": "Ljudböcker med flera röster – cast dina karaktärer, släpp in ett manus, exportera efter kapitel.",
|
||||
"gallery_title": "Röstgalleri",
|
||||
"gallery_desc": "Bläddra bland färdigdesignade röster efter accent, ålder och stil – inga inställningar.",
|
||||
"transcripts_title": "Avskrifter",
|
||||
"transcripts_desc": "Förvandla ljud eller video till redigerbar, sökbar text – på 646 språk.",
|
||||
"recent_files": "Senaste filer",
|
||||
"view_all_files": "Visa alla filer",
|
||||
"file": "Arkiv"
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "Fråga",
|
||||
@@ -271,11 +324,6 @@
|
||||
"outputs": "Utgångar",
|
||||
"crash_log": "Krocklogg",
|
||||
"update_endpoint": "Uppdatera slutpunkt",
|
||||
"update_channel": "Uppdateringskanal",
|
||||
"channel_stable": "Stabil",
|
||||
"channel_preview": "Förhandsvisning",
|
||||
"channel_set": "Uppdateringskanal inställd på {{channel}}",
|
||||
"channel_preview_hint": "Förhandsvisning följer det senaste main-bygget – nyare funktioner, mindre testade. Återgår till stabil om en stabil version ligger före.",
|
||||
"yes": "ja",
|
||||
"no": "nej",
|
||||
"web_preview": "webbförhandsvisning",
|
||||
@@ -284,7 +332,12 @@
|
||||
"copy_diagnostics": "Kopiera diagnostik",
|
||||
"github": "OmniVoice på GitHub",
|
||||
"model_card": "Modellkort",
|
||||
"commercial_license": "Kommersiell licens"
|
||||
"commercial_license": "Kommersiell licens",
|
||||
"update_channel": "Uppdateringskanal",
|
||||
"channel_stable": "Stabil",
|
||||
"channel_preview": "Förhandsvisning",
|
||||
"channel_set": "Uppdateringskanal inställd på {{channel}}",
|
||||
"channel_preview_hint": "Förhandsvisning följer det senaste main-bygget – nyare funktioner, mindre testade. Återgår till stabil om en stabil version ligger före."
|
||||
},
|
||||
"privacy": {
|
||||
"desc": "Allt körs på <1>denna maskin</1>. Ditt ljud, video och transkriptioner lämnar aldrig din dator om du inte uttryckligen använder en onlineöversättare (Google, DeepL, etc.) eller trycker till HuggingFace.",
|
||||
@@ -340,7 +393,30 @@
|
||||
"unavailable": "otillgänglig",
|
||||
"use": "Använd",
|
||||
"loading": "Laddar motorer...",
|
||||
"refresh": "Uppdatera"
|
||||
"refresh": "Uppdatera",
|
||||
"matrixTitle": "Motorkompatibilitetsmatris",
|
||||
"loadFailed": "Det gick inte att ladda motorer: {{message}}",
|
||||
"couldNotLoad": "Kunde inte ladda motorer: {{message}}",
|
||||
"retry": "Försök igen",
|
||||
"activeEngine": "Aktiv {{family}}: {{engine}}",
|
||||
"engineCompatLabel": "{{family}} motorkompatibilitet",
|
||||
"active": "aktiv",
|
||||
"whyUnavailable": "Varför inte tillgänglig?",
|
||||
"lastError": "Senaste fel: {{error}}",
|
||||
"installedAndReady": "Installerad och klar",
|
||||
"notInstalled": "Ej installerad",
|
||||
"available": "Tillgänglig",
|
||||
"subprocessTitle": "Körs i sin egen delprocess + venv",
|
||||
"inProcessTitle": "Körs i OmniVoice Python-processen",
|
||||
"testEngine": "Testa motor",
|
||||
"testing": "Testar...",
|
||||
"recheck": "Kontrollera igen",
|
||||
"rechecking": "Kontrollerar igen...",
|
||||
"latencyMs": "{{ms}} ms",
|
||||
"failed": "misslyckades",
|
||||
"acceptLicense": "Acceptera licens",
|
||||
"noBackends": "Inga backends registrerade.",
|
||||
"switch_failed": "Det gick inte att byta motor"
|
||||
},
|
||||
"capture": {
|
||||
"desc": "Globala snabbtangenter fungerar bara i skrivbordsappen. Webbgränssnittet använder en genväg <1>Ctrl+Skift+Mellanslag</1> på sidan medan fönstret har fokus.",
|
||||
@@ -352,13 +428,55 @@
|
||||
"record_shortcut": "Spela in genväg",
|
||||
"recording": "Spelar in...",
|
||||
"save": "Spara",
|
||||
"reset_default": "Återställ till standard"
|
||||
"reset_default": "Återställ till standard",
|
||||
"listening_label": "Lyssnar...",
|
||||
"transcribing_label": "Transkriberar...",
|
||||
"pasted": "Klistras in",
|
||||
"no_speech": "Inget tal upptäckt",
|
||||
"mic_denied": "Mikrofonåtkomst nekad",
|
||||
"mic_denied_toast": "Mikrofonåtkomst nekad. {{hint}}",
|
||||
"mic_hint_mac": "macOS: öppna Systeminställningar → Sekretess och säkerhet → Mikrofon och aktivera OmniVoice.",
|
||||
"mic_hint_windows": "Windows: öppna Inställningar → Sekretess och säkerhet → Mikrofon och tillåt OmniVoice.",
|
||||
"mic_hint_linux": "Linux: kontrollera att din användare är i ljudgruppen och att WebView har mikrofonåtkomst.",
|
||||
"transcription_failed": "Transkription misslyckades: {{message}}"
|
||||
},
|
||||
"logs": {
|
||||
"no_tauri_log": "Ingen Tauri-inloggningsskiva än - starta via skrivbordsbygget för att skapa en",
|
||||
"empty_frontend": "Inga frontend-konsolposter har registrerats ännu. Interagera med appen — varje konsol* visas här.",
|
||||
"empty_tauri": "Ingen Tauri-logg tillgänglig. Körs endast i skrivbordsskalet.",
|
||||
"empty_backend": "Körtidsloggen är tom. Aktivitet kommer att visas här när backend loggar den."
|
||||
"empty_backend": "Körtidsloggen är tom. Aktivitet kommer att visas här när backend loggar den.",
|
||||
"title": "Loggar",
|
||||
"source_backend": "Backend",
|
||||
"source_frontend": "Frontend",
|
||||
"source_tauri": "Tauri",
|
||||
"expand": "Expandera loggar",
|
||||
"collapse": "Komprimera loggar",
|
||||
"expand_aria": "Expandera loggpanelen",
|
||||
"collapse_aria": "Komprimera loggpanelen",
|
||||
"drag_resize": "Dra för att ändra storlek",
|
||||
"refresh": "Uppdatera",
|
||||
"refresh_aria": "Uppdatera loggar",
|
||||
"copy_visible": "Kopiera synlig logg",
|
||||
"copy_visible_aria": "Kopiera synlig logg",
|
||||
"clear": "Rensa",
|
||||
"clear_aria": "Rensa logg",
|
||||
"report_issue": "Rapportera problem (kopieringsdiagnostik)",
|
||||
"report_issue_aria": "Rapportera problem",
|
||||
"close": "Stäng",
|
||||
"close_aria": "Stäng loggpanelen",
|
||||
"join_discord": "Gå med i vår Discord",
|
||||
"join_discord_aria": "Gå med i vår Discord-gemenskap",
|
||||
"support_project": "Stöd detta projekt",
|
||||
"support_project_aria": "Stöd detta projekt",
|
||||
"empty_frontend_short": "Ingen frontend-konsolutgång ännu.",
|
||||
"empty_lines": "Inga linjer.",
|
||||
"all_clear": "✅ Allt klart — inga problem upptäckts",
|
||||
"log_cleared": "{{source}} loggen rensad",
|
||||
"clear_failed": "Rensa misslyckades: {{message}}",
|
||||
"log_copied": "Kopierade {{source}}-logg",
|
||||
"copy_failed": "Kopieringen misslyckades: {{message}}",
|
||||
"report_copied": "Diagnostisk rapport kopierad – klistra in den i ett GitHub-problem.",
|
||||
"report_failed": "Rapport misslyckades: {{message}}"
|
||||
},
|
||||
"voice_profile": {
|
||||
"test_text": "Hej – det här är ett test av denna röst.",
|
||||
@@ -526,7 +644,17 @@
|
||||
"install_already": "{{engine}} var redan installerad",
|
||||
"install_ok": "{{engine}} installerad",
|
||||
"install_failed": "Installationen misslyckades: {{message}}",
|
||||
"prep_elapsed": "{{time}} har gått"
|
||||
"prep_elapsed": "{{time}} har gått",
|
||||
"add_language": "Lägg till språk",
|
||||
"search_languages": "Sök efter språk...",
|
||||
"languages_selected_one": "{{count}} språk valt",
|
||||
"languages_selected_other": "{{count}} språk har valts",
|
||||
"more_to_narrow": "+{{count}} mer — skriv för att begränsa",
|
||||
"no_matches": "Inga matchningar",
|
||||
"diagnostic_copied": "Diagnostiken kopierad",
|
||||
"copy_failed": "Kopieringen misslyckades",
|
||||
"open_docs": "Öppna dokument",
|
||||
"copy_diagnostic": "Kopiera diagnostik"
|
||||
},
|
||||
"glossary": {
|
||||
"title": "Ordlista",
|
||||
@@ -601,7 +729,17 @@
|
||||
"more_actions_title": "Fler åtgärder",
|
||||
"speaker_pick": "Välj...",
|
||||
"speaker_title_detected": "Högtalare – välj från upptäckt eller skriv ett anpassat namn",
|
||||
"speaker_title_custom": "Högtalare – skriv ett namn (inga diariseringskloner upptäcktes)"
|
||||
"speaker_title_custom": "Högtalare – skriv ett namn (inga diariseringskloner upptäcktes)",
|
||||
"time_edit_title": "Klicka för att redigera starttid (m:ss.s). Enter för att begå, Esc för att avbryta.",
|
||||
"fit_fits": "Passar",
|
||||
"fit_fits_title": "Naturligt ljud passar in i kortplatsen.",
|
||||
"fit_overflows": "Bräddar +{{seconds}}s",
|
||||
"fit_overflows_title": "Den översatta texten var längre än den ursprungliga platsen med {{seconds}}s. Ljudet var hårt trimmat; förkorta texten eller ändra Timing till \"Stretch Video\".",
|
||||
"fit_stretched": "Video {{ratio}}×",
|
||||
"fit_stretched_title": "Stretch Video-läge: detta segments video saktades ner till {{ratio}}× för att passa det naturliga dubbljudet.",
|
||||
"fit_compressed_title": "TTS-ljud är {{pct}}% av kortplatsen — kraftigt komprimerat.",
|
||||
"fit_audio_title": "Ljudet passar in i öppningen.",
|
||||
"fit_ratio_title": "TTS-ljud är {{pct}}% av kortplatsen."
|
||||
},
|
||||
"voice": {
|
||||
"personality": "Personlighet",
|
||||
@@ -696,10 +834,24 @@
|
||||
"status_running": "löpning",
|
||||
"status_done": "gjort",
|
||||
"status_failed": "misslyckades",
|
||||
"status_cancelled": "inställd"
|
||||
"status_cancelled": "inställd",
|
||||
"add_to_queue_title": "Lägg till videor i kön",
|
||||
"drop_hint_text": "Släpp videofiler här eller klicka för att bläddra",
|
||||
"drop_formats": "MP4 · MOV · MKV · WEBM",
|
||||
"files_kicker": "FILER ({{count}})",
|
||||
"file_size_mb": "{{size}} MB",
|
||||
"target_languages": "MÅLSPRÅK",
|
||||
"voice_kicker": "RÖST",
|
||||
"default_option": "Standard",
|
||||
"clone_profiles": "Klona profiler",
|
||||
"presets": "Förinställningar",
|
||||
"preserve_bg": "Bevara bakgrundsljud (musik/FX)",
|
||||
"estimate": "{{videos}} video(ar) × {{langs}} lång(ar) = {{jobs}} jobb(ar)",
|
||||
"select_files_langs": "Välj filer och språk",
|
||||
"add_to_queue": "Lägg till i kö"
|
||||
},
|
||||
"gallery": {
|
||||
"title": "Galleri",
|
||||
"title": "OmniVoice Galleri",
|
||||
"search_placeholder": "Sök på YouTube...",
|
||||
"all_voices": "Alla röster ({{count}})",
|
||||
"no_voices": "Inga röster än",
|
||||
@@ -714,6 +866,14 @@
|
||||
"youtube_results": "YouTube-resultat ({{count}})",
|
||||
"clone_profile": "Klona profil",
|
||||
"crop_audio": "Beskär ljud",
|
||||
"cat_disney": "Disney",
|
||||
"cat_anime": "Anime",
|
||||
"cat_marvel": "Marvel/DC",
|
||||
"cat_celebs": "Kändisar",
|
||||
"cat_politicians": "Politiker",
|
||||
"cat_news": "Företagsnyheter Anchors",
|
||||
"cat_gaming": "Spelande",
|
||||
"cat_books": "Böcker/filmer",
|
||||
"subtitle": "Hundratals färdigdesignade röster — välj en och gå.",
|
||||
"zone_archetypes": "Arketyper",
|
||||
"zone_imports": "Mina importer",
|
||||
@@ -818,7 +978,6 @@
|
||||
"badge": "Kommersiell licens",
|
||||
"hero_title": "Ship AI-röster i produktion",
|
||||
"hero_desc": "OmniVoice Studio är källtillgänglig under Functional Source License (FSL). De flesta användare kan utvärdera, prototyper och till och med distribuera internt utan ett kommersiellt avtal. Du behöver endast en kommersiell licens om du bygger en konkurrerande produkt eller tjänst, eller om ditt användningsfall faller utanför FSL:s gränser.",
|
||||
"hero_note": "Att bygga en konkurrerande produkt eller tjänst eller distribuera i stor skala (t.ex. betjäna pay-per-use API) kräver en kommersiell licens. Prisnivåer kommer snart – hör av dig under tiden.",
|
||||
"why_title": "Varför företag väljer OmniVoice",
|
||||
"pricing_title": "Prissättning",
|
||||
"faq_title": "Vanliga frågor",
|
||||
@@ -838,7 +997,8 @@
|
||||
"benefit_source": "Källa-tillgänglig kärna",
|
||||
"benefit_source_desc": "Full insyn i stapeln. Granska, fördela och anpassa inom licensvillkoren.",
|
||||
"benefit_lang": "646 språk",
|
||||
"benefit_lang_desc": "Transkribera, översätt och dubba på 646 språk med kvalitet på mänsklig nivå."
|
||||
"benefit_lang_desc": "Transkribera, översätt och dubba på 646 språk med kvalitet på mänsklig nivå.",
|
||||
"hero_note": "Intern användning – även i stor skala – är gratis enligt FSL; en kommersiell licens krävs endast för att erbjuda OmniVoice till andra som en konkurrerande produkt eller tjänst (ett värd- eller pay-per-use API, en vidaresåld eller vitmärkt app). Prisnivåer kommer snart - hör av dig under tiden."
|
||||
},
|
||||
"exportModal": {
|
||||
"export": "Exportera",
|
||||
@@ -927,7 +1087,479 @@
|
||||
"dubbing_title": "Se dubbning i aktion",
|
||||
"dubbing_sync": "Synkroniserad uppspelning",
|
||||
"dubbing_picker": "Prova ett annat språk:",
|
||||
"dubbing_cta": "Kör detta på din egen video →"
|
||||
"dubbing_cta": "Kör detta på din egen video →",
|
||||
"dubbing_loading": "Laddar dubbningsdemo...",
|
||||
"dubbing_dismiss": "Avvisa dubbningsdemon",
|
||||
"original_tag": "original",
|
||||
"dubbed_tag": "dubbat",
|
||||
"script_conversational": "Konversation",
|
||||
"script_technical": "Tekniskt ordförråd",
|
||||
"script_french": "Icke-engelska (franska)",
|
||||
"aria_pause": "Pausa {{label}}",
|
||||
"aria_hear": "Hör {{label}}",
|
||||
"aria_replay": "Spela om {{label}} genom transcriber"
|
||||
},
|
||||
"direction": {
|
||||
"title": "Riktning för segment #{{id}}",
|
||||
"desc": "Berätta för pipeline hur den här ledningen ska kännas. Vanlig engelska fungerar — systemet mappar dina ord till en stabil taxonomi (energi / känsla / takt / intimitet / formalitet), sedan trådar taxonomin genom filmisk översättning, TTS och slot-fit.",
|
||||
"label": "Riktning",
|
||||
"lineHint": "Rad: \"{{text}}\"",
|
||||
"placeholder": "t.ex. akut och förvånad / varm, hoppfull / viskande, intim",
|
||||
"previewParse": "Förhandsgranska tolka",
|
||||
"previewFailed": "Förhandsgranskning misslyckades: {{message}}",
|
||||
"clear": "Rensa",
|
||||
"cancel": "Avbryt",
|
||||
"saveDirection": "Spara riktning",
|
||||
"ttsInstruct": "TTS instruerar:",
|
||||
"nothingParsed": "— (inget tolkat)",
|
||||
"translateHint": "Översätt tips:",
|
||||
"rateBias": "Betygsbias:",
|
||||
"speedsUp": "snabbar upp",
|
||||
"slowsDown": "saktar ner",
|
||||
"taxonomyTokens": "taxonomi-tokens"
|
||||
},
|
||||
"errors": {
|
||||
"title": "Den här fliken träffade en hake.",
|
||||
"desc": "Oroa dig inte – resten av appen fungerar fortfarande. Du kan byta flik eller försöka igen nedan.",
|
||||
"tryAgain": "Försök igen",
|
||||
"openDocs": "Öppna dokument för detta fel"
|
||||
},
|
||||
"keyboard": {
|
||||
"title": "Kortkommandon",
|
||||
"footer": "Tryck på <1>?</1> när som helst för att öppna detta.",
|
||||
"or": "eller",
|
||||
"nav": "Navigering",
|
||||
"nav_cheatsheet": "Visa detta cheatsheet",
|
||||
"nav_closeModal": "Stäng modal / avbryt",
|
||||
"nav_save": "Spara projekt / commit trim",
|
||||
"segmentEditor": "Segmentredigerare",
|
||||
"seg_split": "Dela segment vid markören",
|
||||
"seg_merge": "Slå samman med nästa segment",
|
||||
"seg_undo": "Ångra",
|
||||
"seg_redo": "Gör om",
|
||||
"seg_click": "Primär åtgärd",
|
||||
"seg_shiftClick": "Välj intervall",
|
||||
"trimmer": "Audio trimmer",
|
||||
"trim_playPause": "Förhandsgranska spela/pausa",
|
||||
"trim_nudgeStart": "Tryck på starthandtaget",
|
||||
"trim_nudgeEnd": "Knuffa ändhandtaget",
|
||||
"trim_fineNudge": "Fin knuff",
|
||||
"trim_coarseNudge": "Grov knuff",
|
||||
"trim_zoomIn": "Zooma in/ut",
|
||||
"trim_fitAll": "Anpassa alla / Anpassa urval",
|
||||
"trim_confirm": "Bekräfta trimning",
|
||||
"dub": "Dub",
|
||||
"dub_generate": "Generera dub",
|
||||
"dub_sidebar": "Växla sidofältet"
|
||||
},
|
||||
"network": {
|
||||
"sharing_on_title": "Dela på — klicka för mer information",
|
||||
"share_on_network": "Dela på ditt nätverk",
|
||||
"switching": "Byter...",
|
||||
"network": "Nätverk",
|
||||
"local": "Lokalt",
|
||||
"share_confirm_title": "Dela i ditt nätverk?",
|
||||
"share_confirm_hint": "Andra enheter på ditt Wi-Fi/Ethernet kommer att kunna nå OmniVoice med den åtkomst-PIN som visas när den är på.",
|
||||
"enabling": "Aktiverar...",
|
||||
"enable": "Aktivera",
|
||||
"shared_title": "Delas i ditt nätverk",
|
||||
"no_interface": "Inget nåbart nätverksgränssnitt — anslut till Wi-Fi/Ethernet.",
|
||||
"copy_link": "Kopiera länk",
|
||||
"open_in_browser": "Öppna i webbläsaren",
|
||||
"qr_alt": "QR för {{ip}}",
|
||||
"pin": "PIN:",
|
||||
"stop_sharing": "Sluta dela",
|
||||
"copied": "Kopierade",
|
||||
"enable_error": "Kunde inte aktivera delning: {{message}}",
|
||||
"disable_error": "Kunde inte inaktivera: {{message}}"
|
||||
},
|
||||
"readiness": {
|
||||
"checking_system": "Kontrollerar systemet...",
|
||||
"all_ready": "Alla system redo",
|
||||
"system_readiness": "Systemberedskap",
|
||||
"asr_model": "ASR modell",
|
||||
"loaded_ready": "Laddat och klart",
|
||||
"loading_first_run": "Laddar... (detta kan ta 1-2 minuter vid första körningen)",
|
||||
"failed_to_load": "Det gick inte att ladda",
|
||||
"not_loaded_yet": "Inte laddad än – kommer att laddas vid första transkription",
|
||||
"error_check_logs": "Fel: {{error}}. Kontrollera loggar och försök starta om.",
|
||||
"check_logs_restart": "Kontrollera loggar för modellladdningsfel. Testa att starta om.",
|
||||
"llm_cinematic": "LLM (Cinematic)",
|
||||
"llm_configure": "Konfigurera TRANSLATE_BASE_URL för filmisk översättningskvalitet",
|
||||
"llm_set_env": "Ställ in miljövariablerna TRANSLATE_BASE_URL och TRANSLATE_API_KEY. Arbetar med Ollama, OpenAI, LM Studio, mm.",
|
||||
"llm_optional": "Valfritt — ställ in TRANSLATE_BASE_URL för filmkvalitet"
|
||||
},
|
||||
"license": {
|
||||
"title": "Supertonic-3 — Licensgodkännande",
|
||||
"intro": "Supertonic-3 skickas under två distinkta licenser. Vänligen granska båda innan du aktiverar motorn.",
|
||||
"sdk_heading": "SDK-kod · MIT",
|
||||
"sdk_desc": "Python inference SDK (supertonisk) är MIT-licensierad. Tillåten användning, inklusive kommersiell.",
|
||||
"read_mit": "Läs MIT-licensen →",
|
||||
"model_heading": "Modellvikter · OpenRAIL-M",
|
||||
"model_desc": "Supertonic-3-modellvikterna släpps under OpenRAIL-M-licensen. Denna licens begränsar användningen till icke-skadliga ändamål — se den länkade licensen för hela uppsättningen av användningsbaserade begränsningar.",
|
||||
"read_openrail": "Läs OpenRAIL-M-licensen →",
|
||||
"footer": "Genom att klicka på Acceptera registreras ditt godkännande i OmniVoices lokala inställningar och aktiverar motorn. Ditt godkännande lagras endast på denna maskin – ingenting rapporteras till Supertone Inc. eller någon tredje part.",
|
||||
"saving": "Sparar...",
|
||||
"accept": "Acceptera",
|
||||
"accepted_toast": "Supertonic-3-licens accepteras.",
|
||||
"accept_error": "Det gick inte att registrera licensacceptans: {{message}}"
|
||||
},
|
||||
"voicePreview": {
|
||||
"title": "Röstförhandsgranskning",
|
||||
"close": "Stäng förhandsvisning",
|
||||
"default_text": "Hej! Det här är en förhandstitt på hur jag låter med den här rösten.",
|
||||
"default_voice": "Standardröst",
|
||||
"clone_profiles": "Klona profiler",
|
||||
"designed_voices": "Designade röster",
|
||||
"presets": "Förinställningar",
|
||||
"placeholder": "Skriv något att höra...",
|
||||
"stop": "Sluta",
|
||||
"regenerate": "Återskapa",
|
||||
"preview": "Förhandsgranska",
|
||||
"hint": "8 steg · snabb förhandsgranskning"
|
||||
},
|
||||
"header": {
|
||||
"kicker_studio": "Studio",
|
||||
"kicker_library": "Bibliotek",
|
||||
"kicker_preferences": "Inställningar",
|
||||
"kicker_licensing": "Licensiering",
|
||||
"label_launchpad": "Launchpad",
|
||||
"label_clone": "Röstklon",
|
||||
"label_design": "Röstdesign",
|
||||
"label_dub": "Dubbning",
|
||||
"label_projects": "OmniDrive",
|
||||
"label_gallery": "Galleri",
|
||||
"label_transcriptions": "Transkriptioner",
|
||||
"label_settings": "Inställningar",
|
||||
"label_enterprise": "Kommersiell licens",
|
||||
"status_ready": "Klar",
|
||||
"status_loading": "Laddar...",
|
||||
"status_idle": "Inaktiv",
|
||||
"memory_management": "Minneshantering",
|
||||
"flush": "Spola",
|
||||
"loaded_models": "Laddade modeller",
|
||||
"no_models": "Inga modeller laddade",
|
||||
"unload": "Lossa",
|
||||
"flush_caches": "Spola cacher",
|
||||
"unload_all_flush": "Lossa alla + spola"
|
||||
},
|
||||
"sidebar": {
|
||||
"tab_drive": "Kör",
|
||||
"tab_history": "Historia",
|
||||
"tab_exports": "Exporter",
|
||||
"save_project": "Spara Dub Project",
|
||||
"save_new_project": "Spara som nytt dub-projekt",
|
||||
"dub_projects": "Dub-projekt",
|
||||
"voice_clones": "Röstkloner",
|
||||
"designed_voices": "Designade röster",
|
||||
"no_dub_projects": "Inga sparade dub-projekt",
|
||||
"no_dub_hint": "Ladda upp en video och klicka på Spara för att behålla ditt arbete.",
|
||||
"no_clones": "Inga röstkloner ännu",
|
||||
"no_clones_hint": "Spela in eller ladda upp ljud och klicka sedan på Spara som röstprofil.",
|
||||
"no_designs": "Inga designade röster ännu",
|
||||
"no_designs_hint": "Skapa en röst och spara den från historiken.",
|
||||
"history_subtitle": "Generationshistorik · Lagras i SQLite",
|
||||
"no_history": "Ingen generationshistoria",
|
||||
"no_history_hint": "Syntetisera ljud eller kopiera en video – resultaten kommer att visas här.",
|
||||
"clear_history": "Rensa historik",
|
||||
"clear_confirm": "Rensa alla {{count}} historikobjekt? Detta kan inte ångras.",
|
||||
"history_cleared": "Historiken rensad",
|
||||
"recent_exports": "Senaste exporten",
|
||||
"no_exports": "Inga nedladdade utgångar",
|
||||
"no_exports_hint": "Exportera en fil via Tauri för att se den spårad här.",
|
||||
"show_in_folder": "Visa i mapp",
|
||||
"open": "Öppna",
|
||||
"select": "Välj",
|
||||
"try_voice": "Försök",
|
||||
"consistent": "konsekvent",
|
||||
"locked": "Låst",
|
||||
"clone_label": "Klona",
|
||||
"design_label": "Design",
|
||||
"dub_label": "Dub",
|
||||
"save_label": "Spara",
|
||||
"lock_identity": "Lås röstidentitet",
|
||||
"in_folder": "i {{folder}}"
|
||||
},
|
||||
"trimmer": {
|
||||
"title": "Trimma referensljud",
|
||||
"decoding": "Avkodar ljud...",
|
||||
"meta_length": "Längd {{duration}} · {{sampleRate}} Hz",
|
||||
"meta_rendering": "rendering av vågform {{percent}}%",
|
||||
"keyboard_hint": "scroll = zooma · shift+scroll = panorera · alt+dra = panorera · ⏐ ⟵ ⟶ ⏐ tangenter justera handtag",
|
||||
"zoom_in": "Zooma in (+)",
|
||||
"zoom_out": "Zooma ut (-)",
|
||||
"fit_all": "Passar alla (Hem)",
|
||||
"fit_selection": "Anpassningsval (slut)",
|
||||
"fit_sel_btn": "PASSA SEL",
|
||||
"view_range": "Visa {{start}} → {{end}} ({{duration}})",
|
||||
"start_label": "Starta",
|
||||
"end_label": "Slut",
|
||||
"length_label": "Längd",
|
||||
"too_long": ">{{max}}s",
|
||||
"too_short": "för kort",
|
||||
"length_ok": "okej",
|
||||
"loop_preview": "Förhandsvisning av loop",
|
||||
"pause": "Pausa",
|
||||
"preview_selection": "Förhandsgranska val",
|
||||
"play_hint": "Utrymme att spela · Enter för att bekräfta · Esc för att avbryta",
|
||||
"cancel": "Avbryt",
|
||||
"use_trimmed": "Använd trimmad",
|
||||
"decode_failed": "Avkodning misslyckades: {{message}}",
|
||||
"playback_failed": "Uppspelningen misslyckades: {{message}}",
|
||||
"audio_load_failed": "Ljudladdning misslyckades",
|
||||
"unit_seconds": "s"
|
||||
},
|
||||
"casting": {
|
||||
"title": "Högtalargjutning",
|
||||
"auto_assign_title": "Autotilldela röster från extraherade högtalarkloner",
|
||||
"auto_cast": "Autocast",
|
||||
"all_cast": "Alla medverkande",
|
||||
"segments_count": "{{count}} segment",
|
||||
"assign_voice": "Tilldela röst...",
|
||||
"from_video": "🎤 Från video ({{name}})",
|
||||
"no_profiles": "Inga röstprofiler har sparats ännu.",
|
||||
"preview_voice": "Förhandsgranska röst"
|
||||
},
|
||||
"checkpoint": {
|
||||
"asr_title": "Avskrifter klara",
|
||||
"asr_cta": "Översätt",
|
||||
"asr_hint": "Åtgärda eventuella ASR-fel nu – tight diction sparar TTS-försök senare.",
|
||||
"translate_title": "Översättningarna är klara",
|
||||
"translate_cta": "Generera dub",
|
||||
"translate_hint": "Skumma måltexten. Överlånga linjer får fart; du kan också redigera direkt.",
|
||||
"done_title": "Dubbningen är klar",
|
||||
"done_hint": "Granska timing och synkroniseringsförhållanden. Justera vilken linje som helst och tryck på \"Regen ändrad\" för en snabb delvis redo.",
|
||||
"segment_one": "{{count}} segment",
|
||||
"segment_other": "{{count}} segment",
|
||||
"dismiss_title": "Avvisa — kommer inte att dyka upp igen för det här steget förrän du laddar om"
|
||||
},
|
||||
"compare": {
|
||||
"title": "A/B-röstjämförelse",
|
||||
"close": "Nära jämförelse",
|
||||
"desc": "Jämför två röster sida vid sida för att fatta rollbeslut. Appen förblir interaktiv bakom.",
|
||||
"test_phrase": "Testfras",
|
||||
"voice_a": "Röst A",
|
||||
"voice_b": "Röst B",
|
||||
"select_voice": "— Välj röst —",
|
||||
"preset_suffix": "(Förinställd)",
|
||||
"no_audio": "Inget ljud än",
|
||||
"close_btn": "Stäng",
|
||||
"comparing": "Jämför...",
|
||||
"compare_btn": "Jämför",
|
||||
"preparing_voice": "Förbereder röst...",
|
||||
"generating_voice_a": "Genererar röst A...",
|
||||
"generating_voice_b": "Genererar röst B...",
|
||||
"comparison_complete": "Jämförelsen klar!",
|
||||
"play_failed": "Uppspelning misslyckades: {{message}}"
|
||||
},
|
||||
"models": {
|
||||
"hf_token_set_toast": "HuggingFace token set — snabbare nedladdningar aktiverade",
|
||||
"hf_token_save_failed": "Det gick inte att spara token",
|
||||
"install_started": "Installationen påbörjad — framsteg i raden",
|
||||
"delete_confirm": "Ta bort {{repoId}}? Du kan installera om det senare.",
|
||||
"delete_confirm_title": "Ta bort modell",
|
||||
"deleted": "Raderade {{repoId}}",
|
||||
"reinstall_confirm": "Installera om {{repoId}}? Detta kommer att radera den aktuella kopian och ladda ner igen.",
|
||||
"reinstall_confirm_title": "Installera om modellen",
|
||||
"reinstalling": "Installerar om",
|
||||
"recommended_installed": "Rekommenderade modeller är redan installerade.",
|
||||
"started_downloading_one": "Började ladda ner modellen {{count}}",
|
||||
"started_downloading_other": "Började ladda ner {{count}}-modeller",
|
||||
"install_failed": "Installationen misslyckades: {{message}}",
|
||||
"removing_cached": "Tar bort cachade versioner...",
|
||||
"resolving_metadata": "Löser repo-metadata",
|
||||
"retry_attempt": "Försök igen {{attempt}} — {{error}}",
|
||||
"connecting_hf": "Ansluter till HuggingFace...",
|
||||
"resolving_files_one": "Löser {{count}} fil...",
|
||||
"resolving_files_other": "Löser {{count}} filer...",
|
||||
"resolving_files_active": "Löser {{count}} fil(er)... · {{file}}",
|
||||
"measuring": "mäter...",
|
||||
"files_progress": "{{done}}/{{total}} filer",
|
||||
"install_error": "Installationen misslyckades: {{error}}",
|
||||
"view_on_hf": "Se på HuggingFace",
|
||||
"install_btn": "Installera",
|
||||
"reinstall_btn": "Installera om",
|
||||
"downloading": "laddar ner",
|
||||
"deleting": "radera",
|
||||
"working": "arbetar",
|
||||
"installed": "installerat",
|
||||
"not_installed": "inte installerat",
|
||||
"required_tag": "krävs",
|
||||
"delete_btn": "Ta bort",
|
||||
"hf_token_btn": "HF-token",
|
||||
"hf_set_title": "Ställ in HuggingFace-token för snabbare nedladdningar",
|
||||
"get_token": "Få token →",
|
||||
"reco_installed_for": "Rekommenderat paket installerat för {{device}}",
|
||||
"reco_for": "Rekommenderas för {{device}}",
|
||||
"starting": "Börjar...",
|
||||
"required_size": "Krävs ~{{size}} GB",
|
||||
"all_size": "Alla ~{{size}} GB",
|
||||
"req_tag": "req",
|
||||
"search_placeholder": "Sök efter modeller...",
|
||||
"search_label": "Sök efter modeller",
|
||||
"no_matches": "Inga modeller matchar dina filter.",
|
||||
"sort_by": "Sortera efter {{column}}",
|
||||
"column_model": "Modell",
|
||||
"column_role": "Roll",
|
||||
"column_size": "Storlek",
|
||||
"column_status": "Status",
|
||||
"ready_badge": "Klar",
|
||||
"loading_badge": "Laddar...",
|
||||
"idle_badge": "Inaktiv",
|
||||
"started_downloading_required_one": "Började ladda ner {{count}} obligatorisk modell",
|
||||
"started_downloading_required_other": "Började ladda ner {{count}} nödvändiga modeller"
|
||||
},
|
||||
"enterprise_faq": {
|
||||
"q_internal_tools": "Behöver jag en licens för interna verktyg?",
|
||||
"a_internal_tools": "Intern användning av dina anställda och entreprenörer är ett tillåtet syfte enligt FSL - ingen licens krävs. En kommersiell licens behövs när du gör OmniVoice tillgänglig för andra som en del av en konkurrerande produkt eller tjänst (återförsäljning, värd SaaS, white-label).",
|
||||
"q_try_before": "Kan jag prova innan jag binder mig?",
|
||||
"a_try_before": "Ja. Den fullständiga appen är gratis att ladda ner och köra lokalt för utvärdering under FSL. När du är redo att diskutera en kommersiell implementering, maila oss så går vi igenom detaljerna tillsammans.",
|
||||
"q_watermark": "Hur är det med vattenstämpeln?",
|
||||
"a_watermark": "Den osynliga AudioSeal-vattenstämpeln är inbäddad som standard. Kommersiella licenstagare kan inaktivera det i Inställningar → Sekretess. Gratis/personligt bruk inkluderar alltid vattenstämpeln.",
|
||||
"q_apache": "Blir källan någonsin Apache 2.0?",
|
||||
"a_apache": "Ja. Varje utgåva konverteras automatiskt till Apache-licensen, version 2.0 på tvåårsdagen av dess publicering. Det betyder att dagens version är Apache 2.0 om två år, ingen åtgärd krävs från oss – FSL garanterar det oåterkalleligt."
|
||||
},
|
||||
"gallery_extra": {
|
||||
"save_prompt": "Ange ett namn för denna röstprofil:",
|
||||
"saved_as_profile": "Rösten har sparats som profil!",
|
||||
"save_profile_failed": "Det gick inte att spara profilen",
|
||||
"download_failed": "Nedladdning misslyckades: {{message}}",
|
||||
"trim_load_failed": "Det gick inte att ladda ljud för trimning: {{message}}",
|
||||
"upload_crop_failed": "Det gick inte att ladda upp beskuren röst: {{message}}"
|
||||
},
|
||||
"dub_workflow": {
|
||||
"preparing_audio": "Förbereder ljud...",
|
||||
"preparing_video": "Förbereder video...",
|
||||
"extracting_audio_scenes": "Extraherar ljud och scener...",
|
||||
"transcribing_audio": "Transkriberar ljud...",
|
||||
"transcription_complete": "Transkriptionen är klar",
|
||||
"upload_cancelled": "Uppladdningen avbröts",
|
||||
"upload_failed": "Uppladdning misslyckades: {{message}}",
|
||||
"downloading_video": "Laddar ned video...",
|
||||
"ingested": "Intaget {{url}}",
|
||||
"ingest_cancelled": "Intag avbröts",
|
||||
"ingest_failed": "URL-inmatning misslyckades: {{message}}",
|
||||
"retry_cancelled": "Försök igen avbröts",
|
||||
"transcription_failed": "Transkription misslyckades: {{message}}",
|
||||
"import_srt_no_job": "Ladda upp eller mata in en video först – det finns inget jobb att bifoga undertexter till.",
|
||||
"imported_cues": "Importerade {{count}} signal(er) från {{file}}",
|
||||
"skipped_malformed": "{{count}} hoppade över (felformat)",
|
||||
"dropped_overlap": "{{count}} tappade (överlappning)",
|
||||
"clamped_to_duration": "{{count}} fastspänd till medialängden",
|
||||
"srt_import_failed": "SRT-import misslyckades",
|
||||
"cleaned_one": "Rensat {{count}} fragment",
|
||||
"cleaned_other": "Rensade {{count}} fragment",
|
||||
"segments_clean": "Segmenten är redan rena",
|
||||
"cleanup_failed": "Rensa misslyckades: {{message}}",
|
||||
"cinematic_no_llm": "Filmkvalitet behöver en LLM — ställ in TRANSLATE_BASE_URL + TRANSLATE_API_KEY (Ollama fungerar lokalt). Faller tillbaka till Fast.",
|
||||
"translate_errors": "{{errorCount}}/{{totalCount}} segment(er) misslyckades: {{firstError}}",
|
||||
"translated_segments": "Översatta {{count}} segment(er) → {{lang}}",
|
||||
"translated_cinematic_suffix": "(Cinematic)",
|
||||
"translation_failed": "Översättning misslyckades: {{message}}",
|
||||
"regenerating": "Regenererar {{count}} segment(er)...",
|
||||
"generating_dub": "Genererar dubb...",
|
||||
"generating_progress": "Genererar dubb... {{current}}/{{total}}",
|
||||
"generation_aborted": "Generation avbröts.",
|
||||
"dubbing_aborted": "Dubbningen avbröts",
|
||||
"generation_stream_ended": "Generationsströmmen avslutades innan den var klar",
|
||||
"dub_complete": "Dubbningen är klar",
|
||||
"stop_failed": "Det gick inte att sluta",
|
||||
"save_first": "Klicka på \"Ladda upp och transkribera\" först så att videon bearbetas på servern innan du sparar.",
|
||||
"project_saved": "Projektet sparat",
|
||||
"project_created": "Projekt skapat",
|
||||
"save_failed": "Det gick inte att spara: {{message}}",
|
||||
"opened_project": "Öppnad: {{name}}",
|
||||
"delete_project_confirm": "Vill du ta bort det här projektet? Detta kan inte ångras.",
|
||||
"project_deleted": "Projektet raderat",
|
||||
"delete_history_confirm": "Vill du ta bort det här historikobjektet?",
|
||||
"history_deleted": "Historikobjektet har tagits bort",
|
||||
"restored_state": "Återställd föregående generations tillstånd",
|
||||
"upgrading_preview": "Uppgraderar {{count}} segment(er) med förhandsvisningskvalitet till full kvalitet..."
|
||||
},
|
||||
"tts_errors": {
|
||||
"enter_text": "Vänligen ange text",
|
||||
"upload_or_select": "Ladda upp ett ljud eller välj en röstprofil",
|
||||
"trim_hint": "Ljudet är {{duration}}s — trimma till ≤{{max}}s för bästa kloning",
|
||||
"timeout": "Tidsgränsen för generationen gick ut – modellen kanske fortfarande laddas ned. Kontrollera Inställningar → Loggar och försök sedan igen.",
|
||||
"error_prefix": "Fel: {{message}}",
|
||||
"ignored_unsupported": "Ignorerad instruktion som inte stöds: {{items}}",
|
||||
"ignored_duplicate": "Ignorerad (kategori redan inställd): {{items}}"
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Delning och fjärråtkomst",
|
||||
"help": "Exponera den här körande OmniVoice-instansen för dina andra maskiner utan att starta om den. Enbart Loopback är standard - ingenting delas förrän du aktiverar det här.",
|
||||
"local_network": "Lokalt nätverk",
|
||||
"local_help": "Dela på ditt Wi-Fi / Ethernet med en PIN-kod för engångsåtkomst. Andra enheter skannar QR-koden eller öppna länken.",
|
||||
"ports_title": "Hamnar",
|
||||
"ports_help": "Dessa ställs in via miljövariabler som läses vid uppstart. Ändra backend- eller UI-porten genom att ställa in variabeln och starta om OmniVoice.",
|
||||
"backend_port": "Backend-port",
|
||||
"ui_port": "UI-port",
|
||||
"lan_share_port": "LAN-delningsport",
|
||||
"port_error": "Ange en port mellan 1024 och 65535",
|
||||
"port_saved": "LAN-delningsport sparad — gäller nästa gång du aktiverar delning",
|
||||
"port_save_failed": "Kunde inte spara port: {{message}}",
|
||||
"saving": "Sparar...",
|
||||
"ports_note": "Backend- och UI-portar gäller vid omstart. LAN-delningsporten gäller nästa gång du aktiverar delning.",
|
||||
"tailscale_title": "Tailscale (privat fjärråtkomst)",
|
||||
"tailscale_checking": "Söker efter tailscale...",
|
||||
"tailscale_absent": "Svansskala inte upptäckt. Installera den för att nå OmniVoice säkert från var som helst på ditt privata tailnet.",
|
||||
"tailscale_install": "Installera Tailscale",
|
||||
"tailscale_running": "Tailscale är igång. Servera OmniVoice över ditt privata tailnet.",
|
||||
"tailscale_not_logged_in": "Tailscale är installerad men inte inloggad. Starta och logga in på Tailscale först.",
|
||||
"tailscale_enabled": "Tailscale serve aktiverad",
|
||||
"tailscale_enable_failed": "Det gick inte att aktivera Tailscale",
|
||||
"tailscale_enable_error": "Kunde inte aktivera Tailscale: {{message}}",
|
||||
"tailscale_disabled": "Tailscale serve funktionshindrade",
|
||||
"tailscale_disable_failed": "Det gick inte att inaktivera Tailscale",
|
||||
"tailscale_disable_error": "Kunde inte inaktivera Tailscale: {{message}}",
|
||||
"tailscale_enabling": "Aktiverar...",
|
||||
"tailscale_enable_btn": "Aktivera Tailscale-serva",
|
||||
"tailscale_disabling": "Inaktiverar...",
|
||||
"tailscale_disable_btn": "Stoppa Tailscale serve",
|
||||
"tailscale_copy": "Kopiera länk",
|
||||
"tailscale_open": "Öppna i webbläsaren",
|
||||
"copied": "Kopierade",
|
||||
"tailscale_qr_alt": "QR-kod för Tailscale URL"
|
||||
},
|
||||
"reportBug": {
|
||||
"label": "Rapportera ett fel",
|
||||
"title": "Öppnar en förifylld GitHub-problemsida i din webbläsare. Inget skickas förrän du klickar på Skicka."
|
||||
},
|
||||
"app": {
|
||||
"loading": "Laddar...",
|
||||
"trimmed_loaded": "Trimmat ljud har laddats",
|
||||
"toast_exported": "Exporterad: {{name}}",
|
||||
"toast_export_failed": "Export misslyckades: {{message}}",
|
||||
"toast_open_folder_failed": "Kunde inte öppna mappen: {{message}}",
|
||||
"toast_saving": "Sparar {{name}}...",
|
||||
"toast_saved": "Sparad: {{path}}",
|
||||
"toast_save_error": "Spara fel: {{message}}",
|
||||
"toast_processing": "Bearbetar {{name}}...",
|
||||
"toast_downloaded": "Nedladdat {{name}}",
|
||||
"toast_download_error": "Nedladdningsfel: {{message}}",
|
||||
"toast_upload_first": "Klicka på \"Ladda upp och transkribera\" först så att videon bearbetas på servern innan du sparar.",
|
||||
"toast_save_failed": "Det gick inte att spara: {{message}}",
|
||||
"toast_opened": "Öppnad: {{name}}",
|
||||
"toast_project_deleted": "Projektet raderat",
|
||||
"toast_restored_state": "Återställd föregående generations tillstånd",
|
||||
"toast_history_deleted": "Historikobjektet har tagits bort",
|
||||
"toast_flushed": "Spolat — RAM {{ram}}G · VRAM {{vram}}G{{unloaded}}",
|
||||
"toast_model_unloaded": "· modell avlastad",
|
||||
"toast_flush_failed": "Spolning misslyckades: {{message}}",
|
||||
"toast_project_saved": "Projektet sparat",
|
||||
"toast_project_created": "Projekt skapat"
|
||||
},
|
||||
"update": {
|
||||
"available": "Uppdatering {{version}} tillgänglig",
|
||||
"install": "Installera och starta om",
|
||||
"install_hint": "Ladda ner uppdateringen och starta om i den nya versionen",
|
||||
"downloading": "Uppdaterar… {{pct}} %",
|
||||
"restart": "Starta om för att uppdatera",
|
||||
"busy": "Slutför din dubbning först – installera sedan uppdateringen.",
|
||||
"whats_new": "Vad är nytt",
|
||||
"failed": "Uppdateringen misslyckades",
|
||||
"retry": "Försök igen",
|
||||
"dismiss": "Avvisa"
|
||||
},
|
||||
"archetypes": {
|
||||
"featured": "Utvalda",
|
||||
@@ -945,5 +1577,25 @@
|
||||
"facet_accent": "Accent",
|
||||
"facet_lang": "Språk",
|
||||
"facet_whisper": "Viska"
|
||||
},
|
||||
"support": {
|
||||
"tab_support": "Support",
|
||||
"tab_license": "Kommersiell licens",
|
||||
"toggle_label": "Support eller kommersiell licens",
|
||||
"other_ways": "Andra sätt att hjälpa till",
|
||||
"star_github": "Stjärna på GitHub",
|
||||
"join_discord": "Gå med i Discord"
|
||||
},
|
||||
"updates": {
|
||||
"tab": "Uppdateringar",
|
||||
"up_to_date": "Uppdaterad · v{{version}}",
|
||||
"check_now": "Kolla nu",
|
||||
"releases": "Utgåvor",
|
||||
"current": "nuvarande",
|
||||
"prerelease": "förhandsgranskning",
|
||||
"loading": "Läser in utgåvor...",
|
||||
"none": "Inga utgåvor hittades",
|
||||
"load_error": "Det gick inte att läsa in utgåvor (offline?)",
|
||||
"retry_load": "Försök igen"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,4 @@
|
||||
{
|
||||
"update": {
|
||||
"available": "มีอัปเดต {{version}}",
|
||||
"install": "ติดตั้งและรีสตาร์ท",
|
||||
"install_hint": "ดาวน์โหลดอัปเดตและรีสตาร์ทเป็นเวอร์ชันใหม่",
|
||||
"downloading": "กำลังอัปเดต… {{pct}}%",
|
||||
"restart": "รีสตาร์ทเพื่ออัปเดต",
|
||||
"busy": "ทำการพากย์เสียงของคุณให้เสร็จก่อน แล้วจึงติดตั้งอัปเดต"
|
||||
},
|
||||
"nav": {
|
||||
"clone": "โคลนเสียง",
|
||||
"design": "สร้างเสียง",
|
||||
@@ -16,7 +8,10 @@
|
||||
"launchpad": "Launchpad",
|
||||
"gallery": "แกลเลอรี่",
|
||||
"transcripts": "ใบรับรองผลการเรียน",
|
||||
"omnidrive": "OmniDrive"
|
||||
"omnidrive": "OmniDrive",
|
||||
"move_rail_right": "เลื่อนรางไปทางขวา",
|
||||
"move_rail_left": "เลื่อนรางไปทางซ้าย",
|
||||
"flip_rail": "รางพลิกด้านข้าง"
|
||||
},
|
||||
"settings": {
|
||||
"general": "ทั่วไป",
|
||||
@@ -45,7 +40,40 @@
|
||||
"ffmpeg_missing": "ไม่พบ",
|
||||
"ffmpeg_current": "เส้นทางปัจจุบัน",
|
||||
"ffmpeg_desc": "ตั้งค่าเส้นทาง ffmpeg ที่กำหนดเองหากการตรวจจับอัตโนมัติล้มเหลว",
|
||||
"ffmpeg_saved": "ชุดเส้นทาง FFmpeg — รีสตาร์ทแบ็กเอนด์เพื่อใช้"
|
||||
"ffmpeg_saved": "ชุดเส้นทาง FFmpeg — รีสตาร์ทแบ็กเอนด์เพื่อใช้",
|
||||
"diagnostics_copied": "คัดลอกการวินิจฉัยแล้ว — วางลงในรายงานปัญหาของคุณ",
|
||||
"updater_desktop": "Updater ทำงานในแอปเดสก์ท็อปเท่านั้น",
|
||||
"latest_version": "คุณกำลังใช้เวอร์ชันล่าสุด",
|
||||
"logs_load_failed": "ไม่สามารถโหลดบันทึก: {{message}}",
|
||||
"clear_frontend_confirm": "ล้างบัฟเฟอร์บันทึกส่วนหน้าในหน่วยความจำหรือไม่",
|
||||
"clear_frontend_title": "ล้างบันทึก",
|
||||
"frontend_logs_cleared": "ล้างบันทึกส่วนหน้าแล้ว",
|
||||
"clear_tauri_confirm": "ตัดทอนไฟล์บันทึกด้าน Tauri หรือไม่ ระบบปฏิบัติการจะยังคงเขียนรายการใหม่ต่อไป",
|
||||
"clear_tauri_title": "ล้างบันทึก Tauri",
|
||||
"nothing_to_clear": "ไม่มีอะไรต้องล้าง — ยังไม่มีไฟล์บันทึกของ Tauri บนดิสก์",
|
||||
"cleared_tauri_one": "ล้างไฟล์บันทึก {{count}} Tauri แล้ว",
|
||||
"cleared_tauri_other": "ล้างไฟล์บันทึก {{count}} Tauri แล้ว",
|
||||
"clear_tauri_failed": "ไม่สามารถล้างบันทึก Tauri: {{message}}",
|
||||
"clear_backend_confirm": "ล้างรันไทม์แบ็กเอนด์ + บันทึกข้อขัดข้องใช่ไหม สิ่งนี้ไม่สามารถยกเลิกได้",
|
||||
"clear_backend_title": "ล้างบันทึก",
|
||||
"backend_logs_cleared": "ล้างบันทึกแบ็กเอนด์แล้ว",
|
||||
"clear_backend_failed": "ไม่สามารถล้างบันทึกได้",
|
||||
"copy_failed": "การคัดลอกล้มเหลว: {{message}}",
|
||||
"update_check_failed": "การตรวจสอบการอัปเดตล้มเหลว: {{message}}",
|
||||
"save_failed": "บันทึกล้มเหลว: {{message}}",
|
||||
"clear_failed": "การเคลียร์ล้มเหลว: {{message}}",
|
||||
"engine_switched": "{{family}} → {{engine}}",
|
||||
"channel_set_failed": "ไม่สามารถตั้งค่าช่อง: {{message}}",
|
||||
"updater_downloading": "กำลังดาวน์โหลด {{version}}…",
|
||||
"updater_installed": "ติดตั้งแล้ว — เปิดตัวใหม่",
|
||||
"updater_available_title": "อัปเดตพร้อมใช้งาน",
|
||||
"updater_available_body": "มีเวอร์ชัน {{version}} แล้ว\n\n{{notes}}\n\nดาวน์โหลดและติดตั้งทันที?",
|
||||
"updater_notes_fallback": "ดูบันทึกประจำรุ่นบน GitHub",
|
||||
"shortcut_load_failed": "ไม่สามารถโหลดทางลัด: {{message}}",
|
||||
"shortcut_set": "ทางลัดการเขียนตามคำบอกตั้งค่าเป็น {{shortcut}}",
|
||||
"shortcut_register_failed": "ไม่สามารถลงทะเบียนได้: {{message}}",
|
||||
"shortcut_reset": "รีเซ็ตเป็นค่าเริ่มต้น",
|
||||
"shortcut_reset_failed": "การรีเซ็ตล้มเหลว: {{message}}"
|
||||
},
|
||||
"bootstrap": {
|
||||
"title": "OmniVoice Studio",
|
||||
@@ -69,7 +97,13 @@
|
||||
"waiting_output": "กำลังรอข้อมูลส่งออก…",
|
||||
"auto_detect": "ตรวจหาอัตโนมัติ",
|
||||
"suggest_lang": "เปลี่ยนเป็นภาษาไทย?",
|
||||
"select_lang": "ภาษา:"
|
||||
"select_lang": "ภาษา:",
|
||||
"region_global": "ทั่วโลก (โดยตรง)",
|
||||
"region_china": "จีน (กระจก)",
|
||||
"region_russia": "รัสเซีย (กระจก)",
|
||||
"region_restricted": "ถูกจำกัด (มิเรอร์)",
|
||||
"unknown_error": "ข้อผิดพลาดที่ไม่รู้จัก",
|
||||
"retrying": "กำลังลองอีกครั้ง..."
|
||||
},
|
||||
"stories": {
|
||||
"title": "ตัวแก้ไขเรื่องราว",
|
||||
@@ -158,7 +192,17 @@
|
||||
"reload": "บังคับให้โหลด UI ใหม่",
|
||||
"backend": "แบ็กเอนด์",
|
||||
"frontend": "ส่วนหน้า",
|
||||
"tauri": "ทอรี"
|
||||
"tauri": "ทอรี",
|
||||
"cancelOp": "ยกเลิกการดำเนินการ",
|
||||
"dismiss": "ยกเลิก",
|
||||
"dismissStatus": "ยกเลิกสถานะ",
|
||||
"search": "ค้นหา...",
|
||||
"no_matches": "ไม่มีการแข่งขัน",
|
||||
"recent_and_popular": "ล่าสุดและเป็นที่นิยม",
|
||||
"popular_label": "เป็นที่นิยม",
|
||||
"showing_of": "กำลังแสดง {{shown}} จาก {{total}} พิมพ์เพื่อค้นหา...",
|
||||
"yes": "ใช่",
|
||||
"no": "ไม่"
|
||||
},
|
||||
"launchpad": {
|
||||
"greeting": "สวัสดี",
|
||||
@@ -180,7 +224,16 @@
|
||||
"locked": "ล็อคแล้ว",
|
||||
"open": "เปิด",
|
||||
"try_it": "ลองมัน",
|
||||
"audio_only": "เสียงเท่านั้น"
|
||||
"audio_only": "เสียงเท่านั้น",
|
||||
"stories_title": "เรื่องราว",
|
||||
"stories_desc": "หนังสือเสียงหลายเสียง — เลือกตัวละครของคุณ ใส่สคริปต์ ส่งออกตามบท",
|
||||
"gallery_title": "แกลเลอรี่เสียง",
|
||||
"gallery_desc": "เรียกดูเสียงที่ออกแบบสำเร็จรูปตามสำเนียง อายุ และสไตล์ — ไม่ต้องตั้งค่าใดๆ",
|
||||
"transcripts_title": "ใบรับรองผลการเรียน",
|
||||
"transcripts_desc": "เปลี่ยนเสียงหรือวิดีโอให้เป็นข้อความที่แก้ไขและค้นหาได้ใน 646 ภาษา",
|
||||
"recent_files": "ไฟล์ล่าสุด",
|
||||
"view_all_files": "ดูไฟล์ทั้งหมด",
|
||||
"file": "ไฟล์"
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "พรอมต์",
|
||||
@@ -271,11 +324,6 @@
|
||||
"outputs": "เอาท์พุต",
|
||||
"crash_log": "บันทึกความผิดพลาด",
|
||||
"update_endpoint": "อัปเดตจุดสิ้นสุด",
|
||||
"update_channel": "ช่องอัปเดต",
|
||||
"channel_stable": "เสถียร",
|
||||
"channel_preview": "พรีวิว",
|
||||
"channel_set": "ตั้งค่าช่องอัปเดตเป็น {{channel}} แล้ว",
|
||||
"channel_preview_hint": "พรีวิวจะติดตามบิลด์ main ล่าสุด — ฟีเจอร์ใหม่กว่า แต่ทดสอบน้อยกว่า จะกลับไปใช้เวอร์ชันเสถียรหากมีรุ่นเสถียรที่ใหม่กว่า",
|
||||
"yes": "ใช่",
|
||||
"no": "ไม่",
|
||||
"web_preview": "ตัวอย่างเว็บ",
|
||||
@@ -284,7 +332,12 @@
|
||||
"copy_diagnostics": "คัดลอกการวินิจฉัย",
|
||||
"github": "OmniVoice บน GitHub",
|
||||
"model_card": "การ์ดโมเดล",
|
||||
"commercial_license": "ใบอนุญาตการค้า"
|
||||
"commercial_license": "ใบอนุญาตการค้า",
|
||||
"update_channel": "ช่องอัปเดต",
|
||||
"channel_stable": "เสถียร",
|
||||
"channel_preview": "พรีวิว",
|
||||
"channel_set": "ตั้งค่าช่องอัปเดตเป็น {{channel}} แล้ว",
|
||||
"channel_preview_hint": "พรีวิวจะติดตามบิลด์ main ล่าสุด — ฟีเจอร์ใหม่กว่า แต่ทดสอบน้อยกว่า จะกลับไปใช้เวอร์ชันเสถียรหากมีรุ่นเสถียรที่ใหม่กว่า"
|
||||
},
|
||||
"privacy": {
|
||||
"desc": "ทุกอย่างทำงานบน <1>เครื่องนี้</1> เสียง วิดีโอ และการถอดเสียงของคุณจะไม่ออกจากคอมพิวเตอร์ของคุณ เว้นแต่คุณจะใช้โปรแกรมแปลออนไลน์อย่างชัดเจน (Google, DeepL ฯลฯ) หรือกดไปที่ HuggingFace",
|
||||
@@ -340,7 +393,30 @@
|
||||
"unavailable": "ไม่พร้อมใช้งาน",
|
||||
"use": "ใช้",
|
||||
"loading": "กำลังโหลดเครื่องยนต์...",
|
||||
"refresh": "รีเฟรช"
|
||||
"refresh": "รีเฟรช",
|
||||
"matrixTitle": "เมทริกซ์ความเข้ากันได้ของเครื่องยนต์",
|
||||
"loadFailed": "ไม่สามารถโหลดเอ็นจิ้น: {{message}}",
|
||||
"couldNotLoad": "ไม่สามารถโหลดเอ็นจิ้น: {{message}}",
|
||||
"retry": "ลองอีกครั้ง",
|
||||
"activeEngine": "ใช้งาน {{family}}: {{engine}}",
|
||||
"engineCompatLabel": "{{family}} ความเข้ากันได้ของเครื่องยนต์",
|
||||
"active": "ใช้งานอยู่",
|
||||
"whyUnavailable": "ทำไมไม่พร้อมใช้งาน?",
|
||||
"lastError": "ข้อผิดพลาดครั้งล่าสุด: {{error}}",
|
||||
"installedAndReady": "ติดตั้งแล้วพร้อม",
|
||||
"notInstalled": "ไม่ได้ติดตั้ง",
|
||||
"available": "มีจำหน่าย",
|
||||
"subprocessTitle": "ทำงานในกระบวนการย่อยของตัวเอง + venv",
|
||||
"inProcessTitle": "ทำงานในกระบวนการ OmniVoice Python",
|
||||
"testEngine": "ทดสอบเครื่องยนต์",
|
||||
"testing": "กำลังทดสอบ...",
|
||||
"recheck": "ตรวจสอบอีกครั้ง",
|
||||
"rechecking": "กำลังตรวจสอบอีกครั้ง...",
|
||||
"latencyMs": "{{ms}} มิลลิวินาที",
|
||||
"failed": "ล้มเหลว",
|
||||
"acceptLicense": "ยอมรับใบอนุญาต",
|
||||
"noBackends": "ไม่มีแบ็กเอนด์ที่ลงทะเบียน",
|
||||
"switch_failed": "ล้มเหลวในการเปลี่ยนเครื่องยนต์"
|
||||
},
|
||||
"capture": {
|
||||
"desc": "ปุ่มลัดส่วนกลางใช้งานได้ในแอปเดสก์ท็อปเท่านั้น UI ของเว็บใช้ทางลัด <1>Ctrl+Shift+Space</1> ในเพจในขณะที่หน้าต่างมีโฟกัส",
|
||||
@@ -352,13 +428,55 @@
|
||||
"record_shortcut": "บันทึกทางลัด",
|
||||
"recording": "กำลังบันทึก...",
|
||||
"save": "บันทึก",
|
||||
"reset_default": "รีเซ็ตเป็นค่าเริ่มต้น"
|
||||
"reset_default": "รีเซ็ตเป็นค่าเริ่มต้น",
|
||||
"listening_label": "กำลังฟัง...",
|
||||
"transcribing_label": "กำลังถอดเสียง...",
|
||||
"pasted": "วางแล้ว",
|
||||
"no_speech": "ไม่พบคำพูด",
|
||||
"mic_denied": "การเข้าถึงไมค์ถูกปฏิเสธ",
|
||||
"mic_denied_toast": "การเข้าถึงไมโครโฟนถูกปฏิเสธ {{hint}}",
|
||||
"mic_hint_mac": "macOS: เปิดการตั้งค่าระบบ → ความเป็นส่วนตัวและความปลอดภัย → ไมโครโฟน และเปิดใช้งาน OmniVoice",
|
||||
"mic_hint_windows": "Windows: เปิดการตั้งค่า → ความเป็นส่วนตัวและความปลอดภัย → ไมโครโฟน และอนุญาต OmniVoice",
|
||||
"mic_hint_linux": "Linux: ตรวจสอบว่าผู้ใช้ของคุณอยู่ในกลุ่มเสียงและ WebView มีสิทธิ์เข้าถึงไมโครโฟน",
|
||||
"transcription_failed": "การถอดเสียงล้มเหลว: {{message}}"
|
||||
},
|
||||
"logs": {
|
||||
"no_tauri_log": "ยังไม่มีการเข้าสู่ระบบ Tauri บนดิสก์ — เปิดผ่านเดสก์ท็อปเพื่อสร้างหนึ่งรายการ",
|
||||
"empty_frontend": "No frontend console entries captured yet. โต้ตอบกับแอป — ทุกคอนโซล* จะปรากฏที่นี่",
|
||||
"empty_tauri": "ไม่มีบันทึก Tauri ทำงานในเชลล์เดสก์ท็อปเท่านั้น",
|
||||
"empty_backend": "บันทึกรันไทม์ว่างเปล่า กิจกรรมจะปรากฏที่นี่เมื่อแบ็กเอนด์บันทึก"
|
||||
"empty_backend": "บันทึกรันไทม์ว่างเปล่า กิจกรรมจะปรากฏที่นี่เมื่อแบ็กเอนด์บันทึก",
|
||||
"title": "บันทึก",
|
||||
"source_backend": "แบ็กเอนด์",
|
||||
"source_frontend": "ส่วนหน้า",
|
||||
"source_tauri": "ทอรี",
|
||||
"expand": "ขยายบันทึก",
|
||||
"collapse": "ยุบบันทึก",
|
||||
"expand_aria": "ขยายแผงบันทึก",
|
||||
"collapse_aria": "ยุบแผงบันทึก",
|
||||
"drag_resize": "ลากเพื่อปรับขนาด",
|
||||
"refresh": "รีเฟรช",
|
||||
"refresh_aria": "รีเฟรชบันทึก",
|
||||
"copy_visible": "คัดลอกบันทึกที่มองเห็นได้",
|
||||
"copy_visible_aria": "คัดลอกบันทึกที่มองเห็นได้",
|
||||
"clear": "ชัดเจน",
|
||||
"clear_aria": "ล้างบันทึก",
|
||||
"report_issue": "รายงานปัญหา (วินิจฉัยการคัดลอก)",
|
||||
"report_issue_aria": "รายงานปัญหา",
|
||||
"close": "ปิด",
|
||||
"close_aria": "ปิดแผงบันทึก",
|
||||
"join_discord": "เข้าร่วม Discord ของเรา",
|
||||
"join_discord_aria": "เข้าร่วมชุมชน Discord ของเรา",
|
||||
"support_project": "สนับสนุนโครงการนี้",
|
||||
"support_project_aria": "สนับสนุนโครงการนี้",
|
||||
"empty_frontend_short": "ยังไม่มีเอาต์พุตคอนโซลส่วนหน้า",
|
||||
"empty_lines": "ไม่มีเส้น",
|
||||
"all_clear": "✅ ชัดเจนทั้งหมด — ไม่พบปัญหาใดๆ",
|
||||
"log_cleared": "{{source}} บันทึกถูกล้างแล้ว",
|
||||
"clear_failed": "การเคลียร์ล้มเหลว: {{message}}",
|
||||
"log_copied": "คัดลอกบันทึก {{source}} แล้ว",
|
||||
"copy_failed": "การคัดลอกล้มเหลว: {{message}}",
|
||||
"report_copied": "คัดลอกรายงานการวินิจฉัยแล้ว — วางลงในปัญหา GitHub",
|
||||
"report_failed": "รายงานล้มเหลว: {{message}}"
|
||||
},
|
||||
"voice_profile": {
|
||||
"test_text": "สวัสดี — นี่คือการทดสอบเสียงนี้",
|
||||
@@ -526,7 +644,17 @@
|
||||
"install_already": "{{engine}} ได้รับการติดตั้งแล้ว",
|
||||
"install_ok": "{{engine}} ติดตั้งแล้ว",
|
||||
"install_failed": "การติดตั้งล้มเหลว: {{message}}",
|
||||
"prep_elapsed": "{{time}} ผ่านไป"
|
||||
"prep_elapsed": "{{time}} ผ่านไป",
|
||||
"add_language": "เพิ่มภาษา",
|
||||
"search_languages": "ค้นหาภาษา...",
|
||||
"languages_selected_one": "เลือก {{count}} ภาษาแล้ว",
|
||||
"languages_selected_other": "เลือก {{count}} ภาษาแล้ว",
|
||||
"more_to_narrow": "+{{count}} เพิ่มเติม — พิมพ์เพื่อจำกัดให้แคบลง",
|
||||
"no_matches": "ไม่มีการแข่งขัน",
|
||||
"diagnostic_copied": "คัดลอกการวินิจฉัยแล้ว",
|
||||
"copy_failed": "การคัดลอกล้มเหลว",
|
||||
"open_docs": "เปิดเอกสาร",
|
||||
"copy_diagnostic": "คัดลอกการวินิจฉัย"
|
||||
},
|
||||
"glossary": {
|
||||
"title": "อภิธานศัพท์",
|
||||
@@ -601,7 +729,17 @@
|
||||
"more_actions_title": "การดำเนินการเพิ่มเติม",
|
||||
"speaker_pick": "เลือก...",
|
||||
"speaker_title_detected": "ผู้พูด — เลือกจากที่ตรวจพบ หรือพิมพ์ชื่อที่กำหนดเอง",
|
||||
"speaker_title_custom": "ผู้พูด — พิมพ์ชื่อ (ตรวจไม่พบโคลนไดอะไรเซชัน)"
|
||||
"speaker_title_custom": "ผู้พูด — พิมพ์ชื่อ (ตรวจไม่พบโคลนไดอะไรเซชัน)",
|
||||
"time_edit_title": "คลิกเพื่อแก้ไขเวลาเริ่มต้น (m:ss.s) เข้าสู่เพื่อกระทำ Esc เพื่อยกเลิก",
|
||||
"fit_fits": "พอดี",
|
||||
"fit_fits_title": "เสียงที่มีอัตราธรรมชาติพอดีกับช่อง",
|
||||
"fit_overflows": "โอเวอร์โฟลว์ +{{seconds}}s",
|
||||
"fit_overflows_title": "ข้อความที่แปลยาวกว่าช่องต้นฉบับ {{seconds}}s เสียงถูกตัดแต่งอย่างหนัก ย่อข้อความหรือเปลี่ยนระยะเวลาเป็น \"ยืดวิดีโอ\"",
|
||||
"fit_stretched": "วิดีโอ {{ratio}}×",
|
||||
"fit_stretched_title": "โหมดวิดีโอยืด: วิดีโอของส่วนนี้ช้าลงเป็น {{ratio}}× เพื่อให้พอดีกับเสียงพากย์ที่เป็นธรรมชาติ",
|
||||
"fit_compressed_title": "เสียง TTS มีค่า {{pct}}% ของช่อง — บีบอัดอย่างหนัก",
|
||||
"fit_audio_title": "เสียงพอดีกับช่อง",
|
||||
"fit_ratio_title": "เสียง TTS คือ {{pct}}% ของช่อง"
|
||||
},
|
||||
"voice": {
|
||||
"personality": "บุคลิกภาพ",
|
||||
@@ -696,10 +834,24 @@
|
||||
"status_running": "วิ่ง",
|
||||
"status_done": "เสร็จแล้ว",
|
||||
"status_failed": "ล้มเหลว",
|
||||
"status_cancelled": "ยกเลิกแล้ว"
|
||||
"status_cancelled": "ยกเลิกแล้ว",
|
||||
"add_to_queue_title": "เพิ่มวิดีโอลงในคิว",
|
||||
"drop_hint_text": "วางไฟล์วิดีโอที่นี่หรือคลิกเพื่อเรียกดู",
|
||||
"drop_formats": "MP4 · MOV · MKV · เว็บเอ็ม",
|
||||
"files_kicker": "ไฟล์ ({{count}})",
|
||||
"file_size_mb": "{{size}} เมกะไบต์",
|
||||
"target_languages": "ภาษาเป้าหมาย",
|
||||
"voice_kicker": "เสียง",
|
||||
"default_option": "ค่าเริ่มต้น",
|
||||
"clone_profiles": "โปรไฟล์โคลน",
|
||||
"presets": "ค่าที่ตั้งล่วงหน้า",
|
||||
"preserve_bg": "รักษาเสียงพื้นหลัง (เพลง/FX)",
|
||||
"estimate": "{{videos}} วิดีโอ × {{langs}} lang(s) = {{jobs}} งาน",
|
||||
"select_files_langs": "เลือกไฟล์และภาษา",
|
||||
"add_to_queue": "เพิ่มเข้าคิว"
|
||||
},
|
||||
"gallery": {
|
||||
"title": "แกลเลอรี่",
|
||||
"title": "OmniVoice แกลเลอรี่",
|
||||
"search_placeholder": "ค้นหา YouTube...",
|
||||
"all_voices": "ทุกเสียง ({{count}})",
|
||||
"no_voices": "ยังไม่มีเสียง.",
|
||||
@@ -714,6 +866,14 @@
|
||||
"youtube_results": "ผลลัพธ์ของ YouTube ({{count}})",
|
||||
"clone_profile": "โปรไฟล์โคลน",
|
||||
"crop_audio": "ครอบตัดเสียง",
|
||||
"cat_disney": "ดิสนีย์",
|
||||
"cat_anime": "อะนิเมะ",
|
||||
"cat_marvel": "มาร์เวล/ดีซี",
|
||||
"cat_celebs": "คนดัง",
|
||||
"cat_politicians": "นักการเมือง",
|
||||
"cat_news": "ผู้ประกาศข่าว",
|
||||
"cat_gaming": "การเล่นเกม",
|
||||
"cat_books": "หนังสือ/ภาพยนตร์",
|
||||
"subtitle": "เสียงที่ออกแบบมาสำเร็จรูปหลายร้อยเสียง — เลือกหนึ่งเสียงแล้วไปได้เลย",
|
||||
"zone_archetypes": "ต้นแบบ",
|
||||
"zone_imports": "การนำเข้าของฉัน",
|
||||
@@ -818,7 +978,6 @@
|
||||
"badge": "ใบอนุญาตการค้า",
|
||||
"hero_title": "จัดส่งเสียง AI ในการผลิต",
|
||||
"hero_desc": "OmniVoice Studio มีแหล่งที่มาภายใต้ Functional Source License (FSL) ผู้ใช้ส่วนใหญ่สามารถประเมิน สร้างต้นแบบ และแม้แต่ปรับใช้ภายในโดยไม่ต้องมีข้อตกลงทางการค้า คุณต้องมีใบอนุญาตเชิงพาณิชย์เฉพาะในกรณีที่คุณกำลังสร้างผลิตภัณฑ์หรือบริการของคู่แข่ง หรือหากกรณีการใช้งานของคุณอยู่นอกขอบเขตของ FSL",
|
||||
"hero_note": "การสร้างผลิตภัณฑ์หรือบริการที่แข่งขันกัน หรือการปรับใช้ในวงกว้าง (เช่น การให้บริการ API แบบจ่ายตามการใช้งาน) จำเป็นต้องมีใบอนุญาตเชิงพาณิชย์ ระดับราคาจะมาเร็วๆ นี้ โปรดติดต่อในระหว่างนี้",
|
||||
"why_title": "เหตุใดธุรกิจจึงเลือก OmniVoice",
|
||||
"pricing_title": "ราคา",
|
||||
"faq_title": "คำถามทั่วไป",
|
||||
@@ -838,7 +997,8 @@
|
||||
"benefit_source": "แกนหลักที่มีแหล่งที่มา",
|
||||
"benefit_source_desc": "มองเห็นสแต็กได้เต็มรูปแบบ ตรวจสอบ แยก และปรับเปลี่ยนภายในข้อกำหนดสิทธิ์การใช้งาน",
|
||||
"benefit_lang": "646 ภาษา",
|
||||
"benefit_lang_desc": "ถอดเสียง แปล และพากย์ใน 646 ภาษาด้วยคุณภาพระดับมนุษย์"
|
||||
"benefit_lang_desc": "ถอดเสียง แปล และพากย์ใน 646 ภาษาด้วยคุณภาพระดับมนุษย์",
|
||||
"hero_note": "การใช้งานภายใน — แม้ในปริมาณมาก — ไม่มีค่าใช้จ่ายภายใต้ FSL; จำเป็นต้องมีใบอนุญาตเชิงพาณิชย์เพื่อเสนอ OmniVoice ให้กับผู้อื่นในฐานะผลิตภัณฑ์หรือบริการของคู่แข่งเท่านั้น (API ที่โฮสต์หรือแบบจ่ายตามการใช้งาน แอปที่ขายต่อหรือมีป้ายกำกับสีขาว) ระดับราคากำลังจะมาในเร็วๆ นี้ โปรดติดต่อในระหว่างนี้"
|
||||
},
|
||||
"exportModal": {
|
||||
"export": "ส่งออก",
|
||||
@@ -927,7 +1087,479 @@
|
||||
"dubbing_title": "ดูการดำเนินการพากย์",
|
||||
"dubbing_sync": "การเล่นแบบซิงโครไนซ์",
|
||||
"dubbing_picker": "ลองภาษาอื่น:",
|
||||
"dubbing_cta": "เรียกใช้สิ่งนี้ในวิดีโอของคุณเอง →"
|
||||
"dubbing_cta": "เรียกใช้สิ่งนี้ในวิดีโอของคุณเอง →",
|
||||
"dubbing_loading": "กำลังโหลดการสาธิตการพากย์...",
|
||||
"dubbing_dismiss": "ยกเลิกการสาธิตการพากย์เสียง",
|
||||
"original_tag": "ต้นฉบับ",
|
||||
"dubbed_tag": "ขนานนาม",
|
||||
"script_conversational": "การสนทนา",
|
||||
"script_technical": "คำศัพท์ทางเทคนิค",
|
||||
"script_french": "ไม่ใช่ภาษาอังกฤษ (ฝรั่งเศส)",
|
||||
"aria_pause": "หยุดชั่วคราว {{label}}",
|
||||
"aria_hear": "ได้ยิน {{label}}",
|
||||
"aria_replay": "เล่นซ้ำ {{label}} ผ่านตัวถอดเสียง"
|
||||
},
|
||||
"direction": {
|
||||
"title": "ทิศทางสำหรับส่วน #{{id}}",
|
||||
"desc": "บอกไปป์ไลน์ว่าบรรทัดนี้ควรรู้สึกอย่างไร งานภาษาอังกฤษธรรมดา — ระบบจับคู่คำศัพท์ของคุณเข้ากับอนุกรมวิธานที่มั่นคง (พลังงาน / อารมณ์ / ก้าว / ความใกล้ชิด / พิธีการ) จากนั้นจึงจัดลำดับอนุกรมวิธานผ่านการแปลภาพยนตร์, TTS และ slot-fit",
|
||||
"label": "ทิศทาง",
|
||||
"lineHint": "บรรทัด: \"{{text}}\"",
|
||||
"placeholder": "เช่น เร่งด่วนและประหลาดใจ / อบอุ่น มีความหวัง / กระซิบใกล้ชิด",
|
||||
"previewParse": "ดูตัวอย่างการแยกวิเคราะห์",
|
||||
"previewFailed": "การแสดงตัวอย่างล้มเหลว: {{message}}",
|
||||
"clear": "ชัดเจน",
|
||||
"cancel": "ยกเลิก",
|
||||
"saveDirection": "บันทึกทิศทาง",
|
||||
"ttsInstruct": "TTS สั่ง:",
|
||||
"nothingParsed": "— (ไม่มีอะไรแยกวิเคราะห์)",
|
||||
"translateHint": "แปลคำแนะนำ:",
|
||||
"rateBias": "อคติอัตรา:",
|
||||
"speedsUp": "เร็วขึ้น",
|
||||
"slowsDown": "ช้าลง",
|
||||
"taxonomyTokens": "โทเค็นอนุกรมวิธาน"
|
||||
},
|
||||
"errors": {
|
||||
"title": "แท็บนี้ประสบปัญหาขัดข้อง",
|
||||
"desc": "ไม่ต้องกังวล แอปที่เหลือยังใช้งานได้ คุณสามารถสลับแท็บหรือลองอีกครั้งด้านล่าง",
|
||||
"tryAgain": "ลองอีกครั้ง",
|
||||
"openDocs": "เปิดเอกสารเพื่อดูข้อผิดพลาดนี้"
|
||||
},
|
||||
"keyboard": {
|
||||
"title": "แป้นพิมพ์ลัด",
|
||||
"footer": "กด <1>?</1> เมื่อใดก็ได้เพื่อเปิดสิ่งนี้",
|
||||
"or": "หรือ",
|
||||
"nav": "การนำทาง",
|
||||
"nav_cheatsheet": "แสดงสูตรโกงนี้",
|
||||
"nav_closeModal": "ปิดกิริยา / ยกเลิก",
|
||||
"nav_save": "บันทึกโปรเจ็กต์ / คอมมิตการตัดแต่ง",
|
||||
"segmentEditor": "ตัวแก้ไขส่วน",
|
||||
"seg_split": "แยกส่วนที่เคอร์เซอร์",
|
||||
"seg_merge": "รวมกับส่วนถัดไป",
|
||||
"seg_undo": "เลิกทำ",
|
||||
"seg_redo": "ทำซ้ำ",
|
||||
"seg_click": "การกระทำหลัก",
|
||||
"seg_shiftClick": "เลือกช่วง",
|
||||
"trimmer": "เครื่องตัดเสียง",
|
||||
"trim_playPause": "ดูตัวอย่างการเล่น / หยุดชั่วคราว",
|
||||
"trim_nudgeStart": "ดันที่จับสตาร์ท",
|
||||
"trim_nudgeEnd": "ด้ามจับปลายเขยิบ",
|
||||
"trim_fineNudge": "สะกิดได้ดี",
|
||||
"trim_coarseNudge": "เขยิบหยาบ",
|
||||
"trim_zoomIn": "ซูมเข้า / ออก",
|
||||
"trim_fitAll": "พอดีทั้งหมด / การเลือกพอดี",
|
||||
"trim_confirm": "ยืนยันการตัดแต่ง",
|
||||
"dub": "พากย์",
|
||||
"dub_generate": "สร้างพากย์",
|
||||
"dub_sidebar": "สลับแถบด้านข้าง"
|
||||
},
|
||||
"network": {
|
||||
"sharing_on_title": "แบ่งปันบน — คลิกเพื่อดูรายละเอียด",
|
||||
"share_on_network": "แบ่งปันบนเครือข่ายของคุณ",
|
||||
"switching": "กำลังเปลี่ยน...",
|
||||
"network": "เครือข่าย",
|
||||
"local": "ท้องถิ่น",
|
||||
"share_confirm_title": "แบ่งปันบนเครือข่ายของคุณ?",
|
||||
"share_confirm_hint": "อุปกรณ์อื่นๆ บน Wi-Fi/Ethernet ของคุณจะสามารถเข้าถึง OmniVoice ได้โดยใช้ PIN การเข้าถึงที่แสดงเมื่อเปิดใช้งาน",
|
||||
"enabling": "กำลังเปิดใช้งาน...",
|
||||
"enable": "เปิดใช้งาน",
|
||||
"shared_title": "แชร์บนเครือข่ายของคุณ",
|
||||
"no_interface": "ไม่มีอินเทอร์เฟซเครือข่ายที่สามารถเข้าถึงได้ — เชื่อมต่อกับ Wi-Fi/Ethernet",
|
||||
"copy_link": "คัดลอกลิงก์",
|
||||
"open_in_browser": "เปิดในเบราว์เซอร์",
|
||||
"qr_alt": "คิวอาร์สำหรับ {{ip}}",
|
||||
"pin": "พิน:",
|
||||
"stop_sharing": "หยุดแชร์",
|
||||
"copied": "คัดลอกแล้ว",
|
||||
"enable_error": "ไม่สามารถเปิดใช้งานการแชร์: {{message}}",
|
||||
"disable_error": "ไม่สามารถปิดการใช้งาน: {{message}}"
|
||||
},
|
||||
"readiness": {
|
||||
"checking_system": "กำลังตรวจสอบระบบ...",
|
||||
"all_ready": "พร้อมทุกระบบ",
|
||||
"system_readiness": "ความพร้อมของระบบ",
|
||||
"asr_model": "รุ่น ASR",
|
||||
"loaded_ready": "โหลดแล้วพร้อมครับ",
|
||||
"loading_first_run": "กำลังโหลด... (อาจใช้เวลา 1-2 นาทีในการเรียกใช้ครั้งแรก)",
|
||||
"failed_to_load": "โหลดไม่สำเร็จ",
|
||||
"not_loaded_yet": "ยังไม่ได้โหลด — จะโหลดเมื่อถอดเสียงเป็นคำครั้งแรก",
|
||||
"error_check_logs": "ข้อผิดพลาด: {{error}} ตรวจสอบบันทึกและลองรีสตาร์ท",
|
||||
"check_logs_restart": "ตรวจสอบบันทึกเพื่อหาข้อผิดพลาดในการโหลดโมเดล ลองรีสตาร์ท",
|
||||
"llm_cinematic": "LLM (ภาพยนตร์)",
|
||||
"llm_configure": "กำหนดค่า TRANSLATE_BASE_URL สำหรับคุณภาพการแปลแบบภาพยนตร์",
|
||||
"llm_set_env": "ตั้งค่าตัวแปรสภาพแวดล้อม TRANSLATE_BASE_URL และ TRANSLATE_API_KEY ทำงานร่วมกับ Ollama, OpenAI, LM Studio ฯลฯ",
|
||||
"llm_optional": "ไม่บังคับ — ตั้งค่า TRANSLATE_BASE_URL สำหรับคุณภาพระดับภาพยนตร์"
|
||||
},
|
||||
"license": {
|
||||
"title": "Supertonic-3 — การยอมรับใบอนุญาต",
|
||||
"intro": "Supertonic-3 จัดส่งภายใต้ใบอนุญาตที่แตกต่างกันสองฉบับ โปรดตรวจสอบทั้งสองอย่างก่อนที่จะเปิดใช้งานเครื่องยนต์",
|
||||
"sdk_heading": "รหัส SDK · MIT",
|
||||
"sdk_desc": "SDK การอนุมาน Python (supertonic) ได้รับอนุญาตจาก MIT การใช้งานที่ได้รับอนุญาต รวมถึงเชิงพาณิชย์",
|
||||
"read_mit": "อ่านใบอนุญาต MIT →",
|
||||
"model_heading": "ตุ้มน้ำหนักโมเดล · OpenRAIL-M",
|
||||
"model_desc": "ตุ้มน้ำหนักรุ่น Supertonic-3 ได้รับการเผยแพร่ภายใต้ใบอนุญาต OpenRAIL-M ใบอนุญาตนี้จำกัดการใช้งานเพื่อวัตถุประสงค์ที่ไม่เป็นอันตราย — ดูใบอนุญาตที่เชื่อมโยงเพื่อดูข้อจำกัดตามการใช้งานทั้งหมด",
|
||||
"read_openrail": "อ่านใบอนุญาต OpenRAIL-M →",
|
||||
"footer": "การคลิกยอมรับจะบันทึกการยอมรับของคุณในการตั้งค่าภายในเครื่องของ OmniVoice และเปิดใช้งานกลไก การยอมรับของคุณจะถูกเก็บไว้ในเครื่องนี้เท่านั้น ไม่มีการรายงานไปยัง Supertone Inc. หรือบุคคลที่สามใดๆ",
|
||||
"saving": "กำลังบันทึก...",
|
||||
"accept": "ยอมรับ",
|
||||
"accepted_toast": "ยอมรับใบอนุญาต Supertonic-3 แล้ว",
|
||||
"accept_error": "ไม่สามารถบันทึกการยอมรับใบอนุญาต: {{message}}"
|
||||
},
|
||||
"voicePreview": {
|
||||
"title": "การแสดงตัวอย่างด้วยเสียง",
|
||||
"close": "ปิดการแสดงตัวอย่าง",
|
||||
"default_text": "สวัสดี! นี่คือการแสดงตัวอย่างเสียงของฉันในน้ำเสียงนี้",
|
||||
"default_voice": "เสียงเริ่มต้น",
|
||||
"clone_profiles": "โปรไฟล์โคลน",
|
||||
"designed_voices": "เสียงที่ออกแบบมา",
|
||||
"presets": "ค่าที่ตั้งล่วงหน้า",
|
||||
"placeholder": "พิมพ์บางอย่างเพื่อฟัง...",
|
||||
"stop": "หยุด",
|
||||
"regenerate": "สร้างใหม่",
|
||||
"preview": "ดูตัวอย่าง",
|
||||
"hint": "8 ขั้นตอน · ดูตัวอย่างอย่างรวดเร็ว"
|
||||
},
|
||||
"header": {
|
||||
"kicker_studio": "สตูดิโอ",
|
||||
"kicker_library": "ห้องสมุด",
|
||||
"kicker_preferences": "การตั้งค่า",
|
||||
"kicker_licensing": "การออกใบอนุญาต",
|
||||
"label_launchpad": "Launchpad",
|
||||
"label_clone": "โคลนเสียง",
|
||||
"label_design": "การออกแบบเสียง",
|
||||
"label_dub": "การพากย์เสียง",
|
||||
"label_projects": "OmniDrive",
|
||||
"label_gallery": "แกลเลอรี่",
|
||||
"label_transcriptions": "การถอดเสียง",
|
||||
"label_settings": "การตั้งค่า",
|
||||
"label_enterprise": "ใบอนุญาตการค้า",
|
||||
"status_ready": "พร้อม",
|
||||
"status_loading": "กำลังโหลด...",
|
||||
"status_idle": "ไม่ได้ใช้งาน",
|
||||
"memory_management": "การจัดการหน่วยความจำ",
|
||||
"flush": "ฟลัช",
|
||||
"loaded_models": "โมเดลที่โหลดแล้ว",
|
||||
"no_models": "ไม่มีการโหลดโมเดล",
|
||||
"unload": "ยกเลิกการโหลด",
|
||||
"flush_caches": "ล้างแคช",
|
||||
"unload_all_flush": "ยกเลิกการโหลดทั้งหมด + ฟลัช"
|
||||
},
|
||||
"sidebar": {
|
||||
"tab_drive": "ขับรถ",
|
||||
"tab_history": "ประวัติศาสตร์",
|
||||
"tab_exports": "การส่งออก",
|
||||
"save_project": "บันทึกโครงการพากย์",
|
||||
"save_new_project": "บันทึกเป็นโครงการพากย์ใหม่",
|
||||
"dub_projects": "โครงการพากย์",
|
||||
"voice_clones": "เสียงโคลน",
|
||||
"designed_voices": "เสียงที่ออกแบบมา",
|
||||
"no_dub_projects": "ไม่มีโครงการพากย์ที่บันทึกไว้",
|
||||
"no_dub_hint": "อัปโหลดวิดีโอแล้วคลิกบันทึกเพื่อเก็บงานของคุณไว้",
|
||||
"no_clones": "ยังไม่มีเสียงโคลน",
|
||||
"no_clones_hint": "บันทึกหรืออัพโหลดเสียง จากนั้นคลิกบันทึกเป็นโปรไฟล์เสียง",
|
||||
"no_designs": "ยังไม่มีเสียงที่ออกแบบ",
|
||||
"no_designs_hint": "สร้างเสียงและบันทึกไว้จากประวัติ",
|
||||
"history_subtitle": "ประวัติการสร้าง · เก็บไว้ใน SQLite",
|
||||
"no_history": "ไม่มีประวัติรุ่น",
|
||||
"no_history_hint": "สังเคราะห์เสียงหรือพากย์วิดีโอ ผลลัพธ์จะปรากฏที่นี่",
|
||||
"clear_history": "ล้างประวัติ",
|
||||
"clear_confirm": "ล้างรายการประวัติ {{count}} ทั้งหมดหรือไม่ สิ่งนี้ไม่สามารถยกเลิกได้",
|
||||
"history_cleared": "ล้างประวัติแล้ว",
|
||||
"recent_exports": "การส่งออกล่าสุด",
|
||||
"no_exports": "ไม่มีเอาต์พุตที่ดาวน์โหลด",
|
||||
"no_exports_hint": "ส่งออกไฟล์ผ่าน Tauri เพื่อดูการติดตามที่นี่",
|
||||
"show_in_folder": "แสดงในโฟลเดอร์",
|
||||
"open": "เปิด",
|
||||
"select": "เลือก",
|
||||
"try_voice": "ลอง",
|
||||
"consistent": "สม่ำเสมอ",
|
||||
"locked": "ล็อคแล้ว",
|
||||
"clone_label": "โคลน",
|
||||
"design_label": "การออกแบบ",
|
||||
"dub_label": "พากย์",
|
||||
"save_label": "บันทึก",
|
||||
"lock_identity": "ล็อคตัวตนของเสียง",
|
||||
"in_folder": "ใน {{folder}}"
|
||||
},
|
||||
"trimmer": {
|
||||
"title": "ตัดเสียงอ้างอิง",
|
||||
"decoding": "กำลังถอดรหัสเสียง...",
|
||||
"meta_length": "ความยาว {{duration}} · {{sampleRate}} เฮิร์ตซ์",
|
||||
"meta_rendering": "เรนเดอร์รูปคลื่น {{percent}}%",
|
||||
"keyboard_hint": "scroll = ซูม · shift+scroll = pan · alt+drag = pan · ⏐ ⟵ ⟶ ⏐ ปุ่มปรับที่จับ",
|
||||
"zoom_in": "ซูมเข้า (+)",
|
||||
"zoom_out": "ซูมออก (-)",
|
||||
"fit_all": "พอดีทั้งหมด (บ้าน)",
|
||||
"fit_selection": "การเลือกความพอดี (จบ)",
|
||||
"fit_sel_btn": "ฟิตเซล",
|
||||
"view_range": "ดู {{start}} → {{end}} ({{duration}})",
|
||||
"start_label": "เริ่ม",
|
||||
"end_label": "สิ้นสุด",
|
||||
"length_label": "ความยาว",
|
||||
"too_long": ">{{max}}วิ",
|
||||
"too_short": "สั้นเกินไป",
|
||||
"length_ok": "ตกลง",
|
||||
"loop_preview": "ดูตัวอย่างวง",
|
||||
"pause": "หยุดชั่วคราว",
|
||||
"preview_selection": "ดูตัวอย่างการเลือก",
|
||||
"play_hint": "พื้นที่ที่จะเล่น · เข้าไปเพื่อยืนยัน · Esc เพื่อยกเลิก",
|
||||
"cancel": "ยกเลิก",
|
||||
"use_trimmed": "ใช้ตัดแต่ง",
|
||||
"decode_failed": "การถอดรหัสล้มเหลว: {{message}}",
|
||||
"playback_failed": "การเล่นล้มเหลว: {{message}}",
|
||||
"audio_load_failed": "โหลดเสียงล้มเหลว",
|
||||
"unit_seconds": "ส"
|
||||
},
|
||||
"casting": {
|
||||
"title": "การหล่อลำโพง",
|
||||
"auto_assign_title": "กำหนดเสียงอัตโนมัติจากโคลนลำโพงที่แยกออกมา",
|
||||
"auto_cast": "แคสต์อัตโนมัติ",
|
||||
"all_cast": "นักแสดงทั้งหมด",
|
||||
"segments_count": "{{count}} ส่วน",
|
||||
"assign_voice": "กำหนดเสียง...",
|
||||
"from_video": "🎶 จากวีดีโอ ({{name}})",
|
||||
"no_profiles": "ยังไม่มีการบันทึกโปรไฟล์เสียง",
|
||||
"preview_voice": "ดูตัวอย่างเสียง"
|
||||
},
|
||||
"checkpoint": {
|
||||
"asr_title": "ทรานสคริปต์พร้อม",
|
||||
"asr_cta": "แปล",
|
||||
"asr_hint": "แก้ไขข้อผิดพลาด ASR ทันที การใช้ถ้อยคำที่รัดกุมจะช่วยบันทึกความพยายามของ TTS ในภายหลัง",
|
||||
"translate_title": "แปลพร้อมแล้ว",
|
||||
"translate_cta": "สร้างพากย์",
|
||||
"translate_hint": "ข้ามข้อความเป้าหมาย เส้นที่ยาวเกินจะถูกเพิ่มความเร็ว คุณสามารถแก้ไขได้โดยตรง",
|
||||
"done_title": "พากย์เสร็จแล้ว",
|
||||
"done_hint": "ตรวจสอบระยะเวลาและอัตราส่วนการซิงค์ ปรับแต่งบรรทัดใดก็ได้แล้วกด \"Regen changes\" เพื่อการทำซ้ำบางส่วนอย่างรวดเร็ว",
|
||||
"segment_one": "{{count}} ส่วน",
|
||||
"segment_other": "{{count}} ส่วน",
|
||||
"dismiss_title": "ยกเลิก — จะไม่ปรากฏขึ้นอีกสำหรับขั้นตอนนี้จนกว่าจะโหลดซ้ำ"
|
||||
},
|
||||
"compare": {
|
||||
"title": "การเปรียบเทียบเสียง A/B",
|
||||
"close": "ปิดการเปรียบเทียบ",
|
||||
"desc": "เปรียบเทียบสองเสียงเคียงข้างกันเพื่อตัดสินใจคัดเลือกนักแสดง แอพยังคงโต้ตอบอยู่เบื้องหลัง",
|
||||
"test_phrase": "วลีทดสอบ",
|
||||
"voice_a": "เสียงก",
|
||||
"voice_b": "วอยซ์ บี",
|
||||
"select_voice": "— เลือกเสียง —",
|
||||
"preset_suffix": "(พรีเซ็ต)",
|
||||
"no_audio": "ยังไม่มีเสียง",
|
||||
"close_btn": "ปิด",
|
||||
"comparing": "กำลังเปรียบเทียบ...",
|
||||
"compare_btn": "เปรียบเทียบ",
|
||||
"preparing_voice": "กำลังเตรียมเสียง...",
|
||||
"generating_voice_a": "กำลังสร้างเสียง A...",
|
||||
"generating_voice_b": "กำลังสร้างเสียงข...",
|
||||
"comparison_complete": "เปรียบเทียบเสร็จแล้ว!",
|
||||
"play_failed": "การเล่นล้มเหลว: {{message}}"
|
||||
},
|
||||
"models": {
|
||||
"hf_token_set_toast": "ชุดโทเค็น HuggingFace — เปิดใช้งานการดาวน์โหลดที่เร็วขึ้น",
|
||||
"hf_token_save_failed": "บันทึกโทเค็นไม่สำเร็จ",
|
||||
"install_started": "เริ่มการติดตั้ง — ความคืบหน้าในแถว",
|
||||
"delete_confirm": "ลบ {{repoId}}? คุณสามารถติดตั้งใหม่ได้ในภายหลัง",
|
||||
"delete_confirm_title": "ลบโมเดล",
|
||||
"deleted": "ลบแล้ว {{repoId}}",
|
||||
"reinstall_confirm": "ติดตั้ง {{repoId}} ใหม่อีกครั้งหรือไม่ การดำเนินการนี้จะลบสำเนาปัจจุบันและดาวน์โหลดอีกครั้ง",
|
||||
"reinstall_confirm_title": "ติดตั้งโมเดลอีกครั้ง",
|
||||
"reinstalling": "กำลังติดตั้งใหม่",
|
||||
"recommended_installed": "รุ่นที่แนะนำได้รับการติดตั้งแล้ว",
|
||||
"started_downloading_one": "เริ่มดาวน์โหลดโมเดล {{count}}",
|
||||
"started_downloading_other": "เริ่มดาวน์โหลดโมเดล {{count}}",
|
||||
"install_failed": "การติดตั้งล้มเหลว: {{message}}",
|
||||
"removing_cached": "กำลังนำการแก้ไขที่แคชไว้ออก...",
|
||||
"resolving_metadata": "การแก้ไขข้อมูลเมตา repo",
|
||||
"retry_attempt": "ลองอีกครั้ง {{attempt}} — {{error}}",
|
||||
"connecting_hf": "กำลังเชื่อมต่อกับ HuggingFace...",
|
||||
"resolving_files_one": "กำลังแก้ไขไฟล์ {{count}}...",
|
||||
"resolving_files_other": "กำลังแก้ไขไฟล์ {{count}}...",
|
||||
"resolving_files_active": "กำลังแก้ไขไฟล์ {{count}}… · {{file}}",
|
||||
"measuring": "วัด...",
|
||||
"files_progress": "{{done}}/{{total}} ไฟล์",
|
||||
"install_error": "การติดตั้งล้มเหลว: {{error}}",
|
||||
"view_on_hf": "ดูบน HuggingFace",
|
||||
"install_btn": "ติดตั้ง",
|
||||
"reinstall_btn": "ติดตั้งใหม่",
|
||||
"downloading": "กำลังดาวน์โหลด",
|
||||
"deleting": "กำลังลบ",
|
||||
"working": "การทำงาน",
|
||||
"installed": "ติดตั้งแล้ว",
|
||||
"not_installed": "ไม่ได้ติดตั้ง",
|
||||
"required_tag": "จำเป็น",
|
||||
"delete_btn": "ลบ",
|
||||
"hf_token_btn": "โทเค็น HF",
|
||||
"hf_set_title": "ตั้งค่าโทเค็น HuggingFace เพื่อการดาวน์โหลดที่เร็วขึ้น",
|
||||
"get_token": "รับโทเค็น →",
|
||||
"reco_installed_for": "บันเดิลที่แนะนำที่ติดตั้งสำหรับ {{device}}",
|
||||
"reco_for": "แนะนำสำหรับ {{device}}",
|
||||
"starting": "กำลังเริ่ม...",
|
||||
"required_size": "จำเป็น ~{{size}} GB",
|
||||
"all_size": "ทั้งหมด ~{{size}} GB",
|
||||
"req_tag": "คำขอ",
|
||||
"search_placeholder": "ค้นหารุ่น...",
|
||||
"search_label": "ค้นหาโมเดล",
|
||||
"no_matches": "ไม่มีรุ่นที่ตรงกับตัวกรองของคุณ",
|
||||
"sort_by": "จัดเรียงตาม {{column}}",
|
||||
"column_model": "รุ่น",
|
||||
"column_role": "บทบาท",
|
||||
"column_size": "ขนาด",
|
||||
"column_status": "สถานะ",
|
||||
"ready_badge": "พร้อม",
|
||||
"loading_badge": "กำลังโหลด...",
|
||||
"idle_badge": "ไม่ได้ใช้งาน",
|
||||
"started_downloading_required_one": "เริ่มดาวน์โหลด {{count}} โมเดลที่ต้องการ",
|
||||
"started_downloading_required_other": "เริ่มดาวน์โหลด {{count}} โมเดลที่ต้องการ"
|
||||
},
|
||||
"enterprise_faq": {
|
||||
"q_internal_tools": "ฉันจำเป็นต้องมีใบอนุญาตสำหรับเครื่องมือภายในหรือไม่?",
|
||||
"a_internal_tools": "การใช้งานภายในโดยพนักงานและผู้รับเหมาของคุณเป็นวัตถุประสงค์ที่ได้รับอนุญาตภายใต้ FSL — ไม่ต้องมีใบอนุญาต จำเป็นต้องมีใบอนุญาตเชิงพาณิชย์เมื่อคุณทำให้ OmniVoice พร้อมใช้งานสำหรับผู้อื่นโดยเป็นส่วนหนึ่งของผลิตภัณฑ์หรือบริการของคู่แข่ง (การขายต่อ, SaaS ที่โฮสต์, white-label)",
|
||||
"q_try_before": "ฉันสามารถลองก่อนที่จะกระทำได้หรือไม่?",
|
||||
"a_try_before": "ใช่ แอปตัวเต็มสามารถดาวน์โหลดและเรียกใช้ภายในเครื่องได้ฟรีเพื่อการประเมินภายใต้ FSL เมื่อคุณพร้อมที่จะหารือเกี่ยวกับการปรับใช้งานเชิงพาณิชย์ โปรดส่งอีเมลถึงเรา แล้วเราจะดูรายละเอียดร่วมกัน",
|
||||
"q_watermark": "แล้วลายน้ำล่ะ?",
|
||||
"a_watermark": "ลายน้ำ AudioSeal ที่มองไม่เห็นจะถูกฝังไว้ตามค่าเริ่มต้น ผู้ได้รับใบอนุญาตเชิงพาณิชย์สามารถปิดการใช้งานได้ในการตั้งค่า → ความเป็นส่วนตัว การใช้งานฟรี/ส่วนตัวจะมีลายน้ำรวมอยู่ด้วยเสมอ",
|
||||
"q_apache": "แหล่งที่มาเคยเป็น Apache 2.0 หรือไม่",
|
||||
"a_apache": "ใช่ แต่ละรุ่นจะแปลงเป็น Apache License เวอร์ชัน 2.0 โดยอัตโนมัติในวันครบรอบปีที่สองของการเผยแพร่ นั่นหมายถึงการเปิดตัวในวันนี้คือ Apache 2.0 ในอีกสองปี โดยไม่ต้องดำเนินการใดๆ จากเรา — FSL รับประกันว่าจะเพิกถอนไม่ได้"
|
||||
},
|
||||
"gallery_extra": {
|
||||
"save_prompt": "ป้อนชื่อโปรไฟล์เสียงนี้:",
|
||||
"saved_as_profile": "บันทึกเสียงเป็นโปรไฟล์แล้ว!",
|
||||
"save_profile_failed": "ไม่สามารถบันทึกโปรไฟล์ได้",
|
||||
"download_failed": "ดาวน์โหลดล้มเหลว: {{message}}",
|
||||
"trim_load_failed": "ไม่สามารถโหลดเสียงสำหรับการตัด: {{message}}",
|
||||
"upload_crop_failed": "ไม่สามารถอัปโหลดเสียงที่ครอบตัด: {{message}}"
|
||||
},
|
||||
"dub_workflow": {
|
||||
"preparing_audio": "กำลังเตรียมเสียง...",
|
||||
"preparing_video": "กำลังเตรียมวิดีโอ...",
|
||||
"extracting_audio_scenes": "กำลังแยกเสียงและฉาก...",
|
||||
"transcribing_audio": "กำลังถอดเสียง...",
|
||||
"transcription_complete": "การถอดเสียงเป็นคำเสร็จสมบูรณ์",
|
||||
"upload_cancelled": "ยกเลิกการอัปโหลดแล้ว",
|
||||
"upload_failed": "การอัปโหลดล้มเหลว: {{message}}",
|
||||
"downloading_video": "กำลังดาวน์โหลดวิดีโอ...",
|
||||
"ingested": "ติดเครื่อง {{url}}",
|
||||
"ingest_cancelled": "ยกเลิกการนำเข้าแล้ว",
|
||||
"ingest_failed": "การนำเข้า URL ล้มเหลว: {{message}}",
|
||||
"retry_cancelled": "ลองอีกครั้ง ยกเลิก",
|
||||
"transcription_failed": "การถอดเสียงล้มเหลว: {{message}}",
|
||||
"import_srt_no_job": "อัปโหลดหรือนำเข้าวิดีโอก่อน ไม่มีงานให้แนบคำบรรยาย",
|
||||
"imported_cues": "นำเข้า {{count}} คิวจาก {{file}}",
|
||||
"skipped_malformed": "{{count}} ข้ามไป (มีรูปแบบไม่ถูกต้อง)",
|
||||
"dropped_overlap": "{{count}} ลดลง (ทับซ้อนกัน)",
|
||||
"clamped_to_duration": "{{count}} ถูกหนีบเข้ากับความยาวของสื่อ",
|
||||
"srt_import_failed": "การนำเข้า SRT ล้มเหลว",
|
||||
"cleaned_one": "ทำความสะอาดส่วน {{count}} แล้ว",
|
||||
"cleaned_other": "ทำความสะอาด {{count}} แฟรกเมนต์",
|
||||
"segments_clean": "ล้างกลุ่มแล้ว",
|
||||
"cleanup_failed": "การล้างข้อมูลล้มเหลว: {{message}}",
|
||||
"cinematic_no_llm": "คุณภาพระดับภาพยนตร์จำเป็นต้องมี LLM — ตั้งค่า TRANSLATE_BASE_URL + TRANSLATE_API_KEY (Ollama ทำงานในเครื่อง) ถอยกลับไปอย่างรวดเร็ว",
|
||||
"translate_errors": "{{errorCount}}/{{totalCount}} เซ็กเมนต์ล้มเหลว: {{firstError}}",
|
||||
"translated_segments": "แปล {{count}} ส่วน → {{lang}}",
|
||||
"translated_cinematic_suffix": "(ภาพยนตร์)",
|
||||
"translation_failed": "การแปลล้มเหลว: {{message}}",
|
||||
"regenerating": "กำลังสร้างเซ็กเมนต์ {{count}} ใหม่...",
|
||||
"generating_dub": "กำลังสร้างเสียงพากย์…",
|
||||
"generating_progress": "กำลังสร้างพากย์… {{current}}/{{total}}",
|
||||
"generation_aborted": "รุ่นถูกยกเลิก",
|
||||
"dubbing_aborted": "ยกเลิกการพากย์แล้ว",
|
||||
"generation_stream_ended": "กระแสการสร้างสิ้นสุดลงก่อนที่จะเสร็จสิ้น",
|
||||
"dub_complete": "พากย์เสร็จแล้ว",
|
||||
"stop_failed": "ไม่สามารถหยุดได้",
|
||||
"save_first": "โปรดคลิก 'อัปโหลดและถอดเสียง' ก่อน เพื่อให้วิดีโอได้รับการประมวลผลบนเซิร์ฟเวอร์ก่อนบันทึก",
|
||||
"project_saved": "บันทึกโครงการแล้ว",
|
||||
"project_created": "สร้างโครงการแล้ว",
|
||||
"save_failed": "บันทึกล้มเหลว: {{message}}",
|
||||
"opened_project": "เปิดแล้ว: {{name}}",
|
||||
"delete_project_confirm": "ลบโปรเจ็กต์นี้ใช่ไหม สิ่งนี้ไม่สามารถยกเลิกได้",
|
||||
"project_deleted": "ลบโครงการแล้ว",
|
||||
"delete_history_confirm": "ลบรายการประวัตินี้หรือไม่",
|
||||
"history_deleted": "ลบรายการประวัติแล้ว",
|
||||
"restored_state": "คืนค่าสถานะรุ่นก่อนหน้า",
|
||||
"upgrading_preview": "กำลังอัปเกรด {{count}} กลุ่มคุณภาพตัวอย่างเป็นคุณภาพเต็ม..."
|
||||
},
|
||||
"tts_errors": {
|
||||
"enter_text": "กรุณากรอกข้อความ",
|
||||
"upload_or_select": "อัปโหลดเสียงหรือเลือกโปรไฟล์เสียง",
|
||||
"trim_hint": "เสียงคือ {{duration}}s — ตัดเป็น ≤{{max}}s เพื่อการโคลนที่ดีที่สุด",
|
||||
"timeout": "การสร้างหมดเวลา — โมเดลอาจยังคงดาวน์โหลดอยู่ ตรวจสอบการตั้งค่า → บันทึก จากนั้นลองอีกครั้ง",
|
||||
"error_prefix": "ข้อผิดพลาด: {{message}}",
|
||||
"ignored_unsupported": "ละเว้นคำสั่งที่ไม่สนับสนุน: {{items}}",
|
||||
"ignored_duplicate": "ละเว้น (หมวดหมู่ที่ตั้งไว้แล้ว): {{items}}"
|
||||
},
|
||||
"sharing": {
|
||||
"title": "การแชร์และการเข้าถึงระยะไกล",
|
||||
"help": "เปิดเผยอินสแตนซ์ OmniVoice ที่ทำงานอยู่นี้ไปยังเครื่องอื่นๆ ของคุณโดยไม่ต้องรีสตาร์ท Loopback-only เป็นค่าเริ่มต้น — จะไม่มีการแชร์ใดๆ จนกว่าคุณจะเปิดใช้งานที่นี่",
|
||||
"local_network": "เครือข่ายท้องถิ่น",
|
||||
"local_help": "แบ่งปันบน Wi-Fi / Ethernet ของคุณด้วย PIN การเข้าถึงเพียงครั้งเดียว อุปกรณ์อื่นๆ สแกนรหัส QR หรือเปิดลิงก์",
|
||||
"ports_title": "พอร์ต",
|
||||
"ports_help": "สิ่งเหล่านี้ถูกตั้งค่าผ่านตัวแปรสภาพแวดล้อมที่อ่านเมื่อเริ่มต้น เปลี่ยนแบ็กเอนด์หรือพอร์ต UI โดยการตั้งค่าตัวแปรและรีสตาร์ท OmniVoice",
|
||||
"backend_port": "พอร์ตแบ็กเอนด์",
|
||||
"ui_port": "พอร์ตยูไอ",
|
||||
"lan_share_port": "พอร์ต LAN-share",
|
||||
"port_error": "ป้อนพอร์ตระหว่าง 1024 ถึง 65535",
|
||||
"port_saved": "บันทึกพอร์ต LAN-share แล้ว — นำไปใช้ในครั้งถัดไปที่คุณเปิดใช้งานการแชร์",
|
||||
"port_save_failed": "ไม่สามารถบันทึกพอร์ต: {{message}}",
|
||||
"saving": "กำลังบันทึก...",
|
||||
"ports_note": "พอร์ตแบ็กเอนด์และ UI มีผลเมื่อรีสตาร์ท พอร์ต LAN-share จะมีผลในครั้งถัดไปที่คุณเปิดใช้งานการแชร์",
|
||||
"tailscale_title": "Tailscale (การเข้าถึงระยะไกลส่วนตัว)",
|
||||
"tailscale_checking": "กำลังตรวจสอบส่วนท้าย...",
|
||||
"tailscale_absent": "ตรวจไม่พบสเกลท้าย ติดตั้งเพื่อเข้าถึง OmniVoice อย่างปลอดภัยจากทุกที่บน tailnet ส่วนตัวของคุณ",
|
||||
"tailscale_install": "ติดตั้งสเกลท้าย",
|
||||
"tailscale_running": "สเกลท้ายกำลังทำงาน ให้บริการ OmniVoice ผ่าน tailnet ส่วนตัวของคุณ",
|
||||
"tailscale_not_logged_in": "ติดตั้ง Tailscale แล้ว แต่ยังไม่ได้เข้าสู่ระบบ ให้เริ่มและลงชื่อเข้าใช้ Tailscale ก่อน",
|
||||
"tailscale_enabled": "เปิดใช้งานการให้บริการส่วนท้ายแล้ว",
|
||||
"tailscale_enable_failed": "ไม่สามารถเปิดใช้งาน Tailscale ได้",
|
||||
"tailscale_enable_error": "ไม่สามารถเปิดใช้งาน Tailscale: {{message}}",
|
||||
"tailscale_disabled": "ปิดใช้การให้บริการ Tailscale แล้ว",
|
||||
"tailscale_disable_failed": "ไม่สามารถปิดใช้งาน Tailscale ได้",
|
||||
"tailscale_disable_error": "ไม่สามารถปิดการใช้งาน Tailscale: {{message}}",
|
||||
"tailscale_enabling": "กำลังเปิดใช้งาน...",
|
||||
"tailscale_enable_btn": "เปิดใช้งานการแสดง Tailscale",
|
||||
"tailscale_disabling": "กำลังปิดใช้งาน...",
|
||||
"tailscale_disable_btn": "หยุดเสิร์ฟ Tailscale",
|
||||
"tailscale_copy": "คัดลอกลิงก์",
|
||||
"tailscale_open": "เปิดในเบราว์เซอร์",
|
||||
"copied": "คัดลอกแล้ว",
|
||||
"tailscale_qr_alt": "รหัส QR สำหรับ Tailscale URL"
|
||||
},
|
||||
"reportBug": {
|
||||
"label": "รายงานข้อบกพร่อง",
|
||||
"title": "เปิดหน้าปัญหา GitHub ที่กรอกไว้ล่วงหน้าในเบราว์เซอร์ของคุณ ไม่มีการส่งสิ่งใดจนกว่าคุณจะคลิกส่ง"
|
||||
},
|
||||
"app": {
|
||||
"loading": "กำลังโหลด...",
|
||||
"trimmed_loaded": "โหลดเสียงที่ตัดแล้ว",
|
||||
"toast_exported": "ส่งออกแล้ว: {{name}}",
|
||||
"toast_export_failed": "การส่งออกล้มเหลว: {{message}}",
|
||||
"toast_open_folder_failed": "ไม่สามารถเปิดโฟลเดอร์: {{message}}",
|
||||
"toast_saving": "กำลังบันทึก {{name}}...",
|
||||
"toast_saved": "บันทึกแล้ว: {{path}}",
|
||||
"toast_save_error": "บันทึกข้อผิดพลาด: {{message}}",
|
||||
"toast_processing": "กำลังประมวลผล {{name}}...",
|
||||
"toast_downloaded": "ดาวน์โหลดแล้ว {{name}}",
|
||||
"toast_download_error": "ข้อผิดพลาดในการดาวน์โหลด: {{message}}",
|
||||
"toast_upload_first": "โปรดคลิก 'อัปโหลดและถอดเสียง' ก่อน เพื่อให้วิดีโอได้รับการประมวลผลบนเซิร์ฟเวอร์ก่อนบันทึก",
|
||||
"toast_save_failed": "บันทึกล้มเหลว: {{message}}",
|
||||
"toast_opened": "เปิดแล้ว: {{name}}",
|
||||
"toast_project_deleted": "ลบโครงการแล้ว",
|
||||
"toast_restored_state": "คืนค่าสถานะรุ่นก่อนหน้า",
|
||||
"toast_history_deleted": "ลบรายการประวัติแล้ว",
|
||||
"toast_flushed": "ฟลัช — RAM {{ram}}G · VRAM {{vram}}G{{unloaded}}",
|
||||
"toast_model_unloaded": "· โมเดลยกเลิกการโหลดแล้ว",
|
||||
"toast_flush_failed": "การฟลัชล้มเหลว: {{message}}",
|
||||
"toast_project_saved": "บันทึกโครงการแล้ว",
|
||||
"toast_project_created": "สร้างโครงการแล้ว"
|
||||
},
|
||||
"update": {
|
||||
"available": "มีอัปเดต {{version}}",
|
||||
"install": "ติดตั้งและรีสตาร์ท",
|
||||
"install_hint": "ดาวน์โหลดอัปเดตและรีสตาร์ทเป็นเวอร์ชันใหม่",
|
||||
"downloading": "กำลังอัปเดต… {{pct}}%",
|
||||
"restart": "รีสตาร์ทเพื่ออัปเดต",
|
||||
"busy": "ทำการพากย์เสียงของคุณให้เสร็จก่อน แล้วจึงติดตั้งอัปเดต",
|
||||
"whats_new": "มีอะไรใหม่",
|
||||
"failed": "การอัปเดตล้มเหลว",
|
||||
"retry": "ลองอีกครั้ง",
|
||||
"dismiss": "ยกเลิก"
|
||||
},
|
||||
"archetypes": {
|
||||
"featured": "จุดเด่น",
|
||||
@@ -945,5 +1577,25 @@
|
||||
"facet_accent": "สำเนียง",
|
||||
"facet_lang": "ภาษา",
|
||||
"facet_whisper": "กระซิบ"
|
||||
},
|
||||
"support": {
|
||||
"tab_support": "สนับสนุน",
|
||||
"tab_license": "ใบอนุญาตการค้า",
|
||||
"toggle_label": "การสนับสนุนหรือใบอนุญาตทางการค้า",
|
||||
"other_ways": "วิธีอื่นในการช่วยเหลือ",
|
||||
"star_github": "ติดดาวบน GitHub",
|
||||
"join_discord": "เข้าร่วมความไม่ลงรอยกัน"
|
||||
},
|
||||
"updates": {
|
||||
"tab": "อัพเดท",
|
||||
"up_to_date": "อัปเดตแล้ว · v{{version}}",
|
||||
"check_now": "ตรวจสอบตอนนี้",
|
||||
"releases": "ประวัติการออกเวอร์ชัน",
|
||||
"current": "ปัจจุบัน",
|
||||
"prerelease": "ดูตัวอย่าง",
|
||||
"loading": "กำลังโหลดรุ่น...",
|
||||
"none": "ไม่พบการเผยแพร่",
|
||||
"load_error": "ไม่สามารถโหลดรุ่นต่างๆ (ออฟไลน์?)",
|
||||
"retry_load": "ลองอีกครั้ง"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,4 @@
|
||||
{
|
||||
"update": {
|
||||
"available": "{{version}} güncellemesi mevcut",
|
||||
"install": "Yükle ve yeniden başlat",
|
||||
"install_hint": "Güncellemeyi indir ve yeni sürümle yeniden başlat",
|
||||
"downloading": "Güncelleniyor… %{{pct}}",
|
||||
"restart": "Güncellemek için yeniden başlat",
|
||||
"busy": "Önce dublajını bitir — sonra güncellemeyi yükle."
|
||||
},
|
||||
"nav": {
|
||||
"clone": "Klonla",
|
||||
"design": "Tasarla",
|
||||
@@ -16,7 +8,10 @@
|
||||
"launchpad": "Başlatma Çubuğu",
|
||||
"gallery": "Galeri",
|
||||
"transcripts": "Transkriptler",
|
||||
"omnidrive": "OmniDrive"
|
||||
"omnidrive": "OmniDrive",
|
||||
"move_rail_right": "Rayı sağa kaydır",
|
||||
"move_rail_left": "Rayı sola taşı",
|
||||
"flip_rail": "Ray tarafını çevir"
|
||||
},
|
||||
"settings": {
|
||||
"general": "Genel",
|
||||
@@ -45,7 +40,40 @@
|
||||
"ffmpeg_missing": "Bulunamadı",
|
||||
"ffmpeg_current": "Geçerli yol",
|
||||
"ffmpeg_desc": "Otomatik algılama başarısız olursa özel bir ffmpeg yolu ayarlayın.",
|
||||
"ffmpeg_saved": "FFmpeg yolu ayarlandı — uygulamak için arka ucu yeniden başlatın."
|
||||
"ffmpeg_saved": "FFmpeg yolu ayarlandı — uygulamak için arka ucu yeniden başlatın.",
|
||||
"diagnostics_copied": "Teşhis kopyalandı; sorun raporunuza yapıştırın.",
|
||||
"updater_desktop": "Güncelleyici yalnızca masaüstü uygulamasında çalışır.",
|
||||
"latest_version": "En son sürümdesiniz.",
|
||||
"logs_load_failed": "Günlükler yüklenemedi: {{message}}",
|
||||
"clear_frontend_confirm": "Bellek içi ön uç günlük arabelleği temizlensin mi?",
|
||||
"clear_frontend_title": "Günlükleri temizle",
|
||||
"frontend_logs_cleared": "Ön uç günlükleri temizlendi",
|
||||
"clear_tauri_confirm": "Tauri tarafındaki günlük dosyaları kesilsin mi? İşletim sistemi yeni girişler yazmaya devam edecektir.",
|
||||
"clear_tauri_title": "Tauri günlüklerini temizle",
|
||||
"nothing_to_clear": "Temizlenecek bir şey yok; diskte henüz Tauri günlük dosyası yok.",
|
||||
"cleared_tauri_one": "{{count}} Tauri günlük dosyası temizlendi",
|
||||
"cleared_tauri_other": "{{count}} Tauri günlük dosyaları temizlendi",
|
||||
"clear_tauri_failed": "Tauri günlükleri temizlenemedi: {{message}}",
|
||||
"clear_backend_confirm": "Arka uç çalışma zamanı + kilitlenme günlükleri temizlensin mi? Bu geri alınamaz.",
|
||||
"clear_backend_title": "Günlükleri temizle",
|
||||
"backend_logs_cleared": "Arka uç günlükleri temizlendi",
|
||||
"clear_backend_failed": "Günlükler temizlenemedi",
|
||||
"copy_failed": "Kopyalama başarısız oldu: {{message}}",
|
||||
"update_check_failed": "Güncelleme kontrolü başarısız oldu: {{message}}",
|
||||
"save_failed": "Kaydetme başarısız oldu: {{message}}",
|
||||
"clear_failed": "Temizleme başarısız oldu: {{message}}",
|
||||
"engine_switched": "{{family}} → {{engine}}",
|
||||
"channel_set_failed": "Kanal ayarlanamadı: {{message}}",
|
||||
"updater_downloading": "{{version}} indiriliyor…",
|
||||
"updater_installed": "Yüklendi - yeniden başlatılıyor.",
|
||||
"updater_available_title": "Güncelleme mevcut",
|
||||
"updater_available_body": "Sürüm {{version}} mevcut.\n\n{{notes}}\n\nŞimdi indirip yüklemek ister misiniz?",
|
||||
"updater_notes_fallback": "GitHub'daki sürüm notlarına bakın.",
|
||||
"shortcut_load_failed": "Kısayol yüklenemedi: {{message}}",
|
||||
"shortcut_set": "Dikte kısayolu {{shortcut}} olarak ayarlandı",
|
||||
"shortcut_register_failed": "Kaydedilemedi: {{message}}",
|
||||
"shortcut_reset": "Varsayılana sıfırla",
|
||||
"shortcut_reset_failed": "Sıfırlama başarısız oldu: {{message}}"
|
||||
},
|
||||
"bootstrap": {
|
||||
"title": "OmniVoice Studio",
|
||||
@@ -69,7 +97,13 @@
|
||||
"waiting_output": "Çıktı bekleniyor…",
|
||||
"auto_detect": "Otomatik algıla",
|
||||
"suggest_lang": "Türkçe'ye geçilsin mi?",
|
||||
"select_lang": "Dil:"
|
||||
"select_lang": "Dil:",
|
||||
"region_global": "Küresel (doğrudan)",
|
||||
"region_china": "Çin (ayna)",
|
||||
"region_russia": "Rusya (ayna)",
|
||||
"region_restricted": "Kısıtlanmış (ayna)",
|
||||
"unknown_error": "Bilinmeyen hata",
|
||||
"retrying": "Yeniden deneniyor…"
|
||||
},
|
||||
"stories": {
|
||||
"title": "Hikaye Düzenleyici",
|
||||
@@ -158,7 +192,17 @@
|
||||
"reload": "Kullanıcı Arayüzünü Yeniden Yüklemeye Zorla",
|
||||
"backend": "Arka uç",
|
||||
"frontend": "Ön uç",
|
||||
"tauri": "Tauri"
|
||||
"tauri": "Tauri",
|
||||
"cancelOp": "İşlemi iptal et",
|
||||
"dismiss": "Reddet",
|
||||
"dismissStatus": "Durumu reddet",
|
||||
"search": "Ara…",
|
||||
"no_matches": "Eşleşme yok",
|
||||
"recent_and_popular": "En Yeni ve Popüler",
|
||||
"popular_label": "Popüler",
|
||||
"showing_of": "{{total}} / {{shown}} gösteriliyor. Aramak için yazın…",
|
||||
"yes": "Evet",
|
||||
"no": "Hayır"
|
||||
},
|
||||
"launchpad": {
|
||||
"greeting": "merhaba orada",
|
||||
@@ -180,7 +224,16 @@
|
||||
"locked": "KİLİTLİ",
|
||||
"open": "Açık",
|
||||
"try_it": "Deneyin",
|
||||
"audio_only": "Yalnızca Ses"
|
||||
"audio_only": "Yalnızca Ses",
|
||||
"stories_title": "Hikayeler",
|
||||
"stories_desc": "Çok sesli sesli kitaplar — karakterlerinizi yayınlayın, bir komut dosyası ekleyin, bölüme göre dışa aktarın.",
|
||||
"gallery_title": "Ses Galerisi",
|
||||
"gallery_desc": "Hazır tasarlanmış seslere aksan, yaş ve stile göre göz atın; kurulum gerektirmez.",
|
||||
"transcripts_title": "Transkriptler",
|
||||
"transcripts_desc": "Sesi veya videoyu 646 dilde düzenlenebilir, aranabilir metne dönüştürün.",
|
||||
"recent_files": "Son dosyalar",
|
||||
"view_all_files": "Tüm dosyaları görüntüle",
|
||||
"file": "Dosya"
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "İstemi",
|
||||
@@ -271,11 +324,6 @@
|
||||
"outputs": "Çıkışlar",
|
||||
"crash_log": "Kilitlenme günlüğü",
|
||||
"update_endpoint": "Uç noktayı güncelle",
|
||||
"update_channel": "Güncelleme kanalı",
|
||||
"channel_stable": "Kararlı",
|
||||
"channel_preview": "Önizleme",
|
||||
"channel_set": "Güncelleme kanalı {{channel}} olarak ayarlandı",
|
||||
"channel_preview_hint": "Önizleme en son main yapısını izler — daha yeni özellikler, daha az test. Daha yeni bir kararlı sürüm varsa kararlıya geri döner.",
|
||||
"yes": "evet",
|
||||
"no": "hayır",
|
||||
"web_preview": "web önizlemesi",
|
||||
@@ -284,7 +332,12 @@
|
||||
"copy_diagnostics": "Tanılamayı kopyala",
|
||||
"github": "GitHub'da OmniVoice",
|
||||
"model_card": "Model kartı",
|
||||
"commercial_license": "Ticari Lisans"
|
||||
"commercial_license": "Ticari Lisans",
|
||||
"update_channel": "Güncelleme kanalı",
|
||||
"channel_stable": "Kararlı",
|
||||
"channel_preview": "Önizleme",
|
||||
"channel_set": "Güncelleme kanalı {{channel}} olarak ayarlandı",
|
||||
"channel_preview_hint": "Önizleme en son main yapısını izler — daha yeni özellikler, daha az test. Daha yeni bir kararlı sürüm varsa kararlıya geri döner."
|
||||
},
|
||||
"privacy": {
|
||||
"desc": "Her şey <1>bu makine</1> üzerinde çalışıyor. Açıkça bir çevrimiçi çevirmen (Google, DeepL vb.) kullanmadığınız veya HuggingFace'e aktarmadığınız sürece ses, video ve transkriptleriniz asla bilgisayarınızdan ayrılmaz.",
|
||||
@@ -340,7 +393,30 @@
|
||||
"unavailable": "müsait değil",
|
||||
"use": "Kullanım",
|
||||
"loading": "Motorlar yükleniyor…",
|
||||
"refresh": "Yenile"
|
||||
"refresh": "Yenile",
|
||||
"matrixTitle": "Motor Uyumluluk Matrisi",
|
||||
"loadFailed": "Motorlar yüklenemedi: {{message}}",
|
||||
"couldNotLoad": "Motorlar yüklenemedi: {{message}}",
|
||||
"retry": "Yeniden dene",
|
||||
"activeEngine": "Etkin {{family}}: {{engine}}",
|
||||
"engineCompatLabel": "{{family}} motor uyumluluğu",
|
||||
"active": "aktif",
|
||||
"whyUnavailable": "Neden kullanılamıyor?",
|
||||
"lastError": "Son hata: {{error}}",
|
||||
"installedAndReady": "Kurulu ve hazır",
|
||||
"notInstalled": "Kurulu değil",
|
||||
"available": "Mevcut",
|
||||
"subprocessTitle": "Kendi alt sürecinde + venv'de çalışır",
|
||||
"inProcessTitle": "OmniVoice Python sürecinde çalışır",
|
||||
"testEngine": "Test motoru",
|
||||
"testing": "Test ediliyor…",
|
||||
"recheck": "Tekrar kontrol et",
|
||||
"rechecking": "Yeniden kontrol ediliyor…",
|
||||
"latencyMs": "{{ms}} ms",
|
||||
"failed": "başarısız oldu",
|
||||
"acceptLicense": "Lisansı kabul et",
|
||||
"noBackends": "Kayıtlı arka uç yok.",
|
||||
"switch_failed": "Motor değiştirilemedi"
|
||||
},
|
||||
"capture": {
|
||||
"desc": "Genel kısayol tuşları yalnızca masaüstü uygulamasında çalışır. Web kullanıcı arayüzü, pencere odaktayken sayfa içi <1>Ctrl+Shift+Space</1> kısayolunu kullanır.",
|
||||
@@ -352,13 +428,55 @@
|
||||
"record_shortcut": "Kısayolu kaydet",
|
||||
"recording": "Kaydediliyor…",
|
||||
"save": "Kaydet",
|
||||
"reset_default": "Varsayılana sıfırla"
|
||||
"reset_default": "Varsayılana sıfırla",
|
||||
"listening_label": "Dinleniyor...",
|
||||
"transcribing_label": "Metne dönüştürülüyor…",
|
||||
"pasted": "Yapıştırıldı",
|
||||
"no_speech": "Konuşma algılanmadı",
|
||||
"mic_denied": "Mikrofon erişimi reddedildi",
|
||||
"mic_denied_toast": "Mikrofon erişimi reddedildi. {{hint}}",
|
||||
"mic_hint_mac": "macOS: Sistem Ayarları → Gizlilik ve Güvenlik → Mikrofon'u açın ve OmniVoice'u etkinleştirin.",
|
||||
"mic_hint_windows": "Windows: Ayarlar → Gizlilik ve güvenlik → Mikrofon'u açın ve OmniVoice'a izin verin.",
|
||||
"mic_hint_linux": "Linux: Kullanıcınızın ses grubunda olduğundan ve Web Görünümünün mikrofon erişimine sahip olduğundan emin olun.",
|
||||
"transcription_failed": "Transkripsiyon başarısız oldu: {{message}}"
|
||||
},
|
||||
"logs": {
|
||||
"no_tauri_log": "Henüz diskte Tauri oturumu açılmadı; bir tane oluşturmak için masaüstü sürümü aracılığıyla başlatın",
|
||||
"empty_frontend": "Henüz yakalanmış ön uç konsol girişi yok. Uygulamayla etkileşim kurun — her konsolda.* burada görünecektir.",
|
||||
"empty_tauri": "Tauri günlüğü mevcut değil. Yalnızca masaüstü kabuğunda çalışır.",
|
||||
"empty_backend": "Çalışma zamanı günlüğü boş. Arka uç günlüğe kaydettikçe etkinlik burada görünecektir."
|
||||
"empty_backend": "Çalışma zamanı günlüğü boş. Arka uç günlüğe kaydettikçe etkinlik burada görünecektir.",
|
||||
"title": "Günlükler",
|
||||
"source_backend": "Arka uç",
|
||||
"source_frontend": "Ön uç",
|
||||
"source_tauri": "Tauri",
|
||||
"expand": "Günlükleri genişlet",
|
||||
"collapse": "Günlükleri daralt",
|
||||
"expand_aria": "Günlükler panelini genişlet",
|
||||
"collapse_aria": "Günlük panelini daralt",
|
||||
"drag_resize": "Yeniden boyutlandırmak için sürükleyin",
|
||||
"refresh": "Yenile",
|
||||
"refresh_aria": "Günlükleri yenile",
|
||||
"copy_visible": "Görünür günlüğü kopyala",
|
||||
"copy_visible_aria": "Görünür günlüğü kopyala",
|
||||
"clear": "Temizle",
|
||||
"clear_aria": "Günlüğü temizle",
|
||||
"report_issue": "Sorunu bildir (kopya tanılama)",
|
||||
"report_issue_aria": "Sorunu bildir",
|
||||
"close": "Kapat",
|
||||
"close_aria": "Günlükler panelini kapat",
|
||||
"join_discord": "Discord'umuza katılın",
|
||||
"join_discord_aria": "Discord topluluğumuza katılın",
|
||||
"support_project": "Bu projeyi destekleyin",
|
||||
"support_project_aria": "Bu projeyi destekleyin",
|
||||
"empty_frontend_short": "Henüz ön uç konsol çıkışı yok.",
|
||||
"empty_lines": "Çizgi yok.",
|
||||
"all_clear": "✅ Her şey temiz — hiçbir sorun algılanmadı",
|
||||
"log_cleared": "{{source}} günlük temizlendi",
|
||||
"clear_failed": "Temizleme başarısız oldu: {{message}}",
|
||||
"log_copied": "{{source}} günlüğü kopyalandı",
|
||||
"copy_failed": "Kopyalama başarısız oldu: {{message}}",
|
||||
"report_copied": "Teşhis raporu kopyalandı; onu bir GitHub sorununa yapıştırın.",
|
||||
"report_failed": "Rapor başarısız oldu: {{message}}"
|
||||
},
|
||||
"voice_profile": {
|
||||
"test_text": "Merhaba — bu, bu sesin bir testidir.",
|
||||
@@ -526,7 +644,17 @@
|
||||
"install_already": "{{engine}} zaten kurulmuştu",
|
||||
"install_ok": "{{engine}} kuruldu",
|
||||
"install_failed": "Yükleme başarısız oldu: {{message}}",
|
||||
"prep_elapsed": "{{time}} geçti"
|
||||
"prep_elapsed": "{{time}} geçti",
|
||||
"add_language": "Dil ekle",
|
||||
"search_languages": "Dilleri ara…",
|
||||
"languages_selected_one": "{{count}} dil seçildi",
|
||||
"languages_selected_other": "{{count}} dil seçildi",
|
||||
"more_to_narrow": "+{{count}} daha fazla — daraltmak için yazın",
|
||||
"no_matches": "Eşleşme yok",
|
||||
"diagnostic_copied": "Teşhis kopyalandı",
|
||||
"copy_failed": "Kopyalama başarısız oldu",
|
||||
"open_docs": "Dokümanları aç",
|
||||
"copy_diagnostic": "Teşhisi kopyala"
|
||||
},
|
||||
"glossary": {
|
||||
"title": "Sözlük",
|
||||
@@ -601,7 +729,17 @@
|
||||
"more_actions_title": "Daha fazla işlem",
|
||||
"speaker_pick": "Seç…",
|
||||
"speaker_title_detected": "Hoparlör — algılananlardan birini seçin veya özel bir ad yazın",
|
||||
"speaker_title_custom": "Konuşmacı — bir ad yazın (günlük oluşturma klonu algılanmadı)"
|
||||
"speaker_title_custom": "Konuşmacı — bir ad yazın (günlük oluşturma klonu algılanmadı)",
|
||||
"time_edit_title": "Başlangıç saatini (m:ss.s) düzenlemek için tıklayın. Taahhüt etmek için Enter, iptal etmek için Esc.",
|
||||
"fit_fits": "uyar",
|
||||
"fit_fits_title": "Doğal oranlı ses yuvanın içine sığar.",
|
||||
"fit_overflows": "Taşmalar +{{seconds}}s",
|
||||
"fit_overflows_title": "Çevrilen metin orijinal alandan {{seconds}}s daha uzundu. Ses sert bir şekilde kesilmişti; metni kısaltın veya Zamanlamayı \"Videoyu Uzat\" olarak değiştirin.",
|
||||
"fit_stretched": "Video {{ratio}}×",
|
||||
"fit_stretched_title": "Uzatılmış Video modu: Bu bölümün videosu, doğal dub sese uyacak şekilde {{ratio}}× değerine yavaşlatıldı.",
|
||||
"fit_compressed_title": "TTS sesi yuvanın %{{pct}}'si kadardır ve yoğun şekilde sıkıştırılmıştır.",
|
||||
"fit_audio_title": "Ses yuvanın içine sığar.",
|
||||
"fit_ratio_title": "TTS sesi yuvanın %{{pct}} kadarıdır."
|
||||
},
|
||||
"voice": {
|
||||
"personality": "Kişilik",
|
||||
@@ -696,10 +834,24 @@
|
||||
"status_running": "koşu",
|
||||
"status_done": "bitti",
|
||||
"status_failed": "başarısız oldu",
|
||||
"status_cancelled": "iptal edildi"
|
||||
"status_cancelled": "iptal edildi",
|
||||
"add_to_queue_title": "Videoları Kuyruğa Ekle",
|
||||
"drop_hint_text": "Video dosyalarını buraya bırakın veya göz atmak için tıklayın",
|
||||
"drop_formats": "MP4 · MOV · MKV · WEBM",
|
||||
"files_kicker": "DOSYALAR ({{count}})",
|
||||
"file_size_mb": "{{size}} MB",
|
||||
"target_languages": "HEDEF DİLLER",
|
||||
"voice_kicker": "SES",
|
||||
"default_option": "Varsayılan",
|
||||
"clone_profiles": "Profilleri Klonla",
|
||||
"presets": "Ön ayarlar",
|
||||
"preserve_bg": "Arka plan sesini koru (müzik/FX)",
|
||||
"estimate": "{{videos}} video(lar) × {{langs}} dil(ler) = {{jobs}} iş(ler)",
|
||||
"select_files_langs": "Dosyaları ve dilleri seçin",
|
||||
"add_to_queue": "Kuyruğa Ekle"
|
||||
},
|
||||
"gallery": {
|
||||
"title": "Galeri",
|
||||
"title": "OmniVoice Galeri",
|
||||
"search_placeholder": "YouTube'da ara…",
|
||||
"all_voices": "Tüm Sesler ({{count}})",
|
||||
"no_voices": "Henüz ses yok",
|
||||
@@ -714,6 +866,14 @@
|
||||
"youtube_results": "YouTube Sonuçları ({{count}})",
|
||||
"clone_profile": "Profili klonla",
|
||||
"crop_audio": "Sesi kırp",
|
||||
"cat_disney": "Disney",
|
||||
"cat_anime": "anime",
|
||||
"cat_marvel": "Marvel/DC",
|
||||
"cat_celebs": "ünlüler",
|
||||
"cat_politicians": "Politikacılar",
|
||||
"cat_news": "Haber Sunucuları",
|
||||
"cat_gaming": "Oyun",
|
||||
"cat_books": "Kitaplar/Filmler",
|
||||
"subtitle": "Yüzlerce hazır tasarlanmış ses; birini seçin ve gidin.",
|
||||
"zone_archetypes": "Arketipler",
|
||||
"zone_imports": "İthalatlarım",
|
||||
@@ -818,7 +978,6 @@
|
||||
"badge": "Ticari Lisans",
|
||||
"hero_title": "Üretimde yapay zeka seslerini gönderin",
|
||||
"hero_desc": "OmniVoice Studio, İşlevsel Kaynak Lisansı (FSL) kapsamında kaynak olarak mevcuttur. Çoğu kullanıcı ticari bir anlaşma olmadan dahili olarak değerlendirebilir, prototip oluşturabilir ve hatta dağıtabilir. Ticari lisansa yalnızca rakip bir ürün veya hizmet geliştiriyorsanız ya da kullanım alanınız FSL sınırlarının dışında kalıyorsa ihtiyacınız vardır.",
|
||||
"hero_note": "Rakip bir ürün veya hizmet oluşturmak ya da geniş ölçekte dağıtmak (ör. kullanım başına ödeme API'si sunmak) ticari bir lisans gerektirir. Fiyatlandırma katmanları yakında gelecek; bu arada bizimle iletişime geçin.",
|
||||
"why_title": "İşletmeler neden OmniVoice'u seçiyor?",
|
||||
"pricing_title": "Fiyatlandırma",
|
||||
"faq_title": "Sık sorulan sorular",
|
||||
@@ -838,7 +997,8 @@
|
||||
"benefit_source": "Kaynakta mevcut çekirdek",
|
||||
"benefit_source_desc": "Yığına tam görünürlük. Lisans koşulları dahilinde denetleyin, çatallayın ve uyarlayın.",
|
||||
"benefit_lang": "646 dil",
|
||||
"benefit_lang_desc": "646 dilde insan düzeyinde kaliteyle metne dönüştürün, tercüme edin ve dublaj yapın."
|
||||
"benefit_lang_desc": "646 dilde insan düzeyinde kaliteyle metne dönüştürün, tercüme edin ve dublaj yapın.",
|
||||
"hero_note": "FSL kapsamında dahili kullanım - geniş ölçekte bile - ücretsizdir; ticari lisans yalnızca OmniVoice'u başkalarına rakip bir ürün veya hizmet (barındırılan veya kullanım başına ödemeli API, yeniden satılan veya beyaz etiketli bir uygulama) olarak sunmak için gereklidir. Fiyatlandırma katmanları yakında gelecek; bu arada bizimle iletişime geçin."
|
||||
},
|
||||
"exportModal": {
|
||||
"export": "İhracat",
|
||||
@@ -927,7 +1087,479 @@
|
||||
"dubbing_title": "Dublajı çalışırken görün",
|
||||
"dubbing_sync": "Senkronize oynatma",
|
||||
"dubbing_picker": "Başka bir dil deneyin:",
|
||||
"dubbing_cta": "Bunu kendi videonuzda çalıştırın →"
|
||||
"dubbing_cta": "Bunu kendi videonuzda çalıştırın →",
|
||||
"dubbing_loading": "Dublaj demosu yükleniyor…",
|
||||
"dubbing_dismiss": "Dublaj demosunu kapat",
|
||||
"original_tag": "orijinal",
|
||||
"dubbed_tag": "dublajlı",
|
||||
"script_conversational": "konuşkan",
|
||||
"script_technical": "Teknik kelime bilgisi",
|
||||
"script_french": "İngilizce olmayan (Fransızca)",
|
||||
"aria_pause": "Duraklat {{label}}",
|
||||
"aria_hear": "{{label}} sesini duyun",
|
||||
"aria_replay": "{{label}} metnini aktarıcı aracılığıyla tekrar oynat"
|
||||
},
|
||||
"direction": {
|
||||
"title": "#{{id}} segmentinin yönü",
|
||||
"desc": "Boru hattına bu hattın nasıl hissetmesi gerektiğini söyleyin. Sade İngilizce çalışır; sistem kelimelerinizi sabit bir sınıflandırmaya (enerji / duygu / hız / yakınlık / formalite) göre eşler, ardından sınıflandırmayı Sinematik çeviri, TTS ve slot uyumu aracılığıyla düzenler.",
|
||||
"label": "Yön",
|
||||
"lineHint": "Satır: \"{{text}}\"",
|
||||
"placeholder": "örneğin acil ve şaşırmış / sıcak, umutlu / fısıldayan, samimi",
|
||||
"previewParse": "Ayrıştırmanın önizlemesi",
|
||||
"previewFailed": "Önizleme başarısız oldu: {{message}}",
|
||||
"clear": "Temizle",
|
||||
"cancel": "İptal",
|
||||
"saveDirection": "Yönü kaydet",
|
||||
"ttsInstruct": "TTS'nin talimatı:",
|
||||
"nothingParsed": "— (hiçbir şey ayrıştırılmadı)",
|
||||
"translateHint": "Çeviri ipucu:",
|
||||
"rateBias": "Oran önyargısı:",
|
||||
"speedsUp": "hızlanır",
|
||||
"slowsDown": "yavaşlar",
|
||||
"taxonomyTokens": "sınıflandırma belirteçleri"
|
||||
},
|
||||
"errors": {
|
||||
"title": "Bu sekme bir engele çarptı.",
|
||||
"desc": "Endişelenmeyin; uygulamanın geri kalanı hala çalışıyor. Sekmeleri değiştirebilir veya aşağıda tekrar deneyebilirsiniz.",
|
||||
"tryAgain": "Tekrar dene",
|
||||
"openDocs": "Bu hata için dokümanları açın"
|
||||
},
|
||||
"keyboard": {
|
||||
"title": "Klavye kısayolları",
|
||||
"footer": "Bunu açmak için istediğiniz zaman <1>?</1> tuşuna basın.",
|
||||
"or": "veya",
|
||||
"nav": "Navigasyon",
|
||||
"nav_cheatsheet": "Bu kopya sayfasını göster",
|
||||
"nav_closeModal": "Modu kapat / iptal et",
|
||||
"nav_save": "Projeyi kaydet/kesmeyi tamamla",
|
||||
"segmentEditor": "Segment düzenleyici",
|
||||
"seg_split": "İmleçteki segmenti böl",
|
||||
"seg_merge": "Sonraki segmentle birleştir",
|
||||
"seg_undo": "Geri al",
|
||||
"seg_redo": "Yinele",
|
||||
"seg_click": "Birincil eylem",
|
||||
"seg_shiftClick": "Aralık seçimi",
|
||||
"trimmer": "Ses düzeltici",
|
||||
"trim_playPause": "Oynatmayı / duraklatmayı önizleyin",
|
||||
"trim_nudgeStart": "Başlatma tutamacını sürükle",
|
||||
"trim_nudgeEnd": "Sonlandırma kolunu itin",
|
||||
"trim_fineNudge": "İnce dürtme",
|
||||
"trim_coarseNudge": "Kaba dürtme",
|
||||
"trim_zoomIn": "Yakınlaştır / uzaklaştır",
|
||||
"trim_fitAll": "Tümünü sığdır / Seçimi sığdır",
|
||||
"trim_confirm": "Kırpmayı onayla",
|
||||
"dub": "Dublaj",
|
||||
"dub_generate": "Dublaj oluştur",
|
||||
"dub_sidebar": "Kenar çubuğunu aç/kapat"
|
||||
},
|
||||
"network": {
|
||||
"sharing_on_title": "Paylaşım açık — ayrıntılar için tıklayın",
|
||||
"share_on_network": "Ağınızda paylaşın",
|
||||
"switching": "Değiştiriliyor…",
|
||||
"network": "Ağ",
|
||||
"local": "Yerel",
|
||||
"share_confirm_title": "Ağınızda paylaşılsın mı?",
|
||||
"share_confirm_hint": "Wi-Fi/Ethernet'inizdeki diğer cihazlar, açıldığında gösterilen erişim PIN'ini kullanarak OmniVoice'e ulaşabilecektir.",
|
||||
"enabling": "Etkinleştiriliyor…",
|
||||
"enable": "Etkinleştir",
|
||||
"shared_title": "Ağınızda paylaşıldı",
|
||||
"no_interface": "Erişilebilir ağ arayüzü yok — Wi-Fi/Ethernet'e bağlanın.",
|
||||
"copy_link": "Bağlantıyı kopyala",
|
||||
"open_in_browser": "Tarayıcıda aç",
|
||||
"qr_alt": "{{ip}} için QR",
|
||||
"pin": "PIN:",
|
||||
"stop_sharing": "Paylaşımı durdur",
|
||||
"copied": "Kopyalandı",
|
||||
"enable_error": "Paylaşım etkinleştirilemedi: {{message}}",
|
||||
"disable_error": "Devre dışı bırakılamadı: {{message}}"
|
||||
},
|
||||
"readiness": {
|
||||
"checking_system": "Sistem kontrol ediliyor…",
|
||||
"all_ready": "Tüm sistemler hazır",
|
||||
"system_readiness": "Sistem Hazırlığı",
|
||||
"asr_model": "ASR Modeli",
|
||||
"loaded_ready": "Yüklendi ve hazır",
|
||||
"loading_first_run": "Yükleniyor… (ilk çalıştırmada bu işlem 1-2 dakika sürebilir)",
|
||||
"failed_to_load": "Yüklenemedi",
|
||||
"not_loaded_yet": "Henüz yüklenmedi — ilk transkripsiyonda yüklenecek",
|
||||
"error_check_logs": "Hata: {{error}}. Günlükleri kontrol edin ve yeniden başlatmayı deneyin.",
|
||||
"check_logs_restart": "Model yükleme hataları için günlükleri kontrol edin. Yeniden başlatmayı deneyin.",
|
||||
"llm_cinematic": "Yüksek Lisans (Sinematik)",
|
||||
"llm_configure": "Sinematik çeviri kalitesi için TRANSLATE_BASE_URL'yi yapılandırın",
|
||||
"llm_set_env": "TRANSLATE_BASE_URL ve TRANSLATE_API_KEY ortam değişkenlerini ayarlayın. Ollama, OpenAI, LM Studio vb. ile çalışır.",
|
||||
"llm_optional": "İsteğe bağlı — Sinematik kalite için TRANSLATE_BASE_URL'yi ayarlayın"
|
||||
},
|
||||
"license": {
|
||||
"title": "Supertonic-3 — Lisans Kabulü",
|
||||
"intro": "Supertonic-3 iki farklı lisans altında gönderilir. Lütfen motoru etkinleştirmeden önce her ikisini de inceleyin.",
|
||||
"sdk_heading": "SDK Kodu · MIT",
|
||||
"sdk_desc": "Python çıkarım SDK'sı (süpertonik) MIT lisanslıdır. Ticari de dahil olmak üzere izin verilen kullanım.",
|
||||
"read_mit": "MIT lisansını okuyun →",
|
||||
"model_heading": "Model Ağırlıkları · OpenRAIL-M",
|
||||
"model_desc": "Supertonic-3 model ağırlıkları OpenRAIL-M lisansı altında piyasaya sürülmüştür. Bu lisans, kullanımı kötü amaçlı olmayan amaçlarla sınırlandırır; kullanıma dayalı kısıtlamaların tamamı için bağlantılı lisansa bakın.",
|
||||
"read_openrail": "OpenRAIL-M lisansını okuyun →",
|
||||
"footer": "Kabul Et'e tıklamak, kabulünüzü OmniVoice'in yerel ayarlarına kaydeder ve motoru etkinleştirir. Kabulünüz yalnızca bu makinede saklanır; Supertone Inc.'e veya herhangi bir üçüncü tarafa hiçbir şey bildirilmez.",
|
||||
"saving": "Kaydediliyor…",
|
||||
"accept": "Kabul et",
|
||||
"accepted_toast": "Supertonic-3 lisansı kabul edildi.",
|
||||
"accept_error": "Lisans kabulü kaydedilemedi: {{message}}"
|
||||
},
|
||||
"voicePreview": {
|
||||
"title": "Ses Önizleme",
|
||||
"close": "Önizlemeyi kapat",
|
||||
"default_text": "Merhaba! Bu, bu seste nasıl ses çıkardığımın bir önizlemesidir.",
|
||||
"default_voice": "Varsayılan ses",
|
||||
"clone_profiles": "Profilleri Klonla",
|
||||
"designed_voices": "Tasarlanmış Sesler",
|
||||
"presets": "Ön ayarlar",
|
||||
"placeholder": "Duymak için bir şeyler yazın…",
|
||||
"stop": "Durdur",
|
||||
"regenerate": "Yenile",
|
||||
"preview": "Önizleme",
|
||||
"hint": "8 adım · hızlı önizleme"
|
||||
},
|
||||
"header": {
|
||||
"kicker_studio": "stüdyo",
|
||||
"kicker_library": "Kütüphane",
|
||||
"kicker_preferences": "Tercihler",
|
||||
"kicker_licensing": "Lisanslama",
|
||||
"label_launchpad": "Başlatma Çubuğu",
|
||||
"label_clone": "Ses Klonu",
|
||||
"label_design": "Ses Tasarımı",
|
||||
"label_dub": "Dublaj",
|
||||
"label_projects": "OmniDrive",
|
||||
"label_gallery": "Galeri",
|
||||
"label_transcriptions": "Transkripsiyonlar",
|
||||
"label_settings": "Ayarlar",
|
||||
"label_enterprise": "Ticari Lisans",
|
||||
"status_ready": "Hazır",
|
||||
"status_loading": "Yükleniyor…",
|
||||
"status_idle": "Boşta",
|
||||
"memory_management": "Bellek yönetimi",
|
||||
"flush": "Yıkama",
|
||||
"loaded_models": "Yüklü Modeller",
|
||||
"no_models": "Hiçbir model yüklenmedi",
|
||||
"unload": "Boşalt",
|
||||
"flush_caches": "Önbellekleri temizle",
|
||||
"unload_all_flush": "Tümünü boşalt + temizle"
|
||||
},
|
||||
"sidebar": {
|
||||
"tab_drive": "Sürüş",
|
||||
"tab_history": "Tarih",
|
||||
"tab_exports": "İhracat",
|
||||
"save_project": "Dub Projesini Kaydet",
|
||||
"save_new_project": "Yeni Dub Projesi Olarak Kaydet",
|
||||
"dub_projects": "Dublaj Projeleri",
|
||||
"voice_clones": "Ses Klonları",
|
||||
"designed_voices": "Tasarlanmış Sesler",
|
||||
"no_dub_projects": "Kaydedilmiş dub projesi yok",
|
||||
"no_dub_hint": "Bir video yükleyin ve çalışmanızı korumak için Kaydet'e tıklayın.",
|
||||
"no_clones": "Henüz ses klonu yok",
|
||||
"no_clones_hint": "Sesi kaydedin veya yükleyin, ardından Ses Profili Olarak Kaydet'i tıklayın.",
|
||||
"no_designs": "Henüz tasarlanmış ses yok",
|
||||
"no_designs_hint": "Bir ses oluşturun ve onu Geçmiş'ten kaydedin.",
|
||||
"history_subtitle": "Nesil geçmişi · SQLite'ta saklanır",
|
||||
"no_history": "Nesil geçmişi yok",
|
||||
"no_history_hint": "Sesi sentezleyin veya bir videoyu çoğaltın; sonuçlar burada görünecektir.",
|
||||
"clear_history": "Geçmişi Temizle",
|
||||
"clear_confirm": "Tüm {{count}} geçmiş öğeleri temizlensin mi? Bu geri alınamaz.",
|
||||
"history_cleared": "Geçmiş temizlendi",
|
||||
"recent_exports": "Son İhracatlar",
|
||||
"no_exports": "İndirilen çıktı yok",
|
||||
"no_exports_hint": "Burada takip edildiğini görmek için bir dosyayı Tauri aracılığıyla dışa aktarın.",
|
||||
"show_in_folder": "Klasörde göster",
|
||||
"open": "Açık",
|
||||
"select": "Seç",
|
||||
"try_voice": "Deneyin",
|
||||
"consistent": "tutarlı",
|
||||
"locked": "Kilitli",
|
||||
"clone_label": "Klon",
|
||||
"design_label": "Tasarım",
|
||||
"dub_label": "Dublaj",
|
||||
"save_label": "Kaydet",
|
||||
"lock_identity": "Ses kimliğini kilitle",
|
||||
"in_folder": "{{folder}}'da"
|
||||
},
|
||||
"trimmer": {
|
||||
"title": "Referans sesini kırp",
|
||||
"decoding": "Sesin kodu çözülüyor…",
|
||||
"meta_length": "Uzunluk {{duration}} · {{sampleRate}} Hz",
|
||||
"meta_rendering": "dalga biçimi oluşturma {{percent}}%",
|
||||
"keyboard_hint": "kaydırma = yakınlaştırma · üst karakter+kaydırma = kaydırma · alt+sürükleme = kaydırma · ⏐ ⟵ ⟶ ⏐ tuşları tutamaçları ayarlar",
|
||||
"zoom_in": "Yakınlaştır (+)",
|
||||
"zoom_out": "Uzaklaştır (-)",
|
||||
"fit_all": "Tümünü sığdır (Ev)",
|
||||
"fit_selection": "Seçimi sığdır (Son)",
|
||||
"fit_sel_btn": "UYGUNLUK SEÇ",
|
||||
"view_range": "{{start}} → {{end}} ({{duration}}) görüntüle",
|
||||
"start_label": "Başlat",
|
||||
"end_label": "Bitiş",
|
||||
"length_label": "Uzunluk",
|
||||
"too_long": ">{{max}}s",
|
||||
"too_short": "çok kısa",
|
||||
"length_ok": "tamam",
|
||||
"loop_preview": "Döngü önizlemesi",
|
||||
"pause": "Duraklat",
|
||||
"preview_selection": "Seçimi önizleyin",
|
||||
"play_hint": "Oynamak için alan · Onaylamak için Enter · İptal etmek için Esc",
|
||||
"cancel": "İptal",
|
||||
"use_trimmed": "Kırpılmış kullanın",
|
||||
"decode_failed": "Kod çözme başarısız oldu: {{message}}",
|
||||
"playback_failed": "Oynatma başarısız oldu: {{message}}",
|
||||
"audio_load_failed": "Ses yüklemesi başarısız oldu",
|
||||
"unit_seconds": "s"
|
||||
},
|
||||
"casting": {
|
||||
"title": "Hoparlör Dökümü",
|
||||
"auto_assign_title": "Çıkarılan hoparlör klonlarından sesleri otomatik olarak atayın",
|
||||
"auto_cast": "Otomatik yayınlama",
|
||||
"all_cast": "Tüm oyuncular",
|
||||
"segments_count": "{{count}} segment",
|
||||
"assign_voice": "Sesi ata…",
|
||||
"from_video": "🎤 Videodan ({{name}})",
|
||||
"no_profiles": "Henüz kayıtlı ses profili yok.",
|
||||
"preview_voice": "Sesi önizle"
|
||||
},
|
||||
"checkpoint": {
|
||||
"asr_title": "Transkriptler hazır",
|
||||
"asr_cta": "Çevir",
|
||||
"asr_hint": "Tüm ASR hatalarını şimdi düzeltin; sıkı diksiyon, daha sonra TTS denemelerini önler.",
|
||||
"translate_title": "Çeviriler hazır",
|
||||
"translate_cta": "Dublaj oluştur",
|
||||
"translate_hint": "Hedef metni gözden geçirin. Aşırı uzunluktaki hatların hızı artırılıyor; doğrudan da düzenleyebilirsiniz.",
|
||||
"done_title": "Dublaj tamamlandı",
|
||||
"done_hint": "Zamanlamayı ve senkronizasyon oranlarını inceleyin. Herhangi bir satırda ince ayar yapın ve hızlı bir kısmi yineleme için \"Yenileme değişti\" tuşuna basın.",
|
||||
"segment_one": "{{count}} segmenti",
|
||||
"segment_other": "{{count}} segment",
|
||||
"dismiss_title": "Reddet — yeniden yüklenene kadar bu aşamada yeniden görünmeyecek"
|
||||
},
|
||||
"compare": {
|
||||
"title": "A/B Ses Karşılaştırması",
|
||||
"close": "Yakın karşılaştırma",
|
||||
"desc": "Seçim kararlarını vermek için iki sesi yan yana karşılaştırın. Uygulama geride etkileşimli kalır.",
|
||||
"test_phrase": "Test cümlesi",
|
||||
"voice_a": "Ses A",
|
||||
"voice_b": "Ses B",
|
||||
"select_voice": "— Sesi seç —",
|
||||
"preset_suffix": "(Ön ayar)",
|
||||
"no_audio": "Henüz ses yok",
|
||||
"close_btn": "Kapat",
|
||||
"comparing": "Karşılaştırılıyor…",
|
||||
"compare_btn": "Karşılaştır",
|
||||
"preparing_voice": "Ses hazırlanıyor...",
|
||||
"generating_voice_a": "Ses Oluşturuluyor...",
|
||||
"generating_voice_b": "Ses B oluşturuluyor...",
|
||||
"comparison_complete": "Karşılaştırma tamamlandı!",
|
||||
"play_failed": "Oynatma başarısız oldu: {{message}}"
|
||||
},
|
||||
"models": {
|
||||
"hf_token_set_toast": "HuggingFace token seti — daha hızlı indirmeler etkinleştirildi",
|
||||
"hf_token_save_failed": "Jeton kaydedilemedi",
|
||||
"install_started": "Yükleme başladı — satırda ilerleme",
|
||||
"delete_confirm": "{{repoId}} silinsin mi? Daha sonra yeniden yükleyebilirsiniz.",
|
||||
"delete_confirm_title": "Modeli sil",
|
||||
"deleted": "Silindi {{repoId}}",
|
||||
"reinstall_confirm": "{{repoId}} yeniden yüklensin mi? Bu, mevcut kopyayı silecek ve tekrar indirecektir.",
|
||||
"reinstall_confirm_title": "Modeli yeniden yükle",
|
||||
"reinstalling": "Yeniden yükleme",
|
||||
"recommended_installed": "Önerilen modeller zaten yüklü.",
|
||||
"started_downloading_one": "{{count}} modeli indirilmeye başlandı",
|
||||
"started_downloading_other": "{{count}} modelleri indirilmeye başlandı",
|
||||
"install_failed": "Yükleme başarısız oldu: {{message}}",
|
||||
"removing_cached": "Önbelleğe alınmış düzeltmeler kaldırılıyor…",
|
||||
"resolving_metadata": "Repo meta verilerini çözümleme",
|
||||
"retry_attempt": "Yeniden deneme girişimi {{attempt}} — {{error}}",
|
||||
"connecting_hf": "HuggingFace'e bağlanılıyor…",
|
||||
"resolving_files_one": "{{count}} dosyası çözümleniyor…",
|
||||
"resolving_files_other": "{{count}} dosyaları çözümleniyor…",
|
||||
"resolving_files_active": "{{count}} dosya(lar) çözümleniyor… · {{file}}",
|
||||
"measuring": "ölçüyor…",
|
||||
"files_progress": "{{done}}/{{total}} dosyaları",
|
||||
"install_error": "Yükleme başarısız oldu: {{error}}",
|
||||
"view_on_hf": "HuggingFace'te görüntüle",
|
||||
"install_btn": "Yükle",
|
||||
"reinstall_btn": "Yeniden yükle",
|
||||
"downloading": "indiriliyor",
|
||||
"deleting": "siliniyor",
|
||||
"working": "çalışıyor",
|
||||
"installed": "yüklü",
|
||||
"not_installed": "yüklü değil",
|
||||
"required_tag": "gerekli",
|
||||
"delete_btn": "Sil",
|
||||
"hf_token_btn": "HF Jetonu",
|
||||
"hf_set_title": "Daha hızlı indirmeler için HuggingFace jetonunu ayarlayın",
|
||||
"get_token": "Jeton al →",
|
||||
"reco_installed_for": "{{device}} için önerilen paket yüklendi",
|
||||
"reco_for": "{{device}} için önerilir",
|
||||
"starting": "Başlıyor…",
|
||||
"required_size": "Gerekli ~{{size}} GB",
|
||||
"all_size": "Hepsi ~{{size}} GB",
|
||||
"req_tag": "talep",
|
||||
"search_placeholder": "Modelleri ara…",
|
||||
"search_label": "Modelleri ara",
|
||||
"no_matches": "Filtrelerinizle eşleşen model yok.",
|
||||
"sort_by": "{{column}}'ye göre sırala",
|
||||
"column_model": "Modeli",
|
||||
"column_role": "Rol",
|
||||
"column_size": "Boyut",
|
||||
"column_status": "Durum",
|
||||
"ready_badge": "Hazır",
|
||||
"loading_badge": "Yükleniyor…",
|
||||
"idle_badge": "Boşta",
|
||||
"started_downloading_required_one": "{{count}} gerekli modeli indirmeye başlandı",
|
||||
"started_downloading_required_other": "{{count}} gerekli model indirilmeye başlandı"
|
||||
},
|
||||
"enterprise_faq": {
|
||||
"q_internal_tools": "Dahili araçlar için lisansa ihtiyacım var mı?",
|
||||
"a_internal_tools": "Çalışanlarınızın ve yüklenicilerinizin şirket içi kullanımı, FSL kapsamında İzin Verilen Amaçlardan biridir; lisans gerekmez. OmniVoice'u rakip bir ürün veya hizmetin (yeniden satış, barındırılan SaaS, beyaz etiket) parçası olarak başkalarının kullanımına sunduğunuzda ticari bir lisans gerekir.",
|
||||
"q_try_before": "Taahhüt etmeden önce deneyebilir miyim?",
|
||||
"a_try_before": "Evet. Uygulamanın tamamını FSL kapsamında değerlendirme için yerel olarak indirmek ve çalıştırmak ücretsizdir. Ticari bir dağıtımı tartışmaya hazır olduğunuzda bize e-posta gönderin; ayrıntılar üzerinde birlikte çalışalım.",
|
||||
"q_watermark": "Filigran ne olacak?",
|
||||
"a_watermark": "Görünmez AudioSeal filigranı varsayılan olarak gömülüdür. Ticari lisans sahipleri bunu Ayarlar → Gizlilik bölümünden devre dışı bırakabilir. Ücretsiz/kişisel kullanım her zaman filigranı da içerir.",
|
||||
"q_apache": "Kaynak hiç Apache 2.0 olur mu?",
|
||||
"a_apache": "Evet. Her sürüm, yayınlanmasının ikinci yıldönümünde otomatik olarak Apache Lisansı Sürüm 2.0'a dönüştürülür. Bu, bugünkü sürümün iki yıl içinde Apache 2.0 olacağı anlamına gelir; bizden herhangi bir işlem yapılmasına gerek yoktur - FSL bunu geri dönülmez bir şekilde garanti eder."
|
||||
},
|
||||
"gallery_extra": {
|
||||
"save_prompt": "Bu ses profili için bir ad girin:",
|
||||
"saved_as_profile": "Ses profil olarak kaydedildi!",
|
||||
"save_profile_failed": "Profil kaydedilemedi",
|
||||
"download_failed": "İndirme başarısız oldu: {{message}}",
|
||||
"trim_load_failed": "Kırpma için ses yüklenemedi: {{message}}",
|
||||
"upload_crop_failed": "Kırpılmış ses yüklenemedi: {{message}}"
|
||||
},
|
||||
"dub_workflow": {
|
||||
"preparing_audio": "Ses hazırlanıyor…",
|
||||
"preparing_video": "Video hazırlanıyor…",
|
||||
"extracting_audio_scenes": "Ses ve sahneler çıkarılıyor…",
|
||||
"transcribing_audio": "Ses yazıya aktarılıyor…",
|
||||
"transcription_complete": "Transkripsiyon tamamlandı",
|
||||
"upload_cancelled": "Yükleme iptal edildi",
|
||||
"upload_failed": "Yükleme başarısız oldu: {{message}}",
|
||||
"downloading_video": "Video indiriliyor…",
|
||||
"ingested": "Yutuldu {{url}}",
|
||||
"ingest_cancelled": "Besleme iptal edildi",
|
||||
"ingest_failed": "URL alımı başarısız oldu: {{message}}",
|
||||
"retry_cancelled": "Yeniden deneme iptal edildi",
|
||||
"transcription_failed": "Transkripsiyon başarısız oldu: {{message}}",
|
||||
"import_srt_no_job": "Önce bir video yükleyin veya alın; altyazı eklenecek bir iş yoktur.",
|
||||
"imported_cues": "{{file}}'den {{count}} işaret(ler) içe aktarıldı",
|
||||
"skipped_malformed": "{{count}} atlandı (hatalı biçimlendirilmiş)",
|
||||
"dropped_overlap": "{{count}} düştü (örtüşme)",
|
||||
"clamped_to_duration": "{{count}} medya uzunluğuna sabitlendi",
|
||||
"srt_import_failed": "SRT içe aktarma işlemi başarısız oldu",
|
||||
"cleaned_one": "{{count}} parçası temizlendi",
|
||||
"cleaned_other": "{{count}} parça temizlendi",
|
||||
"segments_clean": "Segmentler zaten temiz",
|
||||
"cleanup_failed": "Temizleme başarısız oldu: {{message}}",
|
||||
"cinematic_no_llm": "Sinematik kalitenin bir LLM'ye ihtiyacı vardır - TRANSLATE_BASE_URL + TRANSLATE_API_KEY'i ayarlayın (Ollama yerel olarak çalışır). Hızlı'ya geri dönüyorum.",
|
||||
"translate_errors": "{{errorCount}}/{{totalCount}} segment(ler)i başarısız oldu: {{firstError}}",
|
||||
"translated_segments": "{{count}} segment(ler) çevrildi → {{lang}}",
|
||||
"translated_cinematic_suffix": "(Sinematik)",
|
||||
"translation_failed": "Çeviri başarısız oldu: {{message}}",
|
||||
"regenerating": "{{count}} segment/segmentler yeniden oluşturuluyor…",
|
||||
"generating_dub": "Dublaj oluşturuluyor…",
|
||||
"generating_progress": "Dublaj oluşturuluyor… {{current}}/{{total}}",
|
||||
"generation_aborted": "Oluşturma iptal edildi.",
|
||||
"dubbing_aborted": "Dublaj iptal edildi",
|
||||
"generation_stream_ended": "Oluşturma akışı tamamlanmadan sona erdi",
|
||||
"dub_complete": "Dublaj tamamlandı",
|
||||
"stop_failed": "Durdurulamadı",
|
||||
"save_first": "Videonun kaydedilmeden önce sunucuda işlenmesi için lütfen önce 'Yükle ve Metne Yaz'ı tıklayın.",
|
||||
"project_saved": "Proje kaydedildi",
|
||||
"project_created": "Proje oluşturuldu",
|
||||
"save_failed": "Kaydetme başarısız oldu: {{message}}",
|
||||
"opened_project": "Açılış: {{name}}",
|
||||
"delete_project_confirm": "Bu proje silinsin mi? Bu geri alınamaz.",
|
||||
"project_deleted": "Proje silindi",
|
||||
"delete_history_confirm": "Bu geçmiş öğesi silinsin mi?",
|
||||
"history_deleted": "Geçmiş öğesi silindi",
|
||||
"restored_state": "Önceki nesil durumu geri yüklendi",
|
||||
"upgrading_preview": "{{count}} önizleme kalitesinde segment(ler) tam kaliteye yükseltiliyor…"
|
||||
},
|
||||
"tts_errors": {
|
||||
"enter_text": "Lütfen metni girin",
|
||||
"upload_or_select": "Bir ses yükleyin veya bir ses profili seçin",
|
||||
"trim_hint": "Ses {{duration}}s — en iyi klonlama için ≤{{max}}s'ye kırpın",
|
||||
"timeout": "Oluşturma zaman aşımına uğradı; model hâlâ indiriliyor olabilir. Ayarlar → Günlükler'i kontrol edin ve tekrar deneyin.",
|
||||
"error_prefix": "Hata: {{message}}",
|
||||
"ignored_unsupported": "Desteklenmeyen talimat göz ardı edildi: {{items}}",
|
||||
"ignored_duplicate": "Yoksayıldı (kategori zaten ayarlandı): {{items}}"
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Paylaşım ve Uzaktan Erişim",
|
||||
"help": "Çalışan bu OmniVoice örneğini, yeniden başlatmaya gerek kalmadan diğer makinelerinize gösterin. Yalnızca geri döngü varsayılandır; siz burada açana kadar hiçbir şey paylaşılmaz.",
|
||||
"local_network": "Yerel ağ",
|
||||
"local_help": "Tek seferlik erişim PIN'i ile Wi-Fi/Ethernet'inizde paylaşın. Diğer cihazlar QR kodunu tarar veya bağlantıyı açar.",
|
||||
"ports_title": "Limanlar",
|
||||
"ports_help": "Bunlar başlangıçta okunan ortam değişkenleri aracılığıyla ayarlanır. Değişkeni ayarlayıp OmniVoice'i yeniden başlatarak arka ucu veya kullanıcı arayüzü bağlantı noktasını değiştirin.",
|
||||
"backend_port": "Arka uç bağlantı noktası",
|
||||
"ui_port": "kullanıcı arayüzü bağlantı noktası",
|
||||
"lan_share_port": "LAN paylaşım bağlantı noktası",
|
||||
"port_error": "1024 ile 65535 arasında bir bağlantı noktası girin",
|
||||
"port_saved": "LAN paylaşım bağlantı noktası kaydedildi — paylaşımı bir sonraki etkinleştirdiğinizde geçerli olur",
|
||||
"port_save_failed": "Bağlantı noktası kaydedilemedi: {{message}}",
|
||||
"saving": "Kaydediliyor…",
|
||||
"ports_note": "Arka uç ve kullanıcı arayüzü bağlantı noktaları yeniden başlatma sırasında uygulanır. LAN paylaşım bağlantı noktası, paylaşımı bir sonraki etkinleştirişinizde geçerli olur.",
|
||||
"tailscale_title": "Kuyruk ölçeği (özel uzaktan erişim)",
|
||||
"tailscale_checking": "Kuyruk Ölçeği kontrol ediliyor…",
|
||||
"tailscale_absent": "Kuyruk ölçeği algılanmadı. Özel kuyruk ağınızın herhangi bir yerinden OmniVoice'e güvenli bir şekilde ulaşmak için bunu yükleyin.",
|
||||
"tailscale_install": "Tailscale'i yükleyin",
|
||||
"tailscale_running": "Kuyruk ölçeği çalışıyor. OmniVoice'u özel kuyruk ağınız üzerinden servis edin.",
|
||||
"tailscale_not_logged_in": "Tailscale kuruldu ancak oturum açılmadı. Önce Tailscale'i başlatın ve oturum açın.",
|
||||
"tailscale_enabled": "Kuyruk ölçeği sunumu etkin",
|
||||
"tailscale_enable_failed": "Tailscale etkinleştirilemedi",
|
||||
"tailscale_enable_error": "Tailscale etkinleştirilemedi: {{message}}",
|
||||
"tailscale_disabled": "Kuyruk ölçeği hizmeti devre dışı bırakıldı",
|
||||
"tailscale_disable_failed": "Kuyruk Ölçeği devre dışı bırakılamadı",
|
||||
"tailscale_disable_error": "Kuyruk Ölçeği devre dışı bırakılamadı: {{message}}",
|
||||
"tailscale_enabling": "Etkinleştiriliyor…",
|
||||
"tailscale_enable_btn": "Tailscale hizmetini etkinleştir",
|
||||
"tailscale_disabling": "Devre dışı bırakılıyor…",
|
||||
"tailscale_disable_btn": "Tailscale hizmetini durdur",
|
||||
"tailscale_copy": "Bağlantıyı kopyala",
|
||||
"tailscale_open": "Tarayıcıda aç",
|
||||
"copied": "Kopyalandı",
|
||||
"tailscale_qr_alt": "Kuyruk Ölçeği URL'si için QR kodu"
|
||||
},
|
||||
"reportBug": {
|
||||
"label": "Hata bildir",
|
||||
"title": "Tarayıcınızda önceden doldurulmuş bir GitHub Sorunları sayfasını açar. Siz Gönder'i tıklatana kadar hiçbir şey gönderilmez."
|
||||
},
|
||||
"app": {
|
||||
"loading": "Yükleniyor…",
|
||||
"trimmed_loaded": "Kırpılmış ses yüklendi",
|
||||
"toast_exported": "Dışa aktarıldı: {{name}}",
|
||||
"toast_export_failed": "Dışa aktarma başarısız oldu: {{message}}",
|
||||
"toast_open_folder_failed": "Klasör açılamadı: {{message}}",
|
||||
"toast_saving": "{{name}} kaydediliyor...",
|
||||
"toast_saved": "Kaydedildi: {{path}}",
|
||||
"toast_save_error": "Kaydetme hatası: {{message}}",
|
||||
"toast_processing": "{{name}} işleniyor...",
|
||||
"toast_downloaded": "İndirildi {{name}}",
|
||||
"toast_download_error": "İndirme hatası: {{message}}",
|
||||
"toast_upload_first": "Videonun kaydedilmeden önce sunucuda işlenmesi için lütfen önce 'Yükle ve Metne Yaz'ı tıklayın.",
|
||||
"toast_save_failed": "Kaydetme başarısız oldu: {{message}}",
|
||||
"toast_opened": "Açılış: {{name}}",
|
||||
"toast_project_deleted": "Proje silindi",
|
||||
"toast_restored_state": "Önceki nesil durumu geri yüklendi",
|
||||
"toast_history_deleted": "Geçmiş öğesi silindi",
|
||||
"toast_flushed": "Temizlenmiş — RAM {{ram}}G · VRAM {{vram}}G{{unloaded}}",
|
||||
"toast_model_unloaded": "· model boşaltıldı",
|
||||
"toast_flush_failed": "Temizleme başarısız oldu: {{message}}",
|
||||
"toast_project_saved": "Proje kaydedildi",
|
||||
"toast_project_created": "Proje oluşturuldu"
|
||||
},
|
||||
"update": {
|
||||
"available": "{{version}} güncellemesi mevcut",
|
||||
"install": "Yükle ve yeniden başlat",
|
||||
"install_hint": "Güncellemeyi indir ve yeni sürümle yeniden başlat",
|
||||
"downloading": "Güncelleniyor… %{{pct}}",
|
||||
"restart": "Güncellemek için yeniden başlat",
|
||||
"busy": "Önce dublajını bitir — sonra güncellemeyi yükle.",
|
||||
"whats_new": "Yenilikler",
|
||||
"failed": "Güncelleme başarısız oldu",
|
||||
"retry": "Yeniden dene",
|
||||
"dismiss": "Reddet"
|
||||
},
|
||||
"archetypes": {
|
||||
"featured": "Öne Çıkanlar",
|
||||
@@ -945,5 +1577,25 @@
|
||||
"facet_accent": "Aksan",
|
||||
"facet_lang": "Dil",
|
||||
"facet_whisper": "Fısıltı"
|
||||
},
|
||||
"support": {
|
||||
"tab_support": "Destek",
|
||||
"tab_license": "Ticari Lisans",
|
||||
"toggle_label": "Destek veya ticari lisans",
|
||||
"other_ways": "Yardım etmenin diğer yolları",
|
||||
"star_github": "GitHub'da yıldız",
|
||||
"join_discord": "Discord'a Katılın"
|
||||
},
|
||||
"updates": {
|
||||
"tab": "Güncellemeler",
|
||||
"up_to_date": "Güncel · v{{version}}",
|
||||
"check_now": "Şimdi kontrol et",
|
||||
"releases": "Sürümler",
|
||||
"current": "geçerli",
|
||||
"prerelease": "önizleme",
|
||||
"loading": "Sürümler yükleniyor…",
|
||||
"none": "Yayın bulunamadı",
|
||||
"load_error": "Sürümler yüklenemedi (çevrimdışı mı?)",
|
||||
"retry_load": "Yeniden dene"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,4 @@
|
||||
{
|
||||
"update": {
|
||||
"available": "Доступне оновлення {{version}}",
|
||||
"install": "Встановити та перезапустити",
|
||||
"install_hint": "Завантажити оновлення та перезапустити в новій версії",
|
||||
"downloading": "Оновлення… {{pct}}%",
|
||||
"restart": "Перезапустити для оновлення",
|
||||
"busy": "Спочатку завершіть дубляж — потім встановіть оновлення."
|
||||
},
|
||||
"nav": {
|
||||
"clone": "Клонувати",
|
||||
"design": "Створити",
|
||||
@@ -16,7 +8,10 @@
|
||||
"launchpad": "Стартова панель",
|
||||
"gallery": "Галерея",
|
||||
"transcripts": "Стенограми",
|
||||
"omnidrive": "OmniDrive"
|
||||
"omnidrive": "OmniDrive",
|
||||
"move_rail_right": "Перемістіть рейку вправо",
|
||||
"move_rail_left": "Перемістіть рейку вліво",
|
||||
"flip_rail": "Зворотна сторона рейки"
|
||||
},
|
||||
"settings": {
|
||||
"general": "Загальні",
|
||||
@@ -45,7 +40,40 @@
|
||||
"ffmpeg_missing": "Не знайдено",
|
||||
"ffmpeg_current": "Поточний шлях",
|
||||
"ffmpeg_desc": "Встановіть настроюваний шлях ffmpeg, якщо автоматичне виявлення не вдається.",
|
||||
"ffmpeg_saved": "FFmpeg path set — перезапустіть серверну частину, щоб застосувати."
|
||||
"ffmpeg_saved": "FFmpeg path set — перезапустіть серверну частину, щоб застосувати.",
|
||||
"diagnostics_copied": "Diagnostics copied — paste into your issue report.",
|
||||
"updater_desktop": "Програма оновлення працює лише в настільній програмі.",
|
||||
"latest_version": "Ви використовуєте останню версію.",
|
||||
"logs_load_failed": "Не вдалося завантажити журнали: {{message}}",
|
||||
"clear_frontend_confirm": "Очистити буфер журналу зовнішнього інтерфейсу в пам’яті?",
|
||||
"clear_frontend_title": "Очистити журнали",
|
||||
"frontend_logs_cleared": "Журнали інтерфейсу очищено",
|
||||
"clear_tauri_confirm": "Обрізати файли журналу на стороні Tauri? ОС продовжить записувати нові записи.",
|
||||
"clear_tauri_title": "Ясні таврійські колоди",
|
||||
"nothing_to_clear": "Nothing to clear — no Tauri log file on disk yet.",
|
||||
"cleared_tauri_one": "Очищено файл журналу {{count}} Tauri",
|
||||
"cleared_tauri_other": "Очищено {{count}} журналів Tauri",
|
||||
"clear_tauri_failed": "Не вдалося очистити журнали Tauri: {{message}}",
|
||||
"clear_backend_confirm": "Очистити серверне середовище виконання + журнали збоїв? Це неможливо скасувати.",
|
||||
"clear_backend_title": "Очистити журнали",
|
||||
"backend_logs_cleared": "Серверні журнали очищено",
|
||||
"clear_backend_failed": "Не вдалося очистити журнали",
|
||||
"copy_failed": "Помилка копіювання: {{message}}",
|
||||
"update_check_failed": "Не вдалося перевірити оновлення: {{message}}",
|
||||
"save_failed": "Не вдалося зберегти: {{message}}",
|
||||
"clear_failed": "Помилка очищення: {{message}}",
|
||||
"engine_switched": "{{family}} → {{engine}}",
|
||||
"channel_set_failed": "Не вдалося встановити канал: {{message}}",
|
||||
"updater_downloading": "Завантаження {{version}}…",
|
||||
"updater_installed": "Встановлено — перезапуск.",
|
||||
"updater_available_title": "Доступне оновлення",
|
||||
"updater_available_body": "Доступна версія {{version}}.\n\n{{notes}}\n\nЗавантажити та встановити зараз?",
|
||||
"updater_notes_fallback": "Перегляньте примітки до випуску на GitHub.",
|
||||
"shortcut_load_failed": "Не вдалося завантажити ярлик: {{message}}",
|
||||
"shortcut_set": "Ярлик для диктування встановлено на {{shortcut}}",
|
||||
"shortcut_register_failed": "Не вдалося зареєструватися: {{message}}",
|
||||
"shortcut_reset": "Скинути до замовчування",
|
||||
"shortcut_reset_failed": "Помилка скидання: {{message}}"
|
||||
},
|
||||
"bootstrap": {
|
||||
"title": "OmniVoice Studio",
|
||||
@@ -69,7 +97,13 @@
|
||||
"waiting_output": "Очікування виводу…",
|
||||
"auto_detect": "Автовизначення",
|
||||
"suggest_lang": "Переключити на Українську мову?",
|
||||
"select_lang": "Мова:"
|
||||
"select_lang": "Мова:",
|
||||
"region_global": "Глобальний (прямий)",
|
||||
"region_china": "Китай (дзеркало)",
|
||||
"region_russia": "Росія (дзеркало)",
|
||||
"region_restricted": "Обмежений (дзеркало)",
|
||||
"unknown_error": "Невідома помилка",
|
||||
"retrying": "Повторна спроба…"
|
||||
},
|
||||
"stories": {
|
||||
"title": "Редактор історій",
|
||||
@@ -158,7 +192,17 @@
|
||||
"reload": "Примусове перезавантаження інтерфейсу користувача",
|
||||
"backend": "Backend",
|
||||
"frontend": "Інтерфейс",
|
||||
"tauri": "Телець"
|
||||
"tauri": "Телець",
|
||||
"cancelOp": "Скасувати операцію",
|
||||
"dismiss": "Відхилити",
|
||||
"dismissStatus": "Відмінити статус",
|
||||
"search": "Пошук…",
|
||||
"no_matches": "Немає збігів",
|
||||
"recent_and_popular": "Останні та популярні",
|
||||
"popular_label": "Популярний",
|
||||
"showing_of": "Показано {{shown}} з {{total}}. Введіть для пошуку…",
|
||||
"yes": "так",
|
||||
"no": "немає"
|
||||
},
|
||||
"launchpad": {
|
||||
"greeting": "привіт",
|
||||
@@ -180,7 +224,16 @@
|
||||
"locked": "ЗАБЛОКУВАНО",
|
||||
"open": "відкритий",
|
||||
"try_it": "Спробуйте",
|
||||
"audio_only": "Лише аудіо"
|
||||
"audio_only": "Лише аудіо",
|
||||
"stories_title": "оповідання",
|
||||
"stories_desc": "Багатоголосові аудіокниги — вибирайте персонажів, вставляйте сценарій, експортуйте за розділами.",
|
||||
"gallery_title": "Голосова галерея",
|
||||
"gallery_desc": "Переглядайте готові розроблені голоси за акцентом, віком і стилем — без налаштування.",
|
||||
"transcripts_title": "Стенограми",
|
||||
"transcripts_desc": "Перетворюйте аудіо чи відео на текст, доступний для редагування та пошуку – на 646 мовах.",
|
||||
"recent_files": "Останні файли",
|
||||
"view_all_files": "Переглянути всі файли",
|
||||
"file": "Файл"
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "Підкажіть",
|
||||
@@ -271,11 +324,6 @@
|
||||
"outputs": "Виходи",
|
||||
"crash_log": "Журнал збоїв",
|
||||
"update_endpoint": "Оновити кінцеву точку",
|
||||
"update_channel": "Канал оновлень",
|
||||
"channel_stable": "Стабільний",
|
||||
"channel_preview": "Попередній перегляд",
|
||||
"channel_set": "Канал оновлень встановлено на {{channel}}",
|
||||
"channel_preview_hint": "Попередній перегляд відстежує найновішу збірку main — новіші функції, менше тестування. Повертається до стабільного, якщо стабільний випуск новіший.",
|
||||
"yes": "так",
|
||||
"no": "немає",
|
||||
"web_preview": "веб-перегляд",
|
||||
@@ -284,7 +332,12 @@
|
||||
"copy_diagnostics": "Діагностика копій",
|
||||
"github": "OmniVoice на GitHub",
|
||||
"model_card": "Модель картки",
|
||||
"commercial_license": "Комерційна ліцензія"
|
||||
"commercial_license": "Комерційна ліцензія",
|
||||
"update_channel": "Канал оновлень",
|
||||
"channel_stable": "Стабільний",
|
||||
"channel_preview": "Попередній перегляд",
|
||||
"channel_set": "Канал оновлень встановлено на {{channel}}",
|
||||
"channel_preview_hint": "Попередній перегляд відстежує найновішу збірку main — новіші функції, менше тестування. Повертається до стабільного, якщо стабільний випуск новіший."
|
||||
},
|
||||
"privacy": {
|
||||
"desc": "Усе працює на <1>цій машині</1>. Ваші аудіо, відео та стенограми ніколи не залишають ваш комп’ютер, якщо ви явно не використовуєте онлайн-перекладач (Google, DeepL тощо) або надсилаєте HuggingFace.",
|
||||
@@ -340,7 +393,30 @@
|
||||
"unavailable": "недоступний",
|
||||
"use": "використання",
|
||||
"loading": "Завантаження двигунів…",
|
||||
"refresh": "Оновити"
|
||||
"refresh": "Оновити",
|
||||
"matrixTitle": "Матриця сумісності двигуна",
|
||||
"loadFailed": "Не вдалося завантажити двигуни: {{message}}",
|
||||
"couldNotLoad": "Не вдалося завантажити двигуни: {{message}}",
|
||||
"retry": "Повторіть спробу",
|
||||
"activeEngine": "Активний {{family}}: {{engine}}",
|
||||
"engineCompatLabel": "{{family}} сумісність з двигуном",
|
||||
"active": "активний",
|
||||
"whyUnavailable": "Чому недоступний?",
|
||||
"lastError": "Остання помилка: {{error}}",
|
||||
"installedAndReady": "Встановлено і готово",
|
||||
"notInstalled": "Не встановлено",
|
||||
"available": "в наявності",
|
||||
"subprocessTitle": "Працює у власному підпроцесі + venv",
|
||||
"inProcessTitle": "Працює в процесі OmniVoice Python",
|
||||
"testEngine": "Тестовий двигун",
|
||||
"testing": "Тестування…",
|
||||
"recheck": "Повторна перевірка",
|
||||
"rechecking": "Повторна перевірка…",
|
||||
"latencyMs": "{{ms}} мс",
|
||||
"failed": "не вдалося",
|
||||
"acceptLicense": "Прийняти ліцензію",
|
||||
"noBackends": "Немає зареєстрованих серверних програм.",
|
||||
"switch_failed": "Не вдалося перемкнути двигун"
|
||||
},
|
||||
"capture": {
|
||||
"desc": "Глобальні гарячі клавіші працюють лише в настільній програмі. Веб-інтерфейс використовує ярлик <1>Ctrl+Shift+Пробіл</1> на сторінці, коли вікно має фокус.",
|
||||
@@ -352,13 +428,55 @@
|
||||
"record_shortcut": "Ярлик запису",
|
||||
"recording": "Запис...",
|
||||
"save": "зберегти",
|
||||
"reset_default": "Скинути до замовчування"
|
||||
"reset_default": "Скинути до замовчування",
|
||||
"listening_label": "слухаю...",
|
||||
"transcribing_label": "Транскрибування…",
|
||||
"pasted": "Вставив",
|
||||
"no_speech": "Мовлення не виявлено",
|
||||
"mic_denied": "Доступ до мікрофона заборонено",
|
||||
"mic_denied_toast": "Доступ до мікрофона заборонено. {{hint}}",
|
||||
"mic_hint_mac": "macOS: open System Settings → Privacy & Security → Microphone and enable OmniVoice.",
|
||||
"mic_hint_windows": "Windows: open Settings → Privacy & security → Microphone and allow OmniVoice.",
|
||||
"mic_hint_linux": "Linux: check that your user is in the audio group and the WebView has mic access.",
|
||||
"transcription_failed": "Помилка транскрипції: {{message}}"
|
||||
},
|
||||
"logs": {
|
||||
"no_tauri_log": "На диску ще немає входу Tauri — запустіть його через збірку настільного комп’ютера, щоб створити його",
|
||||
"empty_frontend": "Записи зовнішньої консолі ще не зафіксовано. Взаємодійте з програмою — тут з’являться всі консолі.*.",
|
||||
"empty_tauri": "Немає журналу Tauri. Працює лише в оболонці робочого столу.",
|
||||
"empty_backend": "Журнал виконання порожній. Діяльність відображатиметься тут, оскільки серверна система реєструватиме її."
|
||||
"empty_backend": "Журнал виконання порожній. Діяльність відображатиметься тут, оскільки серверна система реєструватиме її.",
|
||||
"title": "Журнали",
|
||||
"source_backend": "Backend",
|
||||
"source_frontend": "Інтерфейс",
|
||||
"source_tauri": "Телець",
|
||||
"expand": "Розгорнути журнали",
|
||||
"collapse": "Згорнути журнали",
|
||||
"expand_aria": "Розгорнути панель журналів",
|
||||
"collapse_aria": "Згорнути панель журналів",
|
||||
"drag_resize": "Перетягніть, щоб змінити розмір",
|
||||
"refresh": "Оновити",
|
||||
"refresh_aria": "Оновити журнали",
|
||||
"copy_visible": "Копіювати видимий журнал",
|
||||
"copy_visible_aria": "Копіювати видимий журнал",
|
||||
"clear": "ясно",
|
||||
"clear_aria": "Очистити журнал",
|
||||
"report_issue": "Повідомити про проблему (діагностика копіювання)",
|
||||
"report_issue_aria": "Повідомити про проблему",
|
||||
"close": "Закрити",
|
||||
"close_aria": "Закрити панель журналів",
|
||||
"join_discord": "Приєднуйтесь до нашого Discord",
|
||||
"join_discord_aria": "Приєднуйтесь до нашої спільноти Discord",
|
||||
"support_project": "Підтримайте цей проект",
|
||||
"support_project_aria": "Підтримайте цей проект",
|
||||
"empty_frontend_short": "Ще немає вихідних даних для зовнішньої консолі.",
|
||||
"empty_lines": "Ніяких ліній.",
|
||||
"all_clear": "✅ Все чітко — проблем не виявлено",
|
||||
"log_cleared": "Журнал {{source}} очищено",
|
||||
"clear_failed": "Помилка очищення: {{message}}",
|
||||
"log_copied": "Скопійовано журнал {{source}}",
|
||||
"copy_failed": "Помилка копіювання: {{message}}",
|
||||
"report_copied": "Діагностичний звіт скопійовано — вставте його у випуск GitHub.",
|
||||
"report_failed": "Помилка звіту: {{message}}"
|
||||
},
|
||||
"voice_profile": {
|
||||
"test_text": "Привіт — це перевірка цього голосу.",
|
||||
@@ -526,7 +644,17 @@
|
||||
"install_already": "{{engine}} вже встановлено",
|
||||
"install_ok": "{{engine}} встановлено",
|
||||
"install_failed": "Помилка встановлення: {{message}}",
|
||||
"prep_elapsed": "{{time}} минуло"
|
||||
"prep_elapsed": "{{time}} минуло",
|
||||
"add_language": "Додайте мову",
|
||||
"search_languages": "Пошук мов…",
|
||||
"languages_selected_one": "Вибрано {{count}} мову",
|
||||
"languages_selected_other": "Вибрано {{count}} мов",
|
||||
"more_to_narrow": "+{{count}} більше — введіть, щоб звузити",
|
||||
"no_matches": "Немає збігів",
|
||||
"diagnostic_copied": "Діагностику скопійовано",
|
||||
"copy_failed": "Помилка копіювання",
|
||||
"open_docs": "Відкрити документи",
|
||||
"copy_diagnostic": "Діагностика копії"
|
||||
},
|
||||
"glossary": {
|
||||
"title": "Глосарій",
|
||||
@@ -601,7 +729,17 @@
|
||||
"more_actions_title": "Більше дій",
|
||||
"speaker_pick": "Виберіть…",
|
||||
"speaker_title_detected": "Динамік — виберіть із знайдених або введіть власне ім’я",
|
||||
"speaker_title_custom": "Доповідач — введіть ім’я (клонів діаризації не виявлено)"
|
||||
"speaker_title_custom": "Доповідач — введіть ім’я (клонів діаризації не виявлено)",
|
||||
"time_edit_title": "Клацніть, щоб змінити час початку (хв:сс.с). Enter, щоб прийняти, Esc, щоб скасувати.",
|
||||
"fit_fits": "Підходить",
|
||||
"fit_fits_title": "Аудіо з природною швидкістю поміщається в слот.",
|
||||
"fit_overflows": "Переповнення +{{seconds}}s",
|
||||
"fit_overflows_title": "Перекладений текст був довший за оригінальний слот на {{seconds}}s. Аудіо було жорстко обрізано; скоротіть текст або перемкніть параметри часу на «Розтягнути відео».",
|
||||
"fit_stretched": "Відео {{ratio}}×",
|
||||
"fit_stretched_title": "Режим розтягнутого відео: відео цього сегменту було сповільнено до {{ratio}}×, щоб відповідати природному дублюванню звуку.",
|
||||
"fit_compressed_title": "Аудіо TTS займає {{pct}}% слота — сильно стиснуте.",
|
||||
"fit_audio_title": "Аудіо вміщується в слот.",
|
||||
"fit_ratio_title": "Аудіо TTS займає {{pct}}% слота."
|
||||
},
|
||||
"voice": {
|
||||
"personality": "Особистість",
|
||||
@@ -696,10 +834,24 @@
|
||||
"status_running": "біг",
|
||||
"status_done": "зроблено",
|
||||
"status_failed": "не вдалося",
|
||||
"status_cancelled": "скасовано"
|
||||
"status_cancelled": "скасовано",
|
||||
"add_to_queue_title": "Додати відео в чергу",
|
||||
"drop_hint_text": "Перетягніть відеофайли сюди або натисніть, щоб переглянути",
|
||||
"drop_formats": "MP4 · MOV · MKV · WEBM",
|
||||
"files_kicker": "ФАЙЛИ ({{count}})",
|
||||
"file_size_mb": "{{size}} Мб",
|
||||
"target_languages": "ЦІЛЬОВІ МОВИ",
|
||||
"voice_kicker": "ГОЛОС",
|
||||
"default_option": "За замовчуванням",
|
||||
"clone_profiles": "Профілі клонів",
|
||||
"presets": "Предустановки",
|
||||
"preserve_bg": "Збереження фонового звуку (музика/FX)",
|
||||
"estimate": "{{videos}} відео × {{langs}} мов = {{jobs}} вакансій",
|
||||
"select_files_langs": "Виберіть файли та мови",
|
||||
"add_to_queue": "Додати в чергу"
|
||||
},
|
||||
"gallery": {
|
||||
"title": "Галерея",
|
||||
"title": "OmniVoice Галерея",
|
||||
"search_placeholder": "Пошук на YouTube…",
|
||||
"all_voices": "Усі голоси ({{count}})",
|
||||
"no_voices": "Голосів ще немає",
|
||||
@@ -714,6 +866,14 @@
|
||||
"youtube_results": "Результати YouTube ({{count}})",
|
||||
"clone_profile": "Клонувати профіль",
|
||||
"crop_audio": "Обрізати аудіо",
|
||||
"cat_disney": "Дісней",
|
||||
"cat_anime": "аніме",
|
||||
"cat_marvel": "Marvel/DC",
|
||||
"cat_celebs": "Знаменитості",
|
||||
"cat_politicians": "Політики",
|
||||
"cat_news": "Ведучі новин",
|
||||
"cat_gaming": "Ігри",
|
||||
"cat_books": "Книги/Фільми",
|
||||
"subtitle": "Сотні готових розроблених голосів — виберіть один і вперед.",
|
||||
"zone_archetypes": "Архетипи",
|
||||
"zone_imports": "Мій імпорт",
|
||||
@@ -818,7 +978,6 @@
|
||||
"badge": "Комерційна ліцензія",
|
||||
"hero_title": "Надішліть голоси AI у виробництво",
|
||||
"hero_desc": "OmniVoice Studio доступний із джерелом відповідно до ліцензії на функціональне джерело (FSL). Більшість користувачів можуть оцінювати, прототипувати та навіть розгортати всередині країни без комерційної угоди. Вам потрібна комерційна ліцензія, лише якщо ви створюєте конкуруючий продукт чи послугу або якщо ваш варіант використання виходить за межі FSL.",
|
||||
"hero_note": "Створення конкуруючого продукту чи послуги або масштабне розгортання (наприклад, обслуговування API з оплатою за використання) вимагає комерційної ліцензії. Рівні цін незабаром — поки зв’яжіться з нами.",
|
||||
"why_title": "Чому компанії обирають OmniVoice",
|
||||
"pricing_title": "Ціноутворення",
|
||||
"faq_title": "Загальні запитання",
|
||||
@@ -838,7 +997,8 @@
|
||||
"benefit_source": "Доступне джерело ядра",
|
||||
"benefit_source_desc": "Повна видимість стека. Аудит, розгалуження та адаптація в рамках умов ліцензії.",
|
||||
"benefit_lang": "646 мов",
|
||||
"benefit_lang_desc": "Транскрибуйте, перекладайте та дублюйте 646 мовами з якістю людського рівня."
|
||||
"benefit_lang_desc": "Транскрибуйте, перекладайте та дублюйте 646 мовами з якістю людського рівня.",
|
||||
"hero_note": "Внутрішнє використання — навіть у великих масштабах — є безкоштовним відповідно до FSL; комерційна ліцензія потрібна лише для того, щоб пропонувати OmniVoice іншим як конкуруючий продукт або послугу (розміщений API або API з оплатою за використання, програма, що перепродається або має мітку «білий»). Незабаром з’являться рівні цін — поки зв’яжіться з нами."
|
||||
},
|
||||
"exportModal": {
|
||||
"export": "Експорт",
|
||||
@@ -927,7 +1087,479 @@
|
||||
"dubbing_title": "Перегляньте дубляж у дії",
|
||||
"dubbing_sync": "Синхронізоване відтворення",
|
||||
"dubbing_picker": "Спробуйте іншу мову:",
|
||||
"dubbing_cta": "Запустіть це на власному відео →"
|
||||
"dubbing_cta": "Запустіть це на власному відео →",
|
||||
"dubbing_loading": "Завантаження демонстрації дубляжу…",
|
||||
"dubbing_dismiss": "Закрити демонстрацію дубляжу",
|
||||
"original_tag": "оригінальний",
|
||||
"dubbed_tag": "дубльований",
|
||||
"script_conversational": "Розмовний",
|
||||
"script_technical": "Технічна лексика",
|
||||
"script_french": "не англійська (французька)",
|
||||
"aria_pause": "Пауза {{label}}",
|
||||
"aria_hear": "Слухати {{label}}",
|
||||
"aria_replay": "Повторити {{label}} через транскрибатор"
|
||||
},
|
||||
"direction": {
|
||||
"title": "Напрямок для сегмента №{{id}}",
|
||||
"desc": "Скажіть конвеєру, як має відчуватися ця лінія. Працює звичайна англійська — система наносить ваші слова на стабільну таксономію (енергія / емоції / темп / інтимність / формальність), а потім використовує таксономію за допомогою кінематографічного перекладу, TTS і підгонки.",
|
||||
"label": "Напрямок",
|
||||
"lineHint": "Рядок: \"{{text}}\"",
|
||||
"placeholder": "напр. терміновий і здивований / теплий, з надією / прошепотів, інтимний",
|
||||
"previewParse": "Розбір попереднього перегляду",
|
||||
"previewFailed": "Помилка попереднього перегляду: {{message}}",
|
||||
"clear": "ясно",
|
||||
"cancel": "Скасувати",
|
||||
"saveDirection": "Зберегти напрямок",
|
||||
"ttsInstruct": "TTS інструкція:",
|
||||
"nothingParsed": "— (нічого не розібрано)",
|
||||
"translateHint": "Підказка перекладу:",
|
||||
"rateBias": "Зміщення оцінки:",
|
||||
"speedsUp": "прискорюється",
|
||||
"slowsDown": "сповільнюється",
|
||||
"taxonomyTokens": "таксономічні токени"
|
||||
},
|
||||
"errors": {
|
||||
"title": "Ця вкладка натрапила на перешкоду.",
|
||||
"desc": "Не хвилюйтеся — решта програми все ще працює. Ви можете змінити вкладку або спробувати ще раз нижче.",
|
||||
"tryAgain": "Спробуйте знову",
|
||||
"openDocs": "Відкрийте документи для цієї помилки"
|
||||
},
|
||||
"keyboard": {
|
||||
"title": "Комбінації клавіш",
|
||||
"footer": "Натисніть <1>?</1> будь-коли, щоб відкрити це.",
|
||||
"or": "або",
|
||||
"nav": "Навігація",
|
||||
"nav_cheatsheet": "Покажіть цю шпаргалку",
|
||||
"nav_closeModal": "Закрити режим / скасувати",
|
||||
"nav_save": "Зберегти проект / зафіксувати обрізку",
|
||||
"segmentEditor": "Редактор сегментів",
|
||||
"seg_split": "Розділити сегмент на курсорі",
|
||||
"seg_merge": "Об'єднати з наступним сегментом",
|
||||
"seg_undo": "Скасувати",
|
||||
"seg_redo": "Повторити",
|
||||
"seg_click": "Первинна дія",
|
||||
"seg_shiftClick": "Вибір діапазону",
|
||||
"trimmer": "Аудіо тример",
|
||||
"trim_playPause": "Попередній перегляд/пауза",
|
||||
"trim_nudgeStart": "Поштовх ручки початку",
|
||||
"trim_nudgeEnd": "Ручка штовхаючого кінця",
|
||||
"trim_fineNudge": "Тонкий поштовх",
|
||||
"trim_coarseNudge": "Грубий поштовх",
|
||||
"trim_zoomIn": "Збільшення / зменшення",
|
||||
"trim_fitAll": "Fit all / Fit selection",
|
||||
"trim_confirm": "Підтвердити обрізку",
|
||||
"dub": "Dub",
|
||||
"dub_generate": "Згенерувати дубляж",
|
||||
"dub_sidebar": "Перемкнути бічну панель"
|
||||
},
|
||||
"network": {
|
||||
"sharing_on_title": "Надіслати — натисніть, щоб дізнатися більше",
|
||||
"share_on_network": "Поділіться у своїй мережі",
|
||||
"switching": "Перемикання…",
|
||||
"network": "Мережа",
|
||||
"local": "Місцевий",
|
||||
"share_confirm_title": "Поділитися у вашій мережі?",
|
||||
"share_confirm_hint": "Інші пристрої, підключені до мережі Wi-Fi/Ethernet, зможуть зв’язатися з OmniVoice за допомогою PIN-коду доступу, що відображається після ввімкнення.",
|
||||
"enabling": "Увімкнення…",
|
||||
"enable": "Увімкнути",
|
||||
"shared_title": "Опубліковано у вашій мережі",
|
||||
"no_interface": "Немає доступного мережевого інтерфейсу — підключіться до Wi-Fi/Ethernet.",
|
||||
"copy_link": "Копіювати посилання",
|
||||
"open_in_browser": "Відкрити в браузері",
|
||||
"qr_alt": "QR для {{ip}}",
|
||||
"pin": "PIN-код:",
|
||||
"stop_sharing": "Припинити ділитися",
|
||||
"copied": "Скопійовано",
|
||||
"enable_error": "Не вдалося ввімкнути спільний доступ: {{message}}",
|
||||
"disable_error": "Не вдалося вимкнути: {{message}}"
|
||||
},
|
||||
"readiness": {
|
||||
"checking_system": "Перевірка системи…",
|
||||
"all_ready": "Всі системи готові",
|
||||
"system_readiness": "Готовність системи",
|
||||
"asr_model": "Модель ASR",
|
||||
"loaded_ready": "Завантажений і готовий",
|
||||
"loading_first_run": "Завантаження… (це може зайняти 1-2 хвилини під час першого запуску)",
|
||||
"failed_to_load": "Не вдалося завантажити",
|
||||
"not_loaded_yet": "Ще не завантажено — буде завантажено під час першої транскрипції",
|
||||
"error_check_logs": "Помилка: {{error}}. Перевірте журнали та спробуйте перезапустити.",
|
||||
"check_logs_restart": "Перевірте журнали на наявність помилок завантаження моделі. Спробуйте перезапустити.",
|
||||
"llm_cinematic": "LLM (кінематографічний)",
|
||||
"llm_configure": "Налаштуйте TRANSLATE_BASE_URL для кінематографічної якості перекладу",
|
||||
"llm_set_env": "Установіть змінні середовища TRANSLATE_BASE_URL і TRANSLATE_API_KEY. Працює з Ollama, OpenAI, LM Studio тощо.",
|
||||
"llm_optional": "Необов’язково — установіть TRANSLATE_BASE_URL для кінематографічної якості"
|
||||
},
|
||||
"license": {
|
||||
"title": "Супертонік-3 — Прийняття ліцензії",
|
||||
"intro": "Supertonic-3 поставляється за двома різними ліцензіями. Будь ласка, перевірте обидва, перш ніж увімкнути механізм.",
|
||||
"sdk_heading": "Код SDK · MIT",
|
||||
"sdk_desc": "Пакет Python inference SDK (supertonic) має ліцензію MIT. Дозвільне використання, в тому числі комерційне.",
|
||||
"read_mit": "Прочитайте ліцензію MIT →",
|
||||
"model_heading": "Вага моделі · OpenRAIL-M",
|
||||
"model_desc": "Ваги моделі Supertonic-3 випускаються за ліцензією OpenRAIL-M. Ця ліцензія обмежує використання не зловмисними цілями — повний набір обмежень на основі використання див. у пов’язаній ліцензії.",
|
||||
"read_openrail": "Прочитайте ліцензію OpenRAIL-M →",
|
||||
"footer": "Якщо натиснути «Прийняти», ваша згода буде записана в локальних налаштуваннях OmniVoice і ввімкне механізм. Ваша згода зберігається лише на цьому комп’ютері — Supertone Inc. або будь-якій третій стороні нічого не повідомляється.",
|
||||
"saving": "Збереження…",
|
||||
"accept": "прийняти",
|
||||
"accepted_toast": "Ліцензія Supertonic-3 прийнята.",
|
||||
"accept_error": "Не вдалося записати прийняття ліцензії: {{message}}"
|
||||
},
|
||||
"voicePreview": {
|
||||
"title": "Голосовий попередній перегляд",
|
||||
"close": "Закрити попередній перегляд",
|
||||
"default_text": "Привіт! Це попередній перегляд того, як я звучу цим голосом.",
|
||||
"default_voice": "Голос за умовчанням",
|
||||
"clone_profiles": "Профілі клонів",
|
||||
"designed_voices": "Розроблені голоси",
|
||||
"presets": "Предустановки",
|
||||
"placeholder": "Введіть щось, щоб почути…",
|
||||
"stop": "Стоп",
|
||||
"regenerate": "Регенерувати",
|
||||
"preview": "Попередній перегляд",
|
||||
"hint": "8 кроків · швидкий попередній перегляд"
|
||||
},
|
||||
"header": {
|
||||
"kicker_studio": "Студія",
|
||||
"kicker_library": "Бібліотека",
|
||||
"kicker_preferences": "Уподобання",
|
||||
"kicker_licensing": "Ліцензування",
|
||||
"label_launchpad": "Стартова панель",
|
||||
"label_clone": "Голосовий клон",
|
||||
"label_design": "Дизайн голосу",
|
||||
"label_dub": "Дубляж",
|
||||
"label_projects": "OmniDrive",
|
||||
"label_gallery": "Галерея",
|
||||
"label_transcriptions": "транскрипції",
|
||||
"label_settings": "Налаштування",
|
||||
"label_enterprise": "Комерційна ліцензія",
|
||||
"status_ready": "Готовий",
|
||||
"status_loading": "Завантаження…",
|
||||
"status_idle": "Бездіяльність",
|
||||
"memory_management": "Управління пам'яттю",
|
||||
"flush": "Промити",
|
||||
"loaded_models": "Завантажені моделі",
|
||||
"no_models": "Немає завантажених моделей",
|
||||
"unload": "Розвантажити",
|
||||
"flush_caches": "Очистити кеші",
|
||||
"unload_all_flush": "Вивантажити все + промити"
|
||||
},
|
||||
"sidebar": {
|
||||
"tab_drive": "Драйв",
|
||||
"tab_history": "історія",
|
||||
"tab_exports": "Експорт",
|
||||
"save_project": "Зберегти проект Dub",
|
||||
"save_new_project": "Зберегти як новий проект Dub",
|
||||
"dub_projects": "Дуб проекти",
|
||||
"voice_clones": "Голосові клони",
|
||||
"designed_voices": "Розроблені голоси",
|
||||
"no_dub_projects": "Немає збережених проектів дубляжу",
|
||||
"no_dub_hint": "Завантажте відео та натисніть «Зберегти», щоб зберегти свою роботу.",
|
||||
"no_clones": "Голосових клонів ще немає",
|
||||
"no_clones_hint": "Запишіть або завантажте аудіо, а потім натисніть «Зберегти як голосовий профіль».",
|
||||
"no_designs": "Ще немає розроблених голосів",
|
||||
"no_designs_hint": "Згенеруйте голос і збережіть його з історії.",
|
||||
"history_subtitle": "Історія генерації · Зберігається в SQLite",
|
||||
"no_history": "Без історії покоління",
|
||||
"no_history_hint": "Синтезуйте аудіо або дублюйте відео — тут з’являться результати.",
|
||||
"clear_history": "Очистити історію",
|
||||
"clear_confirm": "Очистити всі елементи історії {{count}}? Це неможливо скасувати.",
|
||||
"history_cleared": "Історія очищена",
|
||||
"recent_exports": "Останні експорти",
|
||||
"no_exports": "Немає завантажених виходів",
|
||||
"no_exports_hint": "Експортуйте файл через Tauri, щоб побачити його відстеження тут.",
|
||||
"show_in_folder": "Показати в папці",
|
||||
"open": "відкритий",
|
||||
"select": "Виберіть",
|
||||
"try_voice": "Спробуй",
|
||||
"consistent": "послідовний",
|
||||
"locked": "Заблоковано",
|
||||
"clone_label": "Клон",
|
||||
"design_label": "Дизайн",
|
||||
"dub_label": "Dub",
|
||||
"save_label": "зберегти",
|
||||
"lock_identity": "Блокування голосової ідентифікації",
|
||||
"in_folder": "в {{folder}}"
|
||||
},
|
||||
"trimmer": {
|
||||
"title": "Обрізати контрольне аудіо",
|
||||
"decoding": "Декодування аудіо…",
|
||||
"meta_length": "Довжина {{duration}} · {{sampleRate}} Гц",
|
||||
"meta_rendering": "рендеринг сигналу {{percent}}%",
|
||||
"keyboard_hint": "прокручування = масштабування · shift+прокручування = панорамування · alt+перетягування = панорамування · ⏐ ⟵ ⟶ ⏐ клавіші регулювання ручок",
|
||||
"zoom_in": "Збільшити (+)",
|
||||
"zoom_out": "Зменшити (-)",
|
||||
"fit_all": "Підходить для всіх (Дім)",
|
||||
"fit_selection": "Підібрати вибір (кінець)",
|
||||
"fit_sel_btn": "FIT SEL",
|
||||
"view_range": "Переглянути {{start}} → {{end}} ({{duration}})",
|
||||
"start_label": "Почніть",
|
||||
"end_label": "Кінець",
|
||||
"length_label": "Довжина",
|
||||
"too_long": ">{{max}}с",
|
||||
"too_short": "занадто короткий",
|
||||
"length_ok": "добре",
|
||||
"loop_preview": "Попередній перегляд циклу",
|
||||
"pause": "Пауза",
|
||||
"preview_selection": "Попередній перегляд вибору",
|
||||
"play_hint": "Пробіл для гри · Enter для підтвердження · Esc для скасування",
|
||||
"cancel": "Скасувати",
|
||||
"use_trimmed": "Використовуйте обрізані",
|
||||
"decode_failed": "Помилка декодування: {{message}}",
|
||||
"playback_failed": "Помилка відтворення: {{message}}",
|
||||
"audio_load_failed": "Помилка завантаження аудіо",
|
||||
"unit_seconds": "с"
|
||||
},
|
||||
"casting": {
|
||||
"title": "Кастинг динаміків",
|
||||
"auto_assign_title": "Автоматичне призначення голосів із вилучених клонів динаміків",
|
||||
"auto_cast": "Автокаст",
|
||||
"all_cast": "Всі актори",
|
||||
"segments_count": "{{count}} сегментів",
|
||||
"assign_voice": "Призначити голос…",
|
||||
"from_video": "🎤 З відео ({{name}})",
|
||||
"no_profiles": "Голосові профілі ще не збережено.",
|
||||
"preview_voice": "Попередній перегляд голосу"
|
||||
},
|
||||
"checkpoint": {
|
||||
"asr_title": "Стенограми готові",
|
||||
"asr_cta": "Перекласти",
|
||||
"asr_hint": "Виправляйте будь-які помилки ASR зараз — жорстка дикція збереже спроби TTS пізніше.",
|
||||
"translate_title": "Переклади готові",
|
||||
"translate_cta": "Згенерувати дубляж",
|
||||
"translate_hint": "Перегляньте цільовий текст. Занадто довгі лінії прискорюються; ви також можете редагувати безпосередньо.",
|
||||
"done_title": "Дубляж завершено",
|
||||
"done_hint": "Перегляньте коефіцієнти часу та синхронізації. Налаштуйте будь-який рядок і натисніть «Regen changes» для швидкого часткового повтору.",
|
||||
"segment_one": "{{count}} сегмент",
|
||||
"segment_other": "{{count}} сегментів",
|
||||
"dismiss_title": "Відхилити — не з’являтиметься для цього етапу до перезавантаження"
|
||||
},
|
||||
"compare": {
|
||||
"title": "A/B голосове порівняння",
|
||||
"close": "Близьке порівняння",
|
||||
"desc": "Порівняйте два голоси поруч, щоб прийняти рішення про кастинг. Додаток залишається інтерактивним.",
|
||||
"test_phrase": "Тестова фраза",
|
||||
"voice_a": "Голос А",
|
||||
"voice_b": "Голос Б",
|
||||
"select_voice": "— Виберіть голос —",
|
||||
"preset_suffix": "(Попереднє налаштування)",
|
||||
"no_audio": "Аудіо ще немає",
|
||||
"close_btn": "Закрити",
|
||||
"comparing": "Порівнюючи…",
|
||||
"compare_btn": "Порівняйте",
|
||||
"preparing_voice": "Підготовка голосу...",
|
||||
"generating_voice_a": "Створення голосу A...",
|
||||
"generating_voice_b": "Створення голосу B...",
|
||||
"comparison_complete": "Порівняння завершено!",
|
||||
"play_failed": "Помилка відтворення: {{message}}"
|
||||
},
|
||||
"models": {
|
||||
"hf_token_set_toast": "Набір токенів HuggingFace — увімкнено швидші завантаження",
|
||||
"hf_token_save_failed": "Не вдалося зберегти маркер",
|
||||
"install_started": "Встановлення розпочато — прогрес у рядку",
|
||||
"delete_confirm": "Видалити {{repoId}}? Ви можете перевстановити його пізніше.",
|
||||
"delete_confirm_title": "Видалити модель",
|
||||
"deleted": "Видалено {{repoId}}",
|
||||
"reinstall_confirm": "Перевстановити {{repoId}}? Це призведе до видалення поточної копії та повторного завантаження.",
|
||||
"reinstall_confirm_title": "Перевстановити модель",
|
||||
"reinstalling": "Перевстановлення",
|
||||
"recommended_installed": "Рекомендовані моделі вже встановлені.",
|
||||
"started_downloading_one": "Розпочато завантаження моделі {{count}}",
|
||||
"started_downloading_other": "Розпочато завантаження {{count}} моделей",
|
||||
"install_failed": "Помилка встановлення: {{message}}",
|
||||
"removing_cached": "Видалення кешованих версій…",
|
||||
"resolving_metadata": "Вирішення метаданих репо",
|
||||
"retry_attempt": "Повторна спроба {{attempt}} — {{error}}",
|
||||
"connecting_hf": "Підключення до HuggingFace…",
|
||||
"resolving_files_one": "Вирішення файлу {{count}}…",
|
||||
"resolving_files_other": "Вирішення файлів {{count}}…",
|
||||
"resolving_files_active": "Вирішення {{count}} файл(ів)… · {{file}}",
|
||||
"measuring": "вимірювання…",
|
||||
"files_progress": "{{done}}/{{total}} файлів",
|
||||
"install_error": "Помилка встановлення: {{error}}",
|
||||
"view_on_hf": "Переглянути на HuggingFace",
|
||||
"install_btn": "встановити",
|
||||
"reinstall_btn": "Перевстановити",
|
||||
"downloading": "завантаження",
|
||||
"deleting": "видалення",
|
||||
"working": "працює",
|
||||
"installed": "встановлено",
|
||||
"not_installed": "не встановлено",
|
||||
"required_tag": "потрібно",
|
||||
"delete_btn": "Видалити",
|
||||
"hf_token_btn": "Токен HF",
|
||||
"hf_set_title": "Встановіть маркер HuggingFace для швидшого завантаження",
|
||||
"get_token": "Отримати маркер →",
|
||||
"reco_installed_for": "Рекомендований пакет, встановлений для {{device}}",
|
||||
"reco_for": "Рекомендовано для {{device}}",
|
||||
"starting": "Початок...",
|
||||
"required_size": "Потрібно ~{{size}} Гб",
|
||||
"all_size": "Всі ~{{size}} Гб",
|
||||
"req_tag": "вимога",
|
||||
"search_placeholder": "Пошук моделей…",
|
||||
"search_label": "Пошук моделей",
|
||||
"no_matches": "Жодна модель не відповідає вашим фільтрам.",
|
||||
"sort_by": "Сортувати за {{column}}",
|
||||
"column_model": "Модель",
|
||||
"column_role": "Роль",
|
||||
"column_size": "Розмір",
|
||||
"column_status": "Статус",
|
||||
"ready_badge": "Готовий",
|
||||
"loading_badge": "Завантаження…",
|
||||
"idle_badge": "Бездіяльність",
|
||||
"started_downloading_required_one": "Розпочато завантаження {{count}} потрібної моделі",
|
||||
"started_downloading_required_other": "Розпочато завантаження {{count}} необхідних моделей"
|
||||
},
|
||||
"enterprise_faq": {
|
||||
"q_internal_tools": "Чи потрібна мені ліцензія на внутрішні інструменти?",
|
||||
"a_internal_tools": "Внутрішнє використання вашими співробітниками та підрядниками є дозволеною метою відповідно до FSL — ліцензія не потрібна. Комерційна ліцензія потрібна, коли ви робите OmniVoice доступним для інших як частину конкуруючого продукту чи послуги (перепродаж, хостинг SaaS, white-label).",
|
||||
"q_try_before": "Чи можу я спробувати перед тим, як взяти участь?",
|
||||
"a_try_before": "так Повну програму можна безкоштовно завантажити та запустити локально для оцінки відповідно до FSL. Коли ви будете готові обговорити комерційне впровадження, напишіть нам електронною поштою, і ми разом обговоримо деталі.",
|
||||
"q_watermark": "А як щодо водяного знака?",
|
||||
"a_watermark": "Невидимий водяний знак AudioSeal вбудовано за замовчуванням. Комерційні ліцензіати можуть вимкнути його в Налаштуваннях → Конфіденційність. Безкоштовне/особисте використання завжди містить водяний знак.",
|
||||
"q_apache": "Чи джерело коли-небудь стане Apache 2.0?",
|
||||
"a_apache": "так Кожен випуск автоматично перетворюється на ліцензію Apache версії 2.0 у другу річницю публікації. Це означає, що сьогоднішній випуск – це Apache 2.0 через два роки, від нас не вимагається жодних дій — FSL гарантує це безповоротно."
|
||||
},
|
||||
"gallery_extra": {
|
||||
"save_prompt": "Введіть назву для цього голосового профілю:",
|
||||
"saved_as_profile": "Голос збережено як профіль!",
|
||||
"save_profile_failed": "Не вдалося зберегти профіль",
|
||||
"download_failed": "Помилка завантаження: {{message}}",
|
||||
"trim_load_failed": "Не вдалося завантажити аудіо для обрізання: {{message}}",
|
||||
"upload_crop_failed": "Не вдалося завантажити обрізаний голос: {{message}}"
|
||||
},
|
||||
"dub_workflow": {
|
||||
"preparing_audio": "Підготовка аудіо…",
|
||||
"preparing_video": "Підготовка відео…",
|
||||
"extracting_audio_scenes": "Вилучення аудіо та сцен…",
|
||||
"transcribing_audio": "Транскрибування аудіо…",
|
||||
"transcription_complete": "Транскрипцію завершено",
|
||||
"upload_cancelled": "Завантаження скасовано",
|
||||
"upload_failed": "Помилка завантаження: {{message}}",
|
||||
"downloading_video": "Завантаження відео…",
|
||||
"ingested": "Поглинуто {{url}}",
|
||||
"ingest_cancelled": "Введення скасовано",
|
||||
"ingest_failed": "Помилка введення URL-адреси: {{message}}",
|
||||
"retry_cancelled": "Повторну спробу скасовано",
|
||||
"transcription_failed": "Помилка транскрипції: {{message}}",
|
||||
"import_srt_no_job": "Спершу завантажте або завантажте відео — немає завдання додавати субтитри.",
|
||||
"imported_cues": "Імпортовано {{count}} репліки з {{file}}",
|
||||
"skipped_malformed": "{{count}} пропущено (неправильно)",
|
||||
"dropped_overlap": "{{count}} вилучено (перекриття)",
|
||||
"clamped_to_duration": "{{count}} затиснутий до довжини носія",
|
||||
"srt_import_failed": "Помилка імпорту SRT",
|
||||
"cleaned_one": "Очищено {{count}} фрагмент",
|
||||
"cleaned_other": "Очищено {{count}} фрагментів",
|
||||
"segments_clean": "Сегменти вже чисті",
|
||||
"cleanup_failed": "Помилка очищення: {{message}}",
|
||||
"cinematic_no_llm": "Для кінематографічної якості потрібен LLM — установіть TRANSLATE_BASE_URL + TRANSLATE_API_KEY (Ollama працює локально). Повертаючись до Fast.",
|
||||
"translate_errors": "{{errorCount}}/{{totalCount}} сегментів не виконано: {{firstError}}",
|
||||
"translated_segments": "Перекладено {{count}} сегментів → {{lang}}",
|
||||
"translated_cinematic_suffix": "(кінематографічний)",
|
||||
"translation_failed": "Помилка перекладу: {{message}}",
|
||||
"regenerating": "Відновлення {{count}} сегмента(ів)…",
|
||||
"generating_dub": "Створення копії…",
|
||||
"generating_progress": "Створення копії… {{current}}/{{total}}",
|
||||
"generation_aborted": "Генерацію перервано.",
|
||||
"dubbing_aborted": "Дубляж перервано",
|
||||
"generation_stream_ended": "Потік генерації завершився до завершення",
|
||||
"dub_complete": "Дубляж завершено",
|
||||
"stop_failed": "Зупинити не вдалося",
|
||||
"save_first": "Будь ласка, спочатку натисніть «Завантажити та транскрибувати», щоб відео було оброблено на сервері перед збереженням.",
|
||||
"project_saved": "Проект збережено",
|
||||
"project_created": "Проект створено",
|
||||
"save_failed": "Не вдалося зберегти: {{message}}",
|
||||
"opened_project": "Відкрито: {{name}}",
|
||||
"delete_project_confirm": "Видалити цей проект? Це неможливо скасувати.",
|
||||
"project_deleted": "Проект видалено",
|
||||
"delete_history_confirm": "Видалити цей елемент історії?",
|
||||
"history_deleted": "Елемент історії видалено",
|
||||
"restored_state": "Відновлено стан попереднього покоління",
|
||||
"upgrading_preview": "Оновлення {{count}} сегментів якості попереднього перегляду до повної якості…"
|
||||
},
|
||||
"tts_errors": {
|
||||
"enter_text": "Будь ласка, введіть текст",
|
||||
"upload_or_select": "Завантажте аудіо або виберіть голосовий профіль",
|
||||
"trim_hint": "Аудіо {{duration}}s — обріжте до ≤{{max}}s для найкращого клонування",
|
||||
"timeout": "Час очікування генерації минув — можливо, модель все ще завантажується. Перевірте Параметри → Журнали, а потім повторіть спробу.",
|
||||
"error_prefix": "Помилка: {{message}}",
|
||||
"ignored_unsupported": "Ігнорована непідтримувана інструкція: {{items}}",
|
||||
"ignored_duplicate": "Проігноровано (категорію вже встановлено): {{items}}"
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Спільне використання та віддалений доступ",
|
||||
"help": "Надайте цей запущений екземпляр OmniVoice іншим вашим машинам, не перезапускаючи його. За замовчуванням встановлено лише зворотний зв’язок — нічого не надається, доки ви не ввімкнете його тут.",
|
||||
"local_network": "Локальна мережа",
|
||||
"local_help": "Поділіться через Wi-Fi / Ethernet за допомогою одноразового PIN-коду доступу. Інші пристрої сканують QR-код або відкривають посилання.",
|
||||
"ports_title": "Порти",
|
||||
"ports_help": "Вони встановлюються за допомогою змінних середовища, які зчитуються під час запуску. Змініть серверну частину або порт інтерфейсу користувача, установивши змінну та перезапустивши OmniVoice.",
|
||||
"backend_port": "Внутрішній порт",
|
||||
"ui_port": "порт інтерфейсу користувача",
|
||||
"lan_share_port": "LAN-спільний порт",
|
||||
"port_error": "Введіть порт між 1024 і 65535",
|
||||
"port_saved": "Спільний порт локальної мережі збережено — застосовується наступного разу, коли ви ввімкнете спільний доступ",
|
||||
"port_save_failed": "Не вдалося зберегти порт: {{message}}",
|
||||
"saving": "Збереження…",
|
||||
"ports_note": "Порти серверної частини та інтерфейсу користувача застосовуються після перезавантаження. Порт спільного доступу до локальної мережі буде застосовано наступного разу, коли ви ввімкнете спільний доступ.",
|
||||
"tailscale_title": "Tailscale (приватний віддалений доступ)",
|
||||
"tailscale_checking": "Перевірка хвостової шкали…",
|
||||
"tailscale_absent": "Хвостова луска не виявлена. Установіть його, щоб безпечно зв’язатися з OmniVoice з будь-якої точки вашої приватної мережі.",
|
||||
"tailscale_install": "Встановити Tailscale",
|
||||
"tailscale_running": "Хвостова луска біжить. Обслуговуйте OmniVoice через приватну мережу.",
|
||||
"tailscale_not_logged_in": "Tailscale встановлено, але ви не ввійшли в систему. Спочатку запустіть і ввійдіть у Tailscale.",
|
||||
"tailscale_enabled": "Подача хвостової шкали ввімкнена",
|
||||
"tailscale_enable_failed": "Не вдалося ввімкнути Tailscale",
|
||||
"tailscale_enable_error": "Не вдалося ввімкнути Tailscale: {{message}}",
|
||||
"tailscale_disabled": "Подача хвостової шкали вимкнена",
|
||||
"tailscale_disable_failed": "Не вдалося вимкнути Tailscale",
|
||||
"tailscale_disable_error": "Не вдалося вимкнути хвостову шкалу: {{message}}",
|
||||
"tailscale_enabling": "Увімкнення…",
|
||||
"tailscale_enable_btn": "Увімкнути службу Tailscale",
|
||||
"tailscale_disabling": "Вимкнення…",
|
||||
"tailscale_disable_btn": "Зупинити подачу Tailscale",
|
||||
"tailscale_copy": "Копіювати посилання",
|
||||
"tailscale_open": "Відкрити в браузері",
|
||||
"copied": "Скопійовано",
|
||||
"tailscale_qr_alt": "QR-код для URL-адреси Tailscale"
|
||||
},
|
||||
"reportBug": {
|
||||
"label": "Повідомити про помилку",
|
||||
"title": "Відкриває попередньо заповнену сторінку проблем GitHub у вашому браузері. Нічого не буде надіслано, доки ви не натиснете Надіслати."
|
||||
},
|
||||
"app": {
|
||||
"loading": "Завантаження…",
|
||||
"trimmed_loaded": "Завантажено обрізаний звук",
|
||||
"toast_exported": "Експортовано: {{name}}",
|
||||
"toast_export_failed": "Помилка експорту: {{message}}",
|
||||
"toast_open_folder_failed": "Не вдалося відкрити папку: {{message}}",
|
||||
"toast_saving": "Збереження {{name}}...",
|
||||
"toast_saved": "Збережено: {{path}}",
|
||||
"toast_save_error": "Помилка збереження: {{message}}",
|
||||
"toast_processing": "Обробка {{name}}...",
|
||||
"toast_downloaded": "Завантажено {{name}}",
|
||||
"toast_download_error": "Помилка завантаження: {{message}}",
|
||||
"toast_upload_first": "Будь ласка, спочатку натисніть «Завантажити та транскрибувати», щоб відео було оброблено на сервері перед збереженням.",
|
||||
"toast_save_failed": "Не вдалося зберегти: {{message}}",
|
||||
"toast_opened": "Відкрито: {{name}}",
|
||||
"toast_project_deleted": "Проект видалено",
|
||||
"toast_restored_state": "Відновлено стан попереднього покоління",
|
||||
"toast_history_deleted": "Елемент історії видалено",
|
||||
"toast_flushed": "Очищено — RAM {{ram}}G · VRAM {{vram}}G{{unloaded}}",
|
||||
"toast_model_unloaded": "· модель ненавантажена",
|
||||
"toast_flush_failed": "Помилка очищення: {{message}}",
|
||||
"toast_project_saved": "Проект збережено",
|
||||
"toast_project_created": "Проект створено"
|
||||
},
|
||||
"update": {
|
||||
"available": "Доступне оновлення {{version}}",
|
||||
"install": "Встановити та перезапустити",
|
||||
"install_hint": "Завантажити оновлення та перезапустити в новій версії",
|
||||
"downloading": "Оновлення… {{pct}}%",
|
||||
"restart": "Перезапустити для оновлення",
|
||||
"busy": "Спочатку завершіть дубляж — потім встановіть оновлення.",
|
||||
"whats_new": "Що нового",
|
||||
"failed": "Не вдалося оновити",
|
||||
"retry": "Повторіть спробу",
|
||||
"dismiss": "Відхилити"
|
||||
},
|
||||
"archetypes": {
|
||||
"featured": "Рекомендовані",
|
||||
@@ -945,5 +1577,25 @@
|
||||
"facet_accent": "Акцент",
|
||||
"facet_lang": "Мова",
|
||||
"facet_whisper": "Шепіт"
|
||||
},
|
||||
"support": {
|
||||
"tab_support": "Підтримка",
|
||||
"tab_license": "Комерційна ліцензія",
|
||||
"toggle_label": "Підтримка або комерційна ліцензія",
|
||||
"other_ways": "Інші способи допомоги",
|
||||
"star_github": "Зірка на GitHub",
|
||||
"join_discord": "Приєднуйтесь до Discord"
|
||||
},
|
||||
"updates": {
|
||||
"tab": "Оновлення",
|
||||
"up_to_date": "Актуально · v{{version}}",
|
||||
"check_now": "Перевір зараз",
|
||||
"releases": "Релізи",
|
||||
"current": "поточний",
|
||||
"prerelease": "попередній перегляд",
|
||||
"loading": "Завантаження випусків…",
|
||||
"none": "Випусків не знайдено",
|
||||
"load_error": "Не вдалося завантажити випуски (офлайн?)",
|
||||
"retry_load": "Повторіть спробу"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,4 @@
|
||||
{
|
||||
"update": {
|
||||
"available": "Đã có bản cập nhật {{version}}",
|
||||
"install": "Cài đặt và khởi động lại",
|
||||
"install_hint": "Tải bản cập nhật và khởi động lại vào phiên bản mới",
|
||||
"downloading": "Đang cập nhật… {{pct}}%",
|
||||
"restart": "Khởi động lại để cập nhật",
|
||||
"busy": "Hoàn tất lồng tiếng của bạn trước — rồi cài đặt bản cập nhật."
|
||||
},
|
||||
"nav": {
|
||||
"clone": "Nhân bản",
|
||||
"design": "Thiết kế",
|
||||
@@ -16,7 +8,10 @@
|
||||
"launchpad": "Bệ phóng",
|
||||
"gallery": "Thư viện ảnh",
|
||||
"transcripts": "bảng điểm",
|
||||
"omnidrive": "OmniDrive"
|
||||
"omnidrive": "OmniDrive",
|
||||
"move_rail_right": "Di chuyển đường ray sang phải",
|
||||
"move_rail_left": "Di chuyển đường ray sang trái",
|
||||
"flip_rail": "Mặt đường ray lật"
|
||||
},
|
||||
"settings": {
|
||||
"general": "Chung",
|
||||
@@ -45,7 +40,40 @@
|
||||
"ffmpeg_missing": "Không tìm thấy",
|
||||
"ffmpeg_current": "Đường dẫn hiện tại",
|
||||
"ffmpeg_desc": "Đặt đường dẫn ffmpeg tùy chỉnh nếu tính năng tự động phát hiện không thành công.",
|
||||
"ffmpeg_saved": "Đã đặt đường dẫn FFmpeg - khởi động lại chương trình phụ trợ để áp dụng."
|
||||
"ffmpeg_saved": "Đã đặt đường dẫn FFmpeg - khởi động lại chương trình phụ trợ để áp dụng.",
|
||||
"diagnostics_copied": "Đã sao chép chẩn đoán — dán vào báo cáo sự cố của bạn.",
|
||||
"updater_desktop": "Trình cập nhật chỉ chạy trong ứng dụng dành cho máy tính để bàn.",
|
||||
"latest_version": "Bạn đang dùng phiên bản mới nhất.",
|
||||
"logs_load_failed": "Không tải được nhật ký: {{message}}",
|
||||
"clear_frontend_confirm": "Xóa bộ đệm nhật ký lối vào trong bộ nhớ?",
|
||||
"clear_frontend_title": "Xóa nhật ký",
|
||||
"frontend_logs_cleared": "Đã xóa nhật ký giao diện người dùng",
|
||||
"clear_tauri_confirm": "Cắt bớt các tệp nhật ký phía Tauri? Hệ điều hành sẽ tiếp tục viết các mục mới.",
|
||||
"clear_tauri_title": "Xóa nhật ký Tauri",
|
||||
"nothing_to_clear": "Không có gì để xóa - chưa có tệp nhật ký Tauri trên đĩa.",
|
||||
"cleared_tauri_one": "Đã xóa tệp nhật ký {{count}} Tauri",
|
||||
"cleared_tauri_other": "Đã xóa _V_0__ tệp nhật ký Tauri",
|
||||
"clear_tauri_failed": "Không xóa được nhật ký Tauri: {{message}}",
|
||||
"clear_backend_confirm": "Xóa thời gian chạy phụ trợ + nhật ký sự cố? Điều này không thể hoàn tác được.",
|
||||
"clear_backend_title": "Xóa nhật ký",
|
||||
"backend_logs_cleared": "Đã xóa nhật ký phụ trợ",
|
||||
"clear_backend_failed": "Không thể xóa nhật ký",
|
||||
"copy_failed": "Sao chép không thành công: {{message}}",
|
||||
"update_check_failed": "Kiểm tra cập nhật không thành công: {{message}}",
|
||||
"save_failed": "Lưu không thành công: {{message}}",
|
||||
"clear_failed": "Xóa không thành công: {{message}}",
|
||||
"engine_switched": "{{family}} → {{engine}}",
|
||||
"channel_set_failed": "Không đặt được kênh: {{message}}",
|
||||
"updater_downloading": "Đang tải xuống _V_0__…",
|
||||
"updater_installed": "Đã cài đặt - khởi chạy lại.",
|
||||
"updater_available_title": "Đã có bản cập nhật",
|
||||
"updater_available_body": "Đã có phiên bản {{version}}.\n\n{{notes}}\n\nTải xuống và cài đặt ngay bây giờ?",
|
||||
"updater_notes_fallback": "Xem ghi chú phát hành trên GitHub.",
|
||||
"shortcut_load_failed": "Không thể tải phím tắt: {{message}}",
|
||||
"shortcut_set": "Phím tắt đọc chính tả được đặt thành {{shortcut}}",
|
||||
"shortcut_register_failed": "Không thể đăng ký: {{message}}",
|
||||
"shortcut_reset": "Đặt lại về mặc định",
|
||||
"shortcut_reset_failed": "Đặt lại không thành công: {{message}}"
|
||||
},
|
||||
"bootstrap": {
|
||||
"title": "OmniVoice Studio",
|
||||
@@ -69,7 +97,13 @@
|
||||
"waiting_output": "Đang chờ dữ liệu…",
|
||||
"auto_detect": "Tự động nhận diện",
|
||||
"suggest_lang": "Chuyển sang Tiếng Việt?",
|
||||
"select_lang": "Ngôn ngữ:"
|
||||
"select_lang": "Ngôn ngữ:",
|
||||
"region_global": "Toàn cầu (trực tiếp)",
|
||||
"region_china": "Trung Quốc (gương)",
|
||||
"region_russia": "Nga (gương)",
|
||||
"region_restricted": "Bị hạn chế (gương)",
|
||||
"unknown_error": "Lỗi không xác định",
|
||||
"retrying": "Đang thử lại…"
|
||||
},
|
||||
"stories": {
|
||||
"title": "Biên tập truyện",
|
||||
@@ -158,7 +192,17 @@
|
||||
"reload": "Buộc tải lại giao diện người dùng",
|
||||
"backend": "Phần phụ trợ",
|
||||
"frontend": "Giao diện người dùng",
|
||||
"tauri": "Tauri"
|
||||
"tauri": "Tauri",
|
||||
"cancelOp": "Hủy thao tác",
|
||||
"dismiss": "Loại bỏ",
|
||||
"dismissStatus": "Trạng thái loại bỏ",
|
||||
"search": "Tìm kiếm…",
|
||||
"no_matches": "Không có kết quả phù hợp",
|
||||
"recent_and_popular": "Gần đây & Phổ biến",
|
||||
"popular_label": "phổ biến",
|
||||
"showing_of": "Đang hiển thị {{shown}} trong tổng số {{total}}. Nhập để tìm kiếm…",
|
||||
"yes": "Có",
|
||||
"no": "Không"
|
||||
},
|
||||
"launchpad": {
|
||||
"greeting": "xin chào ở đó",
|
||||
@@ -180,7 +224,16 @@
|
||||
"locked": "ĐÃ KHÓA",
|
||||
"open": "Mở",
|
||||
"try_it": "Hãy thử nó",
|
||||
"audio_only": "Chỉ âm thanh"
|
||||
"audio_only": "Chỉ âm thanh",
|
||||
"stories_title": "Truyện",
|
||||
"stories_desc": "Sách nói nhiều giọng nói — chọn nhân vật, nhập tập lệnh, xuất theo chương.",
|
||||
"gallery_title": "Thư viện giọng nói",
|
||||
"gallery_desc": "Duyệt qua các giọng nói được thiết kế sẵn theo giọng, độ tuổi và phong cách — không cần thiết lập.",
|
||||
"transcripts_title": "bảng điểm",
|
||||
"transcripts_desc": "Biến âm thanh hoặc video thành văn bản có thể chỉnh sửa, tìm kiếm được — trên 646 ngôn ngữ.",
|
||||
"recent_files": "Các tập tin gần đây",
|
||||
"view_all_files": "Xem tất cả các tập tin",
|
||||
"file": "tập tin"
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "Lời nhắc",
|
||||
@@ -271,11 +324,6 @@
|
||||
"outputs": "đầu ra",
|
||||
"crash_log": "Nhật ký sự cố",
|
||||
"update_endpoint": "Cập nhật điểm cuối",
|
||||
"update_channel": "Kênh cập nhật",
|
||||
"channel_stable": "Ổn định",
|
||||
"channel_preview": "Xem trước",
|
||||
"channel_set": "Đã đặt kênh cập nhật thành {{channel}}",
|
||||
"channel_preview_hint": "Bản xem trước theo dõi bản dựng main mới nhất — tính năng mới hơn, ít được kiểm thử hơn. Quay lại bản ổn định nếu có bản phát hành ổn định mới hơn.",
|
||||
"yes": "vâng",
|
||||
"no": "không",
|
||||
"web_preview": "xem trước trang web",
|
||||
@@ -284,7 +332,12 @@
|
||||
"copy_diagnostics": "Sao chép chẩn đoán",
|
||||
"github": "OmniVoice trên GitHub",
|
||||
"model_card": "Thẻ mẫu",
|
||||
"commercial_license": "Giấy phép thương mại"
|
||||
"commercial_license": "Giấy phép thương mại",
|
||||
"update_channel": "Kênh cập nhật",
|
||||
"channel_stable": "Ổn định",
|
||||
"channel_preview": "Xem trước",
|
||||
"channel_set": "Đã đặt kênh cập nhật thành {{channel}}",
|
||||
"channel_preview_hint": "Bản xem trước theo dõi bản dựng main mới nhất — tính năng mới hơn, ít được kiểm thử hơn. Quay lại bản ổn định nếu có bản phát hành ổn định mới hơn."
|
||||
},
|
||||
"privacy": {
|
||||
"desc": "Mọi thứ đều chạy trên <1>máy này</1>. Âm thanh, video và bản ghi của bạn không bao giờ rời khỏi máy tính của bạn trừ khi bạn sử dụng một cách rõ ràng một trình dịch trực tuyến (Google, DeepL, v.v.) hoặc đẩy tới HuggingFace.",
|
||||
@@ -340,7 +393,30 @@
|
||||
"unavailable": "không có sẵn",
|
||||
"use": "sử dụng",
|
||||
"loading": "Đang tải động cơ…",
|
||||
"refresh": "Làm mới"
|
||||
"refresh": "Làm mới",
|
||||
"matrixTitle": "Ma trận tương thích động cơ",
|
||||
"loadFailed": "Không tải được động cơ: {{message}}",
|
||||
"couldNotLoad": "Không thể tải động cơ: {{message}}",
|
||||
"retry": "Thử lại",
|
||||
"activeEngine": "Đang hoạt động {{family}}: {{engine}}",
|
||||
"engineCompatLabel": "{{family}} khả năng tương thích động cơ",
|
||||
"active": "hoạt động",
|
||||
"whyUnavailable": "Tại sao không có sẵn?",
|
||||
"lastError": "Lỗi cuối cùng: {{error}}",
|
||||
"installedAndReady": "Đã cài đặt và sẵn sàng",
|
||||
"notInstalled": "Chưa được cài đặt",
|
||||
"available": "Có sẵn",
|
||||
"subprocessTitle": "Chạy trong quy trình con + venv của chính nó",
|
||||
"inProcessTitle": "Chạy trong quy trình OmniVoice Python",
|
||||
"testEngine": "Động cơ thử nghiệm",
|
||||
"testing": "Đang thử nghiệm…",
|
||||
"recheck": "Kiểm tra lại",
|
||||
"rechecking": "Đang kiểm tra lại…",
|
||||
"latencyMs": "{{ms}} mili giây",
|
||||
"failed": "thất bại",
|
||||
"acceptLicense": "Chấp nhận giấy phép",
|
||||
"noBackends": "Không có phụ trợ được đăng ký.",
|
||||
"switch_failed": "Không thể chuyển đổi động cơ"
|
||||
},
|
||||
"capture": {
|
||||
"desc": "Phím nóng chung chỉ hoạt động trong ứng dụng máy tính để bàn. Giao diện người dùng web sử dụng phím tắt <1>Ctrl+Shift+Space</1> trong trang trong khi cửa sổ đang có tiêu điểm.",
|
||||
@@ -352,13 +428,55 @@
|
||||
"record_shortcut": "Ghi phím tắt",
|
||||
"recording": "Đang ghi…",
|
||||
"save": "Lưu",
|
||||
"reset_default": "Đặt lại về mặc định"
|
||||
"reset_default": "Đặt lại về mặc định",
|
||||
"listening_label": "Đang nghe…",
|
||||
"transcribing_label": "Phiên âm…",
|
||||
"pasted": "Đã dán",
|
||||
"no_speech": "Không phát hiện thấy giọng nói nào",
|
||||
"mic_denied": "Quyền truy cập micrô bị từ chối",
|
||||
"mic_denied_toast": "Quyền truy cập micrô bị từ chối. {{hint}}",
|
||||
"mic_hint_mac": "macOS: mở Cài đặt hệ thống → Quyền riêng tư & bảo mật → Micrô và bật OmniVoice.",
|
||||
"mic_hint_windows": "Windows: mở Cài đặt → Quyền riêng tư & bảo mật → Micrô và bật OmniVoice.",
|
||||
"mic_hint_linux": "Linux: kiểm tra xem người dùng của bạn có thuộc nhóm âm thanh hay không và WebView có quyền truy cập micrô.",
|
||||
"transcription_failed": "Phiên âm không thành công: {{message}}"
|
||||
},
|
||||
"logs": {
|
||||
"no_tauri_log": "Chưa có bản đăng nhập Tauri nào trên đĩa - khởi chạy thông qua bản dựng máy tính để bàn để tạo một bản ghi",
|
||||
"empty_frontend": "Chưa có mục nhập bảng điều khiển giao diện người dùng nào được ghi lại. Tương tác với ứng dụng — mọi bảng điều khiển.* sẽ xuất hiện ở đây.",
|
||||
"empty_tauri": "Không có nhật ký Tauri. Chỉ chạy trong vỏ máy tính để bàn.",
|
||||
"empty_backend": "Nhật ký thời gian chạy trống. Hoạt động sẽ xuất hiện ở đây khi chương trình phụ trợ ghi lại hoạt động đó."
|
||||
"empty_backend": "Nhật ký thời gian chạy trống. Hoạt động sẽ xuất hiện ở đây khi chương trình phụ trợ ghi lại hoạt động đó.",
|
||||
"title": "Nhật ký",
|
||||
"source_backend": "Phần phụ trợ",
|
||||
"source_frontend": "Giao diện người dùng",
|
||||
"source_tauri": "Tauri",
|
||||
"expand": "Mở rộng nhật ký",
|
||||
"collapse": "Thu gọn nhật ký",
|
||||
"expand_aria": "Mở rộng bảng nhật ký",
|
||||
"collapse_aria": "Thu gọn bảng nhật ký",
|
||||
"drag_resize": "Kéo để thay đổi kích thước",
|
||||
"refresh": "Làm mới",
|
||||
"refresh_aria": "Làm mới nhật ký",
|
||||
"copy_visible": "Sao chép nhật ký hiển thị",
|
||||
"copy_visible_aria": "Sao chép nhật ký hiển thị",
|
||||
"clear": "Xóa",
|
||||
"clear_aria": "Xóa nhật ký",
|
||||
"report_issue": "Báo cáo sự cố (chẩn đoán sao chép)",
|
||||
"report_issue_aria": "Báo cáo vấn đề",
|
||||
"close": "Đóng",
|
||||
"close_aria": "Đóng bảng nhật ký",
|
||||
"join_discord": "Tham gia Discord của chúng tôi",
|
||||
"join_discord_aria": "Tham gia cộng đồng Discord của chúng tôi",
|
||||
"support_project": "Hỗ trợ dự án này",
|
||||
"support_project_aria": "Hỗ trợ dự án này",
|
||||
"empty_frontend_short": "Chưa có đầu ra bảng điều khiển giao diện người dùng.",
|
||||
"empty_lines": "Không có dòng.",
|
||||
"all_clear": "✅ Tất cả đều ổn - không phát hiện vấn đề gì",
|
||||
"log_cleared": "{{source}} đã xóa nhật ký",
|
||||
"clear_failed": "Xóa không thành công: {{message}}",
|
||||
"log_copied": "Đã sao chép nhật ký _V_0__",
|
||||
"copy_failed": "Sao chép không thành công: {{message}}",
|
||||
"report_copied": "Đã sao chép báo cáo chẩn đoán — dán báo cáo đó vào vấn đề GitHub.",
|
||||
"report_failed": "Báo cáo không thành công: {{message}}"
|
||||
},
|
||||
"voice_profile": {
|
||||
"test_text": "Xin chào - đây là bài kiểm tra giọng nói này.",
|
||||
@@ -526,7 +644,17 @@
|
||||
"install_already": "{{engine}} đã được cài đặt",
|
||||
"install_ok": "{{engine}} đã cài đặt",
|
||||
"install_failed": "Cài đặt không thành công: {{message}}",
|
||||
"prep_elapsed": "{{time}} đã trôi qua"
|
||||
"prep_elapsed": "{{time}} đã trôi qua",
|
||||
"add_language": "Thêm ngôn ngữ",
|
||||
"search_languages": "Tìm kiếm ngôn ngữ…",
|
||||
"languages_selected_one": "{{count}} ngôn ngữ đã chọn",
|
||||
"languages_selected_other": "{{count}} ngôn ngữ đã chọn",
|
||||
"more_to_narrow": "+{{count}} thêm — gõ để thu hẹp",
|
||||
"no_matches": "Không có kết quả phù hợp",
|
||||
"diagnostic_copied": "Đã sao chép chẩn đoán",
|
||||
"copy_failed": "Sao chép không thành công",
|
||||
"open_docs": "Mở tài liệu",
|
||||
"copy_diagnostic": "Sao chép chẩn đoán"
|
||||
},
|
||||
"glossary": {
|
||||
"title": "Thuật ngữ",
|
||||
@@ -601,7 +729,17 @@
|
||||
"more_actions_title": "Thêm hành động",
|
||||
"speaker_pick": "Chọn…",
|
||||
"speaker_title_detected": "Loa - chọn từ được phát hiện hoặc nhập tên tùy chỉnh",
|
||||
"speaker_title_custom": "Người nói - nhập tên (không phát hiện thấy bản sao nhật ký)"
|
||||
"speaker_title_custom": "Người nói - nhập tên (không phát hiện thấy bản sao nhật ký)",
|
||||
"time_edit_title": "Bấm để chỉnh sửa thời gian bắt đầu (m:ss.s). Enter để cam kết, Esc để hủy.",
|
||||
"fit_fits": "Phù hợp",
|
||||
"fit_fits_title": "Âm thanh tốc độ tự nhiên vừa vặn bên trong khe cắm.",
|
||||
"fit_overflows": "Tràn +{{seconds}}s",
|
||||
"fit_overflows_title": "Văn bản được dịch dài hơn vị trí ban đầu {{seconds}}s. Âm thanh đã được cắt bớt cứng; rút ngắn văn bản hoặc chuyển Thời gian thành \"Căng video\".",
|
||||
"fit_stretched": "Video {{ratio}}×",
|
||||
"fit_stretched_title": "Chế độ Kéo dài video: video của phân đoạn này được giảm tốc độ xuống {{ratio}}× để phù hợp với âm thanh lồng tiếng tự nhiên.",
|
||||
"fit_compressed_title": "Âm thanh TTS chiếm {{pct}}% dung lượng khe — bị nén rất nhiều.",
|
||||
"fit_audio_title": "Âm thanh vừa vặn bên trong khe cắm.",
|
||||
"fit_ratio_title": "Âm thanh TTS chiếm {{pct}}% dung lượng khe."
|
||||
},
|
||||
"voice": {
|
||||
"personality": "Tính cách",
|
||||
@@ -696,10 +834,24 @@
|
||||
"status_running": "đang chạy",
|
||||
"status_done": "xong",
|
||||
"status_failed": "thất bại",
|
||||
"status_cancelled": "đã hủy bỏ"
|
||||
"status_cancelled": "đã hủy bỏ",
|
||||
"add_to_queue_title": "Thêm video vào hàng đợi",
|
||||
"drop_hint_text": "Thả tập tin video vào đây hoặc nhấp để duyệt",
|
||||
"drop_formats": "MP4 · MOV · MKV · WEBM",
|
||||
"files_kicker": "TẬP TIN ({{count}})",
|
||||
"file_size_mb": "{{size}} MB",
|
||||
"target_languages": "NGÔN NGỮ MỤC TIÊU",
|
||||
"voice_kicker": "GIỌNG NÓI",
|
||||
"default_option": "Mặc định",
|
||||
"clone_profiles": "Hồ sơ nhân bản",
|
||||
"presets": "cài đặt trước",
|
||||
"preserve_bg": "Giữ nguyên âm thanh nền (âm nhạc/FX)",
|
||||
"estimate": "{{videos}} video × {{langs}} lang = {{jobs}} công việc",
|
||||
"select_files_langs": "Chọn tập tin và ngôn ngữ",
|
||||
"add_to_queue": "Thêm vào hàng đợi"
|
||||
},
|
||||
"gallery": {
|
||||
"title": "Thư viện ảnh",
|
||||
"title": "OmniVoice Thư viện ảnh",
|
||||
"search_placeholder": "Tìm kiếm YouTube…",
|
||||
"all_voices": "Tất cả các giọng nói ({{count}})",
|
||||
"no_voices": "Chưa có tiếng nói nào",
|
||||
@@ -714,6 +866,14 @@
|
||||
"youtube_results": "Kết quả YouTube ({{count}})",
|
||||
"clone_profile": "Sao chép hồ sơ",
|
||||
"crop_audio": "Cắt âm thanh",
|
||||
"cat_disney": "Disney",
|
||||
"cat_anime": "Anime",
|
||||
"cat_marvel": "Marvel/DC",
|
||||
"cat_celebs": "Người nổi tiếng",
|
||||
"cat_politicians": "Chính trị gia",
|
||||
"cat_news": "Neo tin tức",
|
||||
"cat_gaming": "chơi game",
|
||||
"cat_books": "Sách/Phim",
|
||||
"subtitle": "Hàng trăm giọng nói được thiết kế sẵn — hãy chọn một giọng nói và sử dụng.",
|
||||
"zone_archetypes": "nguyên mẫu",
|
||||
"zone_imports": "Nhập khẩu của tôi",
|
||||
@@ -818,7 +978,6 @@
|
||||
"badge": "Giấy phép thương mại",
|
||||
"hero_title": "Vận chuyển giọng nói AI trong quá trình sản xuất",
|
||||
"hero_desc": "OmniVoice Studio có sẵn nguồn theo Giấy phép Nguồn Chức năng (FSL). Hầu hết người dùng có thể đánh giá, tạo nguyên mẫu và thậm chí triển khai nội bộ mà không cần thỏa thuận thương mại. Bạn chỉ cần giấy phép thương mại nếu bạn đang xây dựng một sản phẩm hoặc dịch vụ cạnh tranh hoặc nếu trường hợp sử dụng của bạn nằm ngoài ranh giới của FSL.",
|
||||
"hero_note": "Việc xây dựng một sản phẩm hoặc dịch vụ cạnh tranh hoặc triển khai trên quy mô lớn (ví dụ: cung cấp API trả tiền cho mỗi lần sử dụng) cần có giấy phép thương mại. Sắp có mức giá — hãy liên hệ trong thời gian chờ đợi.",
|
||||
"why_title": "Tại sao doanh nghiệp chọn OmniVoice",
|
||||
"pricing_title": "Định giá",
|
||||
"faq_title": "Câu hỏi thường gặp",
|
||||
@@ -838,7 +997,8 @@
|
||||
"benefit_source": "Lõi có sẵn nguồn",
|
||||
"benefit_source_desc": "Hiển thị đầy đủ vào ngăn xếp. Kiểm tra, phân nhánh và điều chỉnh theo các điều khoản cấp phép.",
|
||||
"benefit_lang": "646 ngôn ngữ",
|
||||
"benefit_lang_desc": "Phiên âm, dịch và lồng tiếng trên 646 ngôn ngữ với chất lượng ngang tầm con người."
|
||||
"benefit_lang_desc": "Phiên âm, dịch và lồng tiếng trên 646 ngôn ngữ với chất lượng ngang tầm con người.",
|
||||
"hero_note": "Việc sử dụng nội bộ — ngay cả ở quy mô lớn — đều miễn phí theo FSL; giấy phép thương mại chỉ được yêu cầu để cung cấp OmniVoice cho người khác dưới dạng sản phẩm hoặc dịch vụ cạnh tranh (API được lưu trữ hoặc trả tiền cho mỗi lần sử dụng, ứng dụng được bán lại hoặc được gắn nhãn trắng). Sắp có mức giá - hãy liên hệ trong thời gian chờ đợi."
|
||||
},
|
||||
"exportModal": {
|
||||
"export": "Xuất khẩu",
|
||||
@@ -927,7 +1087,479 @@
|
||||
"dubbing_title": "Xem lồng tiếng đang hoạt động",
|
||||
"dubbing_sync": "Phát lại được đồng bộ hóa",
|
||||
"dubbing_picker": "Hãy thử một ngôn ngữ khác:",
|
||||
"dubbing_cta": "Chạy cái này trên video của riêng bạn →"
|
||||
"dubbing_cta": "Chạy cái này trên video của riêng bạn →",
|
||||
"dubbing_loading": "Đang tải bản demo lồng tiếng…",
|
||||
"dubbing_dismiss": "Loại bỏ bản demo lồng tiếng",
|
||||
"original_tag": "nguyên bản",
|
||||
"dubbed_tag": "được mệnh danh là",
|
||||
"script_conversational": "đàm thoại",
|
||||
"script_technical": "Từ vựng kỹ thuật",
|
||||
"script_french": "Không phải tiếng Anh (tiếng Pháp)",
|
||||
"aria_pause": "Tạm dừng {{label}}",
|
||||
"aria_hear": "Nghe _V_0__",
|
||||
"aria_replay": "Phát lại {{label}} thông qua người ghi âm"
|
||||
},
|
||||
"direction": {
|
||||
"title": "Hướng cho đoạn #{{id}}",
|
||||
"desc": "Cho đường dẫn biết đường này sẽ có cảm giác như thế nào. Tiếng Anh đơn giản hoạt động hiệu quả — hệ thống ánh xạ các từ của bạn vào một phân loại ổn định (năng lượng / cảm xúc / tốc độ / sự thân mật / hình thức), sau đó phân loại phân loại đó thông qua Bản dịch điện ảnh, TTS và phù hợp với vị trí.",
|
||||
"label": "Hướng",
|
||||
"lineHint": "Dòng: \"{{text}}\"",
|
||||
"placeholder": "ví dụ: cấp bách và ngạc nhiên/ấm áp, hy vọng/thì thầm, thân mật",
|
||||
"previewParse": "Xem trước phân tích cú pháp",
|
||||
"previewFailed": "Xem trước không thành công: {{message}}",
|
||||
"clear": "Xóa",
|
||||
"cancel": "Hủy bỏ",
|
||||
"saveDirection": "Lưu hướng",
|
||||
"ttsInstruct": "TTS hướng dẫn:",
|
||||
"nothingParsed": "- (không có gì được phân tích)",
|
||||
"translateHint": "Dịch gợi ý:",
|
||||
"rateBias": "Tỷ lệ thiên vị:",
|
||||
"speedsUp": "tăng tốc",
|
||||
"slowsDown": "chậm lại",
|
||||
"taxonomyTokens": "mã thông báo phân loại"
|
||||
},
|
||||
"errors": {
|
||||
"title": "Tab này gặp trở ngại.",
|
||||
"desc": "Đừng lo lắng — phần còn lại của ứng dụng vẫn hoạt động. Bạn có thể chuyển đổi tab hoặc thử lại bên dưới.",
|
||||
"tryAgain": "Thử lại",
|
||||
"openDocs": "Mở tài liệu cho lỗi này"
|
||||
},
|
||||
"keyboard": {
|
||||
"title": "Phím tắt",
|
||||
"footer": "Nhấn <1>?</1> bất kỳ lúc nào để mở mục này.",
|
||||
"or": "hoặc",
|
||||
"nav": "Điều hướng",
|
||||
"nav_cheatsheet": "Hiển thị bảng cheat này",
|
||||
"nav_closeModal": "Đóng phương thức / hủy",
|
||||
"nav_save": "Lưu dự án / cam kết cắt",
|
||||
"segmentEditor": "Trình chỉnh sửa phân đoạn",
|
||||
"seg_split": "Tách đoạn tại con trỏ",
|
||||
"seg_merge": "Hợp nhất với phân đoạn tiếp theo",
|
||||
"seg_undo": "Hoàn tác",
|
||||
"seg_redo": "Làm lại",
|
||||
"seg_click": "Hành động chính",
|
||||
"seg_shiftClick": "Chọn phạm vi",
|
||||
"trimmer": "Tông đơ âm thanh",
|
||||
"trim_playPause": "Xem trước phát/tạm dừng",
|
||||
"trim_nudgeStart": "Đẩy tay cầm khởi động",
|
||||
"trim_nudgeEnd": "Đẩy tay cầm cuối",
|
||||
"trim_fineNudge": "tốt nũng nịu",
|
||||
"trim_coarseNudge": "Cú huých thô",
|
||||
"trim_zoomIn": "Phóng to / thu nhỏ",
|
||||
"trim_fitAll": "Phù hợp với tất cả / Lựa chọn phù hợp",
|
||||
"trim_confirm": "Xác nhận cắt",
|
||||
"dub": "lồng tiếng",
|
||||
"dub_generate": "Tạo lồng tiếng",
|
||||
"dub_sidebar": "Chuyển đổi thanh bên"
|
||||
},
|
||||
"network": {
|
||||
"sharing_on_title": "Bật chia sẻ — nhấp để biết chi tiết",
|
||||
"share_on_network": "Chia sẻ trên mạng của bạn",
|
||||
"switching": "Đang chuyển đổi…",
|
||||
"network": "Mạng",
|
||||
"local": "Địa phương",
|
||||
"share_confirm_title": "Chia sẻ trên mạng của bạn?",
|
||||
"share_confirm_hint": "Các thiết bị khác trên Wi-Fi/Ethernet của bạn sẽ có thể truy cập OmniVoice bằng mã PIN truy cập được hiển thị sau khi bật.",
|
||||
"enabling": "Đang bật…",
|
||||
"enable": "Kích hoạt",
|
||||
"shared_title": "Được chia sẻ trên mạng của bạn",
|
||||
"no_interface": "Không có giao diện mạng có thể truy cập - kết nối với Wi-Fi/Ethernet.",
|
||||
"copy_link": "Sao chép liên kết",
|
||||
"open_in_browser": "Mở trong trình duyệt",
|
||||
"qr_alt": "QR cho {{ip}}",
|
||||
"pin": "mã PIN:",
|
||||
"stop_sharing": "Dừng chia sẻ",
|
||||
"copied": "Đã sao chép",
|
||||
"enable_error": "Không thể bật chia sẻ: {{message}}",
|
||||
"disable_error": "Không thể tắt: {{message}}"
|
||||
},
|
||||
"readiness": {
|
||||
"checking_system": "Hệ thống kiểm tra…",
|
||||
"all_ready": "Tất cả các hệ thống đã sẵn sàng",
|
||||
"system_readiness": "Sự sẵn sàng của hệ thống",
|
||||
"asr_model": "Mô hình ASR",
|
||||
"loaded_ready": "Đã tải và sẵn sàng",
|
||||
"loading_first_run": "Đang tải… (việc này có thể mất 1-2 phút trong lần chạy đầu tiên)",
|
||||
"failed_to_load": "Không tải được",
|
||||
"not_loaded_yet": "Chưa được tải - sẽ tải vào lần phiên âm đầu tiên",
|
||||
"error_check_logs": "Lỗi: {{error}}. Kiểm tra nhật ký và thử khởi động lại.",
|
||||
"check_logs_restart": "Kiểm tra nhật ký để tìm lỗi tải mô hình. Hãy thử khởi động lại.",
|
||||
"llm_cinematic": "LLM (Điện ảnh)",
|
||||
"llm_configure": "Định cấu hình TRANSLATE_BASE_URL cho chất lượng bản dịch điện ảnh",
|
||||
"llm_set_env": "Đặt các biến môi trường TRANSLATE_BASE_URL và TRANSLATE_API_KEY. Hoạt động với Ollama, OpenAI, LM Studio, v.v.",
|
||||
"llm_optional": "Tùy chọn — đặt TRANSLATE_BASE_URL cho chất lượng Điện ảnh"
|
||||
},
|
||||
"license": {
|
||||
"title": "Supertonic-3 — Chấp nhận giấy phép",
|
||||
"intro": "Supertonic-3 được vận chuyển theo hai giấy phép riêng biệt. Vui lòng xem lại cả hai trước khi kích hoạt động cơ.",
|
||||
"sdk_heading": "Mã SDK · MIT",
|
||||
"sdk_desc": "SDK suy luận Python (supertonic) được MIT cấp phép. Cho phép sử dụng, bao gồm cả thương mại.",
|
||||
"read_mit": "Đọc giấy phép MIT →",
|
||||
"model_heading": "Trọng lượng mô hình · OpenRAIL-M",
|
||||
"model_desc": "Quả tạ mẫu Supertonic-3 được phát hành theo giấy phép OpenRAIL-M. Giấy phép này hạn chế sử dụng cho các mục đích không độc hại — xem giấy phép được liên kết để biết toàn bộ các hạn chế dựa trên việc sử dụng.",
|
||||
"read_openrail": "Đọc giấy phép OpenRAIL-M →",
|
||||
"footer": "Nhấp vào Chấp nhận sẽ ghi lại sự chấp nhận của bạn trong cài đặt cục bộ của OmniVoice và kích hoạt công cụ. Sự chấp nhận của bạn chỉ được lưu trữ trên máy này - không có gì được báo cáo cho Supertone Inc. hoặc bất kỳ bên thứ ba nào.",
|
||||
"saving": "Đang lưu…",
|
||||
"accept": "Chấp nhận",
|
||||
"accepted_toast": "Giấy phép Supertonic-3 được chấp nhận.",
|
||||
"accept_error": "Không ghi lại được việc chấp nhận giấy phép: {{message}}"
|
||||
},
|
||||
"voicePreview": {
|
||||
"title": "Xem trước bằng giọng nói",
|
||||
"close": "Đóng bản xem trước",
|
||||
"default_text": "Xin chào! Đây là bản xem trước về cách tôi phát âm bằng giọng nói này.",
|
||||
"default_voice": "Giọng nói mặc định",
|
||||
"clone_profiles": "Hồ sơ nhân bản",
|
||||
"designed_voices": "Giọng nói được thiết kế",
|
||||
"presets": "cài đặt trước",
|
||||
"placeholder": "Nhập nội dung nào đó để nghe…",
|
||||
"stop": "Dừng lại",
|
||||
"regenerate": "tái sinh",
|
||||
"preview": "Xem trước",
|
||||
"hint": "8 bước · xem trước nhanh"
|
||||
},
|
||||
"header": {
|
||||
"kicker_studio": "Studio",
|
||||
"kicker_library": "Thư viện",
|
||||
"kicker_preferences": "Tùy chọn",
|
||||
"kicker_licensing": "Cấp phép",
|
||||
"label_launchpad": "Bệ phóng",
|
||||
"label_clone": "Bản sao giọng nói",
|
||||
"label_design": "Thiết kế giọng nói",
|
||||
"label_dub": "Lồng tiếng",
|
||||
"label_projects": "OmniDrive",
|
||||
"label_gallery": "Thư viện ảnh",
|
||||
"label_transcriptions": "Phiên âm",
|
||||
"label_settings": "Cài đặt",
|
||||
"label_enterprise": "Giấy phép thương mại",
|
||||
"status_ready": "Sẵn sàng",
|
||||
"status_loading": "Đang tải…",
|
||||
"status_idle": "Nhàn rỗi",
|
||||
"memory_management": "Quản lý bộ nhớ",
|
||||
"flush": "Xả",
|
||||
"loaded_models": "Mô hình đã tải",
|
||||
"no_models": "Không có mô hình nào được tải",
|
||||
"unload": "Dỡ bỏ",
|
||||
"flush_caches": "Xóa bộ nhớ đệm",
|
||||
"unload_all_flush": "Dỡ bỏ tất cả + xả nước"
|
||||
},
|
||||
"sidebar": {
|
||||
"tab_drive": "Lái xe",
|
||||
"tab_history": "Lịch sử",
|
||||
"tab_exports": "Xuất khẩu",
|
||||
"save_project": "Lưu dự án lồng tiếng",
|
||||
"save_new_project": "Lưu dưới dạng dự án lồng tiếng mới",
|
||||
"dub_projects": "Dự án lồng tiếng",
|
||||
"voice_clones": "Bản sao giọng nói",
|
||||
"designed_voices": "Giọng nói được thiết kế",
|
||||
"no_dub_projects": "Không có dự án lồng tiếng nào được lưu",
|
||||
"no_dub_hint": "Tải video lên và nhấp vào Lưu để giữ tác phẩm của bạn.",
|
||||
"no_clones": "Chưa có bản sao giọng nói",
|
||||
"no_clones_hint": "Ghi hoặc tải âm thanh lên, sau đó nhấp vào Lưu dưới dạng Hồ sơ giọng nói.",
|
||||
"no_designs": "Chưa có giọng nói được thiết kế nào",
|
||||
"no_designs_hint": "Tạo giọng nói và lưu nó từ Lịch sử.",
|
||||
"history_subtitle": "Lịch sử thế hệ · Được lưu trữ trong SQLite",
|
||||
"no_history": "Không có lịch sử thế hệ",
|
||||
"no_history_hint": "Tổng hợp âm thanh hoặc lồng tiếng video — kết quả sẽ xuất hiện ở đây.",
|
||||
"clear_history": "Xóa lịch sử",
|
||||
"clear_confirm": "Xóa tất cả _V_0__ mục lịch sử? Điều này không thể hoàn tác được.",
|
||||
"history_cleared": "Đã xóa lịch sử",
|
||||
"recent_exports": "Xuất khẩu gần đây",
|
||||
"no_exports": "Không có kết quả đầu ra được tải xuống",
|
||||
"no_exports_hint": "Xuất tệp qua Tauri để xem nó được theo dõi tại đây.",
|
||||
"show_in_folder": "Hiển thị trong thư mục",
|
||||
"open": "Mở",
|
||||
"select": "chọn",
|
||||
"try_voice": "Hãy thử",
|
||||
"consistent": "nhất quán",
|
||||
"locked": "Đã khóa",
|
||||
"clone_label": "bản sao",
|
||||
"design_label": "Thiết kế",
|
||||
"dub_label": "lồng tiếng",
|
||||
"save_label": "Lưu",
|
||||
"lock_identity": "Khóa nhận dạng giọng nói",
|
||||
"in_folder": "trong {{folder}}"
|
||||
},
|
||||
"trimmer": {
|
||||
"title": "Cắt âm thanh tham chiếu",
|
||||
"decoding": "Giải mã âm thanh…",
|
||||
"meta_length": "Độ dài {{duration}} · {{sampleRate}} Hz",
|
||||
"meta_rendering": "hiển thị dạng sóng _V_0__%",
|
||||
"keyboard_hint": "cuộn = thu phóng · shift+cuộn = xoay · alt+kéo = xoay · ⏐ ⟵ ⟶ ⏐ phím điều chỉnh tay cầm",
|
||||
"zoom_in": "Phóng to (+)",
|
||||
"zoom_out": "Thu nhỏ (-)",
|
||||
"fit_all": "Phù hợp với tất cả (Nhà)",
|
||||
"fit_selection": "Lựa chọn phù hợp (Kết thúc)",
|
||||
"fit_sel_btn": "FIT SEL",
|
||||
"view_range": "Xem {{start}} → {{end}} ({{duration}})",
|
||||
"start_label": "Bắt đầu",
|
||||
"end_label": "Kết thúc",
|
||||
"length_label": "Chiều dài",
|
||||
"too_long": ">{{max}}s",
|
||||
"too_short": "quá ngắn",
|
||||
"length_ok": "được",
|
||||
"loop_preview": "Xem trước vòng lặp",
|
||||
"pause": "Tạm dừng",
|
||||
"preview_selection": "Lựa chọn xem trước",
|
||||
"play_hint": "Không gian để chơi · Nhập để xác nhận · Esc để hủy",
|
||||
"cancel": "Hủy bỏ",
|
||||
"use_trimmed": "Sử dụng cắt tỉa",
|
||||
"decode_failed": "Giải mã không thành công: {{message}}",
|
||||
"playback_failed": "Phát lại không thành công: {{message}}",
|
||||
"audio_load_failed": "Tải âm thanh không thành công",
|
||||
"unit_seconds": "s"
|
||||
},
|
||||
"casting": {
|
||||
"title": "Đúc loa",
|
||||
"auto_assign_title": "Tự động gán giọng nói từ bản sao loa được trích xuất",
|
||||
"auto_cast": "Tự động truyền",
|
||||
"all_cast": "Tất cả diễn viên",
|
||||
"segments_count": "{{count}} phân đoạn",
|
||||
"assign_voice": "Chỉ định giọng nói…",
|
||||
"from_video": "🎤 Từ video ({{name}})",
|
||||
"no_profiles": "Chưa có cấu hình giọng nói nào được lưu.",
|
||||
"preview_voice": "Xem trước giọng nói"
|
||||
},
|
||||
"checkpoint": {
|
||||
"asr_title": "Bản ghi đã sẵn sàng",
|
||||
"asr_cta": "Dịch",
|
||||
"asr_hint": "Hãy khắc phục mọi lỗi ASR ngay bây giờ — chuyển hướng chặt chẽ sẽ giúp tiết kiệm các lần thử TTS sau này.",
|
||||
"translate_title": "Bản dịch đã sẵn sàng",
|
||||
"translate_cta": "Tạo lồng tiếng",
|
||||
"translate_hint": "Đọc lướt văn bản mục tiêu. Các dòng quá dài được tăng tốc độ; bạn cũng có thể chỉnh sửa trực tiếp.",
|
||||
"done_title": "Lồng tiếng hoàn tất",
|
||||
"done_hint": "Xem lại tỷ lệ thời gian và đồng bộ hóa. Tinh chỉnh bất kỳ dòng nào và nhấn \"Regen đã thay đổi\" để làm lại một phần nhanh chóng.",
|
||||
"segment_one": "Đoạn {{count}}",
|
||||
"segment_other": "{{count}} phân đoạn",
|
||||
"dismiss_title": "Loại bỏ - sẽ không xuất hiện lại ở giai đoạn này cho đến khi tải lại"
|
||||
},
|
||||
"compare": {
|
||||
"title": "So sánh giọng nói A/B",
|
||||
"close": "So sánh chặt chẽ",
|
||||
"desc": "So sánh hai giọng nói cạnh nhau để đưa ra quyết định tuyển chọn. Ứng dụng vẫn tương tác phía sau.",
|
||||
"test_phrase": "Cụm từ kiểm tra",
|
||||
"voice_a": "Giọng nói A",
|
||||
"voice_b": "Giọng nói B",
|
||||
"select_voice": "— Chọn giọng nói —",
|
||||
"preset_suffix": "(Đặt trước)",
|
||||
"no_audio": "Chưa có âm thanh",
|
||||
"close_btn": "Đóng",
|
||||
"comparing": "So sánh…",
|
||||
"compare_btn": "So sánh",
|
||||
"preparing_voice": "Đang chuẩn bị giọng nói...",
|
||||
"generating_voice_a": "Đang tạo giọng nói và...",
|
||||
"generating_voice_b": "Đang tạo giọng nói B...",
|
||||
"comparison_complete": "So sánh hoàn tất!",
|
||||
"play_failed": "Chơi không thành công: {{message}}"
|
||||
},
|
||||
"models": {
|
||||
"hf_token_set_toast": "Bộ mã thông báo HuggingFace - cho phép tải xuống nhanh hơn",
|
||||
"hf_token_save_failed": "Không lưu được mã thông báo",
|
||||
"install_started": "Đã bắt đầu cài đặt - tiến trình trong hàng",
|
||||
"delete_confirm": "Xóa {{repoId}}? Bạn có thể cài đặt lại nó sau.",
|
||||
"delete_confirm_title": "Xóa mô hình",
|
||||
"deleted": "Đã xóa {{repoId}}",
|
||||
"reinstall_confirm": "Cài đặt lại {{repoId}}? Thao tác này sẽ xóa bản sao hiện tại và tải xuống lại.",
|
||||
"reinstall_confirm_title": "Cài đặt lại mô hình",
|
||||
"reinstalling": "Đang cài đặt lại",
|
||||
"recommended_installed": "Các mô hình được đề xuất đã được cài đặt.",
|
||||
"started_downloading_one": "Đã bắt đầu tải xuống mô hình _V_0__",
|
||||
"started_downloading_other": "Đã bắt đầu tải xuống mô hình _V_0__",
|
||||
"install_failed": "Cài đặt không thành công: {{message}}",
|
||||
"removing_cached": "Đang xóa các bản sửa đổi được lưu trong bộ nhớ đệm…",
|
||||
"resolving_metadata": "Giải quyết siêu dữ liệu repo",
|
||||
"retry_attempt": "Thử lại {{attempt}} — {{error}}",
|
||||
"connecting_hf": "Đang kết nối với HuggingFace…",
|
||||
"resolving_files_one": "Đang giải quyết tệp _V_0__…",
|
||||
"resolving_files_other": "Đang giải quyết các tệp {{count}}…",
|
||||
"resolving_files_active": "Đang giải quyết {{count}} tệp… · {{file}}",
|
||||
"measuring": "đo…",
|
||||
"files_progress": "Tệp {{done}}/{{total}}",
|
||||
"install_error": "Cài đặt không thành công: {{error}}",
|
||||
"view_on_hf": "Xem trên HuggingFace",
|
||||
"install_btn": "cài đặt",
|
||||
"reinstall_btn": "Cài đặt lại",
|
||||
"downloading": "đang tải xuống",
|
||||
"deleting": "xóa",
|
||||
"working": "đang làm việc",
|
||||
"installed": "đã cài đặt",
|
||||
"not_installed": "chưa được cài đặt",
|
||||
"required_tag": "bắt buộc",
|
||||
"delete_btn": "Xóa",
|
||||
"hf_token_btn": "Mã thông báo HF",
|
||||
"hf_set_title": "Đặt mã thông báo HuggingFace để tải xuống nhanh hơn",
|
||||
"get_token": "Nhận mã thông báo →",
|
||||
"reco_installed_for": "Gói được đề xuất đã cài đặt cho _V_0__",
|
||||
"reco_for": "Được đề xuất cho _V_0__",
|
||||
"starting": "Bắt đầu…",
|
||||
"required_size": "Bắt buộc ~{{size}} GB",
|
||||
"all_size": "Tất cả ~{{size}} GB",
|
||||
"req_tag": "yêu cầu",
|
||||
"search_placeholder": "Tìm kiếm mô hình…",
|
||||
"search_label": "Tìm kiếm mô hình",
|
||||
"no_matches": "Không có mô hình nào phù hợp với bộ lọc của bạn.",
|
||||
"sort_by": "Sắp xếp theo {{column}}",
|
||||
"column_model": "người mẫu",
|
||||
"column_role": "Vai trò",
|
||||
"column_size": "Kích thước",
|
||||
"column_status": "Trạng thái",
|
||||
"ready_badge": "Sẵn sàng",
|
||||
"loading_badge": "Đang tải…",
|
||||
"idle_badge": "Nhàn rỗi",
|
||||
"started_downloading_required_one": "Đã bắt đầu tải xuống mô hình bắt buộc _V_0__",
|
||||
"started_downloading_required_other": "Đã bắt đầu tải xuống _V_0__ các mẫu được yêu cầu"
|
||||
},
|
||||
"enterprise_faq": {
|
||||
"q_internal_tools": "Tôi có cần giấy phép cho các công cụ nội bộ không?",
|
||||
"a_internal_tools": "Việc sử dụng nội bộ của nhân viên và nhà thầu của bạn là Mục đích được phép theo FSL — không cần giấy phép. Cần có giấy phép thương mại khi bạn cung cấp OmniVoice cho người khác như một phần của sản phẩm hoặc dịch vụ cạnh tranh (bán lại, SaaS được lưu trữ, nhãn trắng).",
|
||||
"q_try_before": "Tôi có thể thử trước khi cam kết không?",
|
||||
"a_try_before": "Vâng. Ứng dụng đầy đủ được tải xuống và chạy cục bộ miễn phí để đánh giá theo FSL. Khi bạn sẵn sàng thảo luận về việc triển khai thương mại, hãy gửi email cho chúng tôi và chúng ta sẽ cùng nhau giải quyết các chi tiết.",
|
||||
"q_watermark": "Còn hình mờ thì sao?",
|
||||
"a_watermark": "Hình mờ AudioSeal vô hình được nhúng theo mặc định. Người được cấp phép thương mại có thể tắt nó trong Cài đặt → Quyền riêng tư. Sử dụng miễn phí/cá nhân luôn bao gồm hình mờ.",
|
||||
"q_apache": "Nguồn có bao giờ trở thành Apache 2.0 không?",
|
||||
"a_apache": "Vâng. Mỗi bản phát hành sẽ tự động chuyển đổi sang Giấy phép Apache, Phiên bản 2.0 vào ngày kỷ niệm thứ hai ngày xuất bản. Điều đó có nghĩa là bản phát hành hôm nay là Apache 2.0 sau hai năm nữa, chúng tôi không cần thực hiện hành động nào — FSL đảm bảo điều đó không thể hủy bỏ."
|
||||
},
|
||||
"gallery_extra": {
|
||||
"save_prompt": "Nhập tên cho cấu hình giọng nói này:",
|
||||
"saved_as_profile": "Giọng nói được lưu dưới dạng hồ sơ!",
|
||||
"save_profile_failed": "Không lưu được hồ sơ",
|
||||
"download_failed": "Tải xuống không thành công: {{message}}",
|
||||
"trim_load_failed": "Không thể tải âm thanh để cắt xén: {{message}}",
|
||||
"upload_crop_failed": "Không thể tải lên giọng nói đã bị cắt: {{message}}"
|
||||
},
|
||||
"dub_workflow": {
|
||||
"preparing_audio": "Đang chuẩn bị âm thanh…",
|
||||
"preparing_video": "Đang chuẩn bị video…",
|
||||
"extracting_audio_scenes": "Đang trích xuất âm thanh và cảnh…",
|
||||
"transcribing_audio": "Phiên âm âm thanh…",
|
||||
"transcription_complete": "Phiên âm hoàn tất",
|
||||
"upload_cancelled": "Đã hủy tải lên",
|
||||
"upload_failed": "Tải lên không thành công: {{message}}",
|
||||
"downloading_video": "Đang tải xuống video…",
|
||||
"ingested": "Đã nhập {{url}}",
|
||||
"ingest_cancelled": "Đã hủy nhập",
|
||||
"ingest_failed": "Nhập URL không thành công: {{message}}",
|
||||
"retry_cancelled": "Đã hủy thử lại",
|
||||
"transcription_failed": "Phiên âm không thành công: {{message}}",
|
||||
"import_srt_no_job": "Trước tiên hãy tải lên hoặc nhập video — không cần phải đính kèm phụ đề.",
|
||||
"imported_cues": "Đã nhập _V_0__ tín hiệu từ {{file}}",
|
||||
"skipped_malformed": "{{count}} bị bỏ qua (không đúng định dạng)",
|
||||
"dropped_overlap": "{{count}} bị rơi (chồng chéo)",
|
||||
"clamped_to_duration": "{{count}} được kẹp theo chiều dài phương tiện",
|
||||
"srt_import_failed": "Nhập SRT không thành công",
|
||||
"cleaned_one": "Đã làm sạch đoạn _V_0__",
|
||||
"cleaned_other": "Đã làm sạch _V_0__ mảnh vỡ",
|
||||
"segments_clean": "Phân khúc đã sạch",
|
||||
"cleanup_failed": "Dọn dẹp không thành công: {{message}}",
|
||||
"cinematic_no_llm": "Chất lượng điện ảnh cần có LLM — đặt TRANSLATE_BASE_URL + TRANSLATE_API_KEY (Ollama hoạt động cục bộ). Trở lại Nhanh.",
|
||||
"translate_errors": "{{errorCount}}/{{totalCount}} phân đoạn không thành công: {{firstError}}",
|
||||
"translated_segments": "Đã dịch _V_0__ phân đoạn → {{lang}}",
|
||||
"translated_cinematic_suffix": "(Điện ảnh)",
|
||||
"translation_failed": "Dịch không thành công: {{message}}",
|
||||
"regenerating": "Đang tái tạo _V_0__ phân đoạn…",
|
||||
"generating_dub": "Đang tạo lồng tiếng…",
|
||||
"generating_progress": "Đang tạo bản lồng tiếng… {{current}}/{{total}}",
|
||||
"generation_aborted": "Thế hệ bị hủy bỏ.",
|
||||
"dubbing_aborted": "Việc lồng tiếng bị hủy bỏ",
|
||||
"generation_stream_ended": "Luồng thế hệ đã kết thúc trước khi hoàn thành",
|
||||
"dub_complete": "Lồng tiếng hoàn tất",
|
||||
"stop_failed": "Không thể dừng lại",
|
||||
"save_first": "Vui lòng nhấp vào 'Tải lên & Phiên âm' trước để video được xử lý trên máy chủ trước khi lưu.",
|
||||
"project_saved": "Đã lưu dự án",
|
||||
"project_created": "Dự án đã được tạo",
|
||||
"save_failed": "Lưu không thành công: {{message}}",
|
||||
"opened_project": "Đã mở: {{name}}",
|
||||
"delete_project_confirm": "Xóa dự án này? Điều này không thể hoàn tác được.",
|
||||
"project_deleted": "Dự án đã bị xóa",
|
||||
"delete_history_confirm": "Xóa mục lịch sử này?",
|
||||
"history_deleted": "Mục lịch sử đã bị xóa",
|
||||
"restored_state": "Đã khôi phục trạng thái thế hệ trước",
|
||||
"upgrading_preview": "Đang nâng cấp _V_0__ phân đoạn chất lượng xem trước lên chất lượng đầy đủ…"
|
||||
},
|
||||
"tts_errors": {
|
||||
"enter_text": "Vui lòng nhập văn bản",
|
||||
"upload_or_select": "Tải lên âm thanh hoặc chọn cấu hình giọng nói",
|
||||
"trim_hint": "Âm thanh là {{duration}}s — cắt thành ≤{{max}}s để sao chép tốt nhất",
|
||||
"timeout": "Đã hết thời gian tạo - mô hình có thể vẫn đang được tải xuống. Kiểm tra Cài đặt → Nhật ký, sau đó thử lại.",
|
||||
"error_prefix": "Lỗi: {{message}}",
|
||||
"ignored_unsupported": "Đã bỏ qua hướng dẫn không được hỗ trợ: {{items}}",
|
||||
"ignored_duplicate": "Đã bỏ qua (danh mục đã được đặt): {{items}}"
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Chia sẻ & Truy cập từ xa",
|
||||
"help": "Hiển thị phiên bản OmniVoice đang chạy này cho các máy khác của bạn mà không cần khởi động lại. Chỉ vòng lặp lại là mặc định — không có gì được chia sẻ cho đến khi bạn bật tính năng này tại đây.",
|
||||
"local_network": "Mạng cục bộ",
|
||||
"local_help": "Chia sẻ trên Wi-Fi / Ethernet của bạn bằng mã PIN truy cập một lần. Các thiết bị khác quét mã QR hoặc mở link.",
|
||||
"ports_title": "Cổng",
|
||||
"ports_help": "Chúng được đặt thông qua các biến môi trường được đọc khi khởi động. Thay đổi cổng phụ trợ hoặc giao diện người dùng bằng cách đặt biến và khởi động lại OmniVoice.",
|
||||
"backend_port": "Cổng phụ trợ",
|
||||
"ui_port": "cổng giao diện người dùng",
|
||||
"lan_share_port": "Cổng chia sẻ mạng LAN",
|
||||
"port_error": "Nhập một cổng từ 1024 đến 65535",
|
||||
"port_saved": "Đã lưu cổng chia sẻ mạng LAN - áp dụng vào lần tiếp theo bạn bật chia sẻ",
|
||||
"port_save_failed": "Không thể lưu cổng: {{message}}",
|
||||
"saving": "Đang lưu…",
|
||||
"ports_note": "Cổng phụ trợ và giao diện người dùng áp dụng khi khởi động lại. Cổng chia sẻ mạng LAN sẽ áp dụng vào lần tiếp theo bạn bật chia sẻ.",
|
||||
"tailscale_title": "Tailscale (truy cập từ xa riêng tư)",
|
||||
"tailscale_checking": "Đang kiểm tra vảy đuôi…",
|
||||
"tailscale_absent": "Không phát hiện được vảy đuôi. Cài đặt nó để truy cập OmniVoice một cách an toàn từ mọi nơi trên mạng riêng của bạn.",
|
||||
"tailscale_install": "Cài đặt Tailscale",
|
||||
"tailscale_running": "Tailscale đang chạy. Phục vụ OmniVoice qua tailnet riêng của bạn.",
|
||||
"tailscale_not_logged_in": "Tailscale đã được cài đặt nhưng chưa đăng nhập. Trước tiên hãy bắt đầu và đăng nhập vào Tailscale.",
|
||||
"tailscale_enabled": "Đã bật phân phối theo quy mô",
|
||||
"tailscale_enable_failed": "Không thể bật Tailscale",
|
||||
"tailscale_enable_error": "Không thể bật Tailscale: {{message}}",
|
||||
"tailscale_disabled": "Đã vô hiệu hóa giao dịch theo tỷ lệ",
|
||||
"tailscale_disable_failed": "Không thể tắt Tailscale",
|
||||
"tailscale_disable_error": "Không thể tắt Tailscale: {{message}}",
|
||||
"tailscale_enabling": "Đang bật…",
|
||||
"tailscale_enable_btn": "Bật phân phát Tailscale",
|
||||
"tailscale_disabling": "Đang tắt…",
|
||||
"tailscale_disable_btn": "Dừng giao bóng Tailscale",
|
||||
"tailscale_copy": "Sao chép liên kết",
|
||||
"tailscale_open": "Mở trong trình duyệt",
|
||||
"copied": "Đã sao chép",
|
||||
"tailscale_qr_alt": "Mã QR cho URL Tailscale"
|
||||
},
|
||||
"reportBug": {
|
||||
"label": "Báo cáo lỗi",
|
||||
"title": "Mở trang Vấn đề GitHub được điền sẵn trong trình duyệt của bạn. Không có gì được gửi cho đến khi bạn nhấp vào Gửi."
|
||||
},
|
||||
"app": {
|
||||
"loading": "Đang tải…",
|
||||
"trimmed_loaded": "Đã tải âm thanh đã cắt bớt",
|
||||
"toast_exported": "Đã xuất: {{name}}",
|
||||
"toast_export_failed": "Xuất không thành công: {{message}}",
|
||||
"toast_open_folder_failed": "Không thể mở thư mục: {{message}}",
|
||||
"toast_saving": "Đang lưu {{name}}...",
|
||||
"toast_saved": "Đã lưu: {{path}}",
|
||||
"toast_save_error": "Lỗi lưu: {{message}}",
|
||||
"toast_processing": "Đang xử lý {{name}}...",
|
||||
"toast_downloaded": "Đã tải xuống _V_0__",
|
||||
"toast_download_error": "Lỗi tải xuống: _V_0__",
|
||||
"toast_upload_first": "Vui lòng nhấp vào 'Tải lên & Phiên âm' trước để video được xử lý trên máy chủ trước khi lưu.",
|
||||
"toast_save_failed": "Lưu không thành công: {{message}}",
|
||||
"toast_opened": "Đã mở: {{name}}",
|
||||
"toast_project_deleted": "Dự án đã bị xóa",
|
||||
"toast_restored_state": "Đã khôi phục trạng thái thế hệ trước",
|
||||
"toast_history_deleted": "Mục lịch sử đã bị xóa",
|
||||
"toast_flushed": "Đã xóa — RAM {{ram}}G · VRAM {{vram}}G{{unloaded}}",
|
||||
"toast_model_unloaded": "· dỡ mô hình",
|
||||
"toast_flush_failed": "Xóa không thành công: {{message}}",
|
||||
"toast_project_saved": "Đã lưu dự án",
|
||||
"toast_project_created": "Dự án đã được tạo"
|
||||
},
|
||||
"update": {
|
||||
"available": "Đã có bản cập nhật {{version}}",
|
||||
"install": "Cài đặt và khởi động lại",
|
||||
"install_hint": "Tải bản cập nhật và khởi động lại vào phiên bản mới",
|
||||
"downloading": "Đang cập nhật… {{pct}}%",
|
||||
"restart": "Khởi động lại để cập nhật",
|
||||
"busy": "Hoàn tất lồng tiếng của bạn trước — rồi cài đặt bản cập nhật.",
|
||||
"whats_new": "Có gì mới",
|
||||
"failed": "Cập nhật không thành công",
|
||||
"retry": "Thử lại",
|
||||
"dismiss": "Loại bỏ"
|
||||
},
|
||||
"archetypes": {
|
||||
"featured": "Nổi bật",
|
||||
@@ -945,5 +1577,25 @@
|
||||
"facet_accent": "Giọng",
|
||||
"facet_lang": "Ngôn ngữ",
|
||||
"facet_whisper": "Thì thầm"
|
||||
},
|
||||
"support": {
|
||||
"tab_support": "Hỗ trợ",
|
||||
"tab_license": "Giấy phép thương mại",
|
||||
"toggle_label": "Hỗ trợ hoặc giấy phép thương mại",
|
||||
"other_ways": "Những cách khác để giúp đỡ",
|
||||
"star_github": "Sao trên GitHub",
|
||||
"join_discord": "Tham gia bất hòa"
|
||||
},
|
||||
"updates": {
|
||||
"tab": "Cập nhật",
|
||||
"up_to_date": "Đã cập nhật · v{{version}}",
|
||||
"check_now": "Kiểm tra ngay",
|
||||
"releases": "Bản phát hành",
|
||||
"current": "hiện tại",
|
||||
"prerelease": "xem trước",
|
||||
"loading": "Đang tải bản phát hành…",
|
||||
"none": "Không tìm thấy bản phát hành nào",
|
||||
"load_error": "Không thể tải bản phát hành (ngoại tuyến?)",
|
||||
"retry_load": "Thử lại"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,4 @@
|
||||
{
|
||||
"update": {
|
||||
"available": "有可用更新 {{version}}",
|
||||
"install": "安装并重启",
|
||||
"install_hint": "下载更新并重启到新版本",
|
||||
"downloading": "更新中… {{pct}}%",
|
||||
"restart": "重启以更新",
|
||||
"busy": "请先完成配音,再安装更新。"
|
||||
},
|
||||
"stories": {
|
||||
"title": "故事编辑器",
|
||||
"subtitle": "多轨有声书,为每个角色分配语音",
|
||||
@@ -94,7 +86,17 @@
|
||||
"reload": "刷新界面",
|
||||
"backend": "后端",
|
||||
"frontend": "前端",
|
||||
"tauri": "Tauri"
|
||||
"tauri": "Tauri",
|
||||
"cancelOp": "取消操作",
|
||||
"dismiss": "解雇",
|
||||
"dismissStatus": "解除状态",
|
||||
"search": "搜索...",
|
||||
"no_matches": "没有匹配项",
|
||||
"recent_and_popular": "最近和热门",
|
||||
"popular_label": "热门",
|
||||
"showing_of": "显示 {{total}} 的 {{shown}}。输入搜索...",
|
||||
"yes": "是的",
|
||||
"no": "否"
|
||||
},
|
||||
"nav": {
|
||||
"launchpad": "工作台",
|
||||
@@ -105,7 +107,10 @@
|
||||
"transcripts": "转录",
|
||||
"omnidrive": "OmniDrive",
|
||||
"settings": "设置",
|
||||
"stories": "故事"
|
||||
"stories": "故事",
|
||||
"move_rail_right": "将轨道移至右侧",
|
||||
"move_rail_left": "将导轨移至左侧",
|
||||
"flip_rail": "翻转导轨侧"
|
||||
},
|
||||
"launchpad": {
|
||||
"greeting": "你好",
|
||||
@@ -127,7 +132,16 @@
|
||||
"locked": "已锁定",
|
||||
"open": "打开",
|
||||
"try_it": "试试看",
|
||||
"audio_only": "仅音频"
|
||||
"audio_only": "仅音频",
|
||||
"stories_title": "故事",
|
||||
"stories_desc": "多角色有声书——为角色分配语音,粘贴脚本,按章节导出。",
|
||||
"gallery_title": "语音库",
|
||||
"gallery_desc": "按口音、年龄和风格浏览现成的设计语音——无需设置。",
|
||||
"transcripts_title": "成绩单",
|
||||
"transcripts_desc": "将音频或视频转换为可编辑、可搜索的文本 - 涵盖 646 种语言。",
|
||||
"recent_files": "最近的文件",
|
||||
"view_all_files": "查看所有文件",
|
||||
"file": "文件"
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "提示词",
|
||||
@@ -225,7 +239,40 @@
|
||||
"ffmpeg_missing": "未找到",
|
||||
"ffmpeg_current": "当前路径",
|
||||
"ffmpeg_desc": "如果自动检测失败,可手动设置 ffmpeg 路径。",
|
||||
"ffmpeg_saved": "FFmpeg 路径已设置 — 重启后端生效。"
|
||||
"ffmpeg_saved": "FFmpeg 路径已设置 — 重启后端生效。",
|
||||
"diagnostics_copied": "已复制诊断信息 — 粘贴到您的问题报告中。",
|
||||
"updater_desktop": "更新程序仅在桌面应用程序中运行。",
|
||||
"latest_version": "您使用的是最新版本。",
|
||||
"logs_load_failed": "无法加载日志:{{message}}",
|
||||
"clear_frontend_confirm": "清除内存中的前端日志缓冲区吗?",
|
||||
"clear_frontend_title": "清除日志",
|
||||
"frontend_logs_cleared": "前端日志已清除",
|
||||
"clear_tauri_confirm": "截断 Tauri 端日志文件?操作系统将继续写入新条目。",
|
||||
"clear_tauri_title": "清除 Tauri 日志",
|
||||
"nothing_to_clear": "没有什么需要清除的 — 磁盘上还没有 Tauri 日志文件。",
|
||||
"cleared_tauri_one": "已清除 {{count}} Tauri 日志文件",
|
||||
"cleared_tauri_other": "已清除 {{count}} Tauri 日志文件",
|
||||
"clear_tauri_failed": "无法清除 Tauri 日志:{{message}}",
|
||||
"clear_backend_confirm": "清除后端运行时+崩溃日志?此操作无法撤消。",
|
||||
"clear_backend_title": "清除日志",
|
||||
"backend_logs_cleared": "后台日志清除",
|
||||
"clear_backend_failed": "清除日志失败",
|
||||
"copy_failed": "复制失败:{{message}}",
|
||||
"update_check_failed": "更新检查失败:{{message}}",
|
||||
"save_failed": "保存失败:{{message}}",
|
||||
"clear_failed": "清除失败:{{message}}",
|
||||
"engine_switched": "{{family}} → {{engine}}",
|
||||
"channel_set_failed": "无法设置频道:{{message}}",
|
||||
"updater_downloading": "正在下载 {{version}}...",
|
||||
"updater_installed": "已安装 - 重新启动。",
|
||||
"updater_available_title": "可用更新",
|
||||
"updater_available_body": "版本 {{version}} 可用。\n\n{{notes}}\n\n立即下载并安装?",
|
||||
"updater_notes_fallback": "请参阅 GitHub 上的发行说明。",
|
||||
"shortcut_load_failed": "无法加载快捷方式:{{message}}",
|
||||
"shortcut_set": "听写快捷键设置为 {{shortcut}}",
|
||||
"shortcut_register_failed": "无法注册:{{message}}",
|
||||
"shortcut_reset": "重置为默认值",
|
||||
"shortcut_reset_failed": "重置失败:{{message}}"
|
||||
},
|
||||
"about": {
|
||||
"app": "应用",
|
||||
@@ -247,11 +294,6 @@
|
||||
"outputs": "输出目录",
|
||||
"crash_log": "崩溃日志",
|
||||
"update_endpoint": "更新地址",
|
||||
"update_channel": "更新通道",
|
||||
"channel_stable": "稳定版",
|
||||
"channel_preview": "预览版",
|
||||
"channel_set": "更新通道已设置为 {{channel}}",
|
||||
"channel_preview_hint": "预览版跟踪最新的 main 构建——功能更新,但测试较少。若稳定版更新,则回退到稳定版。",
|
||||
"yes": "是",
|
||||
"no": "否",
|
||||
"web_preview": "网页预览",
|
||||
@@ -260,7 +302,12 @@
|
||||
"copy_diagnostics": "复制诊断信息",
|
||||
"github": "GitHub 仓库",
|
||||
"model_card": "模型卡片",
|
||||
"commercial_license": "商业许可"
|
||||
"commercial_license": "商业许可",
|
||||
"update_channel": "更新通道",
|
||||
"channel_stable": "稳定版",
|
||||
"channel_preview": "预览版",
|
||||
"channel_set": "更新通道已设置为 {{channel}}",
|
||||
"channel_preview_hint": "预览版跟踪最新的 main 构建——功能更新,但测试较少。若稳定版更新,则回退到稳定版。"
|
||||
},
|
||||
"privacy": {
|
||||
"desc": "所有数据都在<1>本机</1>运行。您的音频、视频和转录内容不会离开您的电脑,除非您显式使用在线翻译器(Google、DeepL 等)或推送到 HuggingFace。",
|
||||
@@ -316,7 +363,30 @@
|
||||
"unavailable": "不可用",
|
||||
"use": "使用",
|
||||
"loading": "加载引擎中…",
|
||||
"refresh": "刷新"
|
||||
"refresh": "刷新",
|
||||
"matrixTitle": "发动机兼容性矩阵",
|
||||
"loadFailed": "无法加载引擎:{{message}}",
|
||||
"couldNotLoad": "无法加载引擎:{{message}}",
|
||||
"retry": "重试",
|
||||
"activeEngine": "活动 {{family}}:{{engine}}",
|
||||
"engineCompatLabel": "{{family}} 引擎兼容性",
|
||||
"active": "活跃的",
|
||||
"whyUnavailable": "为什么不可用?",
|
||||
"lastError": "最后一个错误:{{error}}",
|
||||
"installedAndReady": "安装并准备就绪",
|
||||
"notInstalled": "未安装",
|
||||
"available": "可用",
|
||||
"subprocessTitle": "在自己的子进程+venv中运行",
|
||||
"inProcessTitle": "在 OmniVoice Python 进程中运行",
|
||||
"testEngine": "测试引擎",
|
||||
"testing": "测试...",
|
||||
"recheck": "重新检查",
|
||||
"rechecking": "重新检查…",
|
||||
"latencyMs": "{{ms}} 毫秒",
|
||||
"failed": "失败了",
|
||||
"acceptLicense": "接受许可",
|
||||
"noBackends": "没有注册后端。",
|
||||
"switch_failed": "切换引擎失败"
|
||||
},
|
||||
"capture": {
|
||||
"desc": "全局热键仅在桌面应用中有效。网页界面使用页面内快捷键 <1>Ctrl+Shift+Space</1>(窗口聚焦时可用)。",
|
||||
@@ -328,13 +398,55 @@
|
||||
"record_shortcut": "录制快捷键",
|
||||
"recording": "录制中…",
|
||||
"save": "保存",
|
||||
"reset_default": "恢复默认"
|
||||
"reset_default": "恢复默认",
|
||||
"listening_label": "正在听……",
|
||||
"transcribing_label": "正在抄写…",
|
||||
"pasted": "粘贴的",
|
||||
"no_speech": "未检测到语音",
|
||||
"mic_denied": "麦克风访问被拒绝",
|
||||
"mic_denied_toast": "麦克风访问被拒绝。 {{hint}}",
|
||||
"mic_hint_mac": "macOS:打开系统设置 → 隐私和安全 → 麦克风并启用 OmniVoice。",
|
||||
"mic_hint_windows": "Windows:打开设置 → 隐私和安全 → 麦克风并允许 OmniVoice。",
|
||||
"mic_hint_linux": "Linux:检查您的用户是否在音频组中并且 WebView 是否具有麦克风访问权限。",
|
||||
"transcription_failed": "转录失败:{{message}}"
|
||||
},
|
||||
"logs": {
|
||||
"no_tauri_log": "磁盘上尚无 Tauri 日志 — 请通过桌面构建启动以生成",
|
||||
"empty_frontend": "尚未捕获到前端控制台日志。与应用交互后,所有 console.* 将显示在此处。",
|
||||
"empty_tauri": "无 Tauri 日志可用。仅在桌面 Shell 中运行。",
|
||||
"empty_backend": "运行时日志为空。后端记录活动后将显示在此处。"
|
||||
"empty_backend": "运行时日志为空。后端记录活动后将显示在此处。",
|
||||
"title": "日志",
|
||||
"source_backend": "后端",
|
||||
"source_frontend": "前端",
|
||||
"source_tauri": "金牛座",
|
||||
"expand": "展开日志",
|
||||
"collapse": "折叠日志",
|
||||
"expand_aria": "展开日志面板",
|
||||
"collapse_aria": "折叠日志面板",
|
||||
"drag_resize": "拖动以调整大小",
|
||||
"refresh": "刷新",
|
||||
"refresh_aria": "刷新日志",
|
||||
"copy_visible": "复制可见日志",
|
||||
"copy_visible_aria": "复制可见日志",
|
||||
"clear": "清除",
|
||||
"clear_aria": "清除日志",
|
||||
"report_issue": "报告问题(复制诊断)",
|
||||
"report_issue_aria": "报告问题",
|
||||
"close": "关闭",
|
||||
"close_aria": "关闭日志面板",
|
||||
"join_discord": "加入我们的不和谐",
|
||||
"join_discord_aria": "加入我们的 Discord 社区",
|
||||
"support_project": "支持这个项目",
|
||||
"support_project_aria": "支持这个项目",
|
||||
"empty_frontend_short": "还没有前端控制台输出。",
|
||||
"empty_lines": "没有线路。",
|
||||
"all_clear": "✅ 一切正常 — 未检测到任何问题",
|
||||
"log_cleared": "{{source}} 日志已清除",
|
||||
"clear_failed": "清除失败:{{message}}",
|
||||
"log_copied": "已复制 {{source}} 日志",
|
||||
"copy_failed": "复制失败:{{message}}",
|
||||
"report_copied": "已复制诊断报告 — 将其粘贴到 GitHub 问题中。",
|
||||
"report_failed": "报告失败:{{message}}"
|
||||
},
|
||||
"voice_profile": {
|
||||
"test_text": "你好 — 这是对该声音的测试。",
|
||||
@@ -502,7 +614,17 @@
|
||||
"install_already": "{{engine}} 已经安装",
|
||||
"install_ok": "{{engine}} 已安装",
|
||||
"install_failed": "安装失败:{{message}}",
|
||||
"prep_elapsed": "已用 {{time}}"
|
||||
"prep_elapsed": "已用 {{time}}",
|
||||
"add_language": "添加语言",
|
||||
"search_languages": "搜索语言...",
|
||||
"languages_selected_one": "已选择 {{count}} 语言",
|
||||
"languages_selected_other": "已选择 {{count}} 种语言",
|
||||
"more_to_narrow": "+{{count}} more — 键入缩小范围",
|
||||
"no_matches": "没有匹配项",
|
||||
"diagnostic_copied": "诊断复制",
|
||||
"copy_failed": "复制失败",
|
||||
"open_docs": "打开文档",
|
||||
"copy_diagnostic": "复制诊断"
|
||||
},
|
||||
"glossary": {
|
||||
"title": "术语表",
|
||||
@@ -577,7 +699,17 @@
|
||||
"more_actions_title": "更多操作",
|
||||
"speaker_pick": "选择…",
|
||||
"speaker_title_detected": "说话人 — 从检测到的列表中选择,或输入自定义名称",
|
||||
"speaker_title_custom": "说话人 — 输入名称(未检测到分离克隆)"
|
||||
"speaker_title_custom": "说话人 — 输入名称(未检测到分离克隆)",
|
||||
"time_edit_title": "单击可编辑开始时间 (m:ss.s)。 Enter 提交,Esc 取消。",
|
||||
"fit_fits": "适合",
|
||||
"fit_fits_title": "自然速率音频适合插槽内。",
|
||||
"fit_overflows": "溢出 +{{seconds}}s",
|
||||
"fit_overflows_title": "翻译后的文本比原始文本长 {{seconds}}s。音频经过严格修剪;缩短文本或将计时切换为“拉伸视频”。",
|
||||
"fit_stretched": "视频{{ratio}}×",
|
||||
"fit_stretched_title": "拉伸视频模式:该片段的视频速度减慢至 {{ratio}}× 以适应自然的配音音频。",
|
||||
"fit_compressed_title": "TTS 音频占插槽的 {{pct}}% — 被严重压缩。",
|
||||
"fit_audio_title": "音频适合插槽内。",
|
||||
"fit_ratio_title": "TTS 音频占插槽的 {{pct}}%。"
|
||||
},
|
||||
"voice": {
|
||||
"personality": "风格",
|
||||
@@ -672,10 +804,24 @@
|
||||
"status_running": "运行中",
|
||||
"status_done": "已完成",
|
||||
"status_failed": "失败",
|
||||
"status_cancelled": "已取消"
|
||||
"status_cancelled": "已取消",
|
||||
"add_to_queue_title": "将视频添加到队列",
|
||||
"drop_hint_text": "将视频文件拖放到此处或单击浏览",
|
||||
"drop_formats": "MP4·MOV·MKV·WEBM",
|
||||
"files_kicker": "文件 ({{count}})",
|
||||
"file_size_mb": "{{size}} MB",
|
||||
"target_languages": "目标语言",
|
||||
"voice_kicker": "语音",
|
||||
"default_option": "默认",
|
||||
"clone_profiles": "克隆配置文件",
|
||||
"presets": "预设",
|
||||
"preserve_bg": "保留背景音频(音乐/FX)",
|
||||
"estimate": "{{videos}} 视频 × {{langs}} 语言 = {{jobs}} 作业",
|
||||
"select_files_langs": "选择文件和语言",
|
||||
"add_to_queue": "添加到队列"
|
||||
},
|
||||
"gallery": {
|
||||
"title": "音色库",
|
||||
"title": "OmniVoice 音色库",
|
||||
"search_placeholder": "搜索 YouTube…",
|
||||
"all_voices": "全部声音({{count}})",
|
||||
"no_voices": "暂无声音",
|
||||
@@ -690,6 +836,14 @@
|
||||
"youtube_results": "YouTube 搜索结果({{count}})",
|
||||
"clone_profile": "克隆配置",
|
||||
"crop_audio": "裁剪音频",
|
||||
"cat_disney": "迪士尼",
|
||||
"cat_anime": "动漫",
|
||||
"cat_marvel": "漫威/DC",
|
||||
"cat_celebs": "名人",
|
||||
"cat_politicians": "政治家",
|
||||
"cat_news": "新闻主播",
|
||||
"cat_gaming": "游戏",
|
||||
"cat_books": "书籍/电影",
|
||||
"subtitle": "Hundreds of ready-made designed voices — pick one and go.",
|
||||
"zone_archetypes": "原型",
|
||||
"zone_imports": "My Imports",
|
||||
@@ -794,7 +948,6 @@
|
||||
"badge": "商业许可",
|
||||
"hero_title": "在生产环境中部署 AI 语音",
|
||||
"hero_desc": "OmniVoice Studio 采用功能性源代码许可(FSL)。大多数用户可以在无需商业协议的情况下评估、原型开发甚至内部部署。只有在构建竞争产品或服务,或您的使用场景超出 FSL 范围时,才需要商业许可。",
|
||||
"hero_note": "构建竞争产品或服务,或大规模部署(例如提供按量付费 API)需要商业许可。定价层级即将推出 — 在此期间请与我们联系。",
|
||||
"why_title": "为什么企业选择 OmniVoice",
|
||||
"pricing_title": "定价",
|
||||
"faq_title": "常见问题",
|
||||
@@ -814,7 +967,8 @@
|
||||
"benefit_source": "源码可用",
|
||||
"benefit_source_desc": "完全可见的技术栈。在许可条款内审计、分叉和适配。",
|
||||
"benefit_lang": "646 种语言",
|
||||
"benefit_lang_desc": "以人类级质量转录、翻译和配音 646 种语言。"
|
||||
"benefit_lang_desc": "以人类级质量转录、翻译和配音 646 种语言。",
|
||||
"hero_note": "根据 FSL,内部使用(即使是大规模使用)也是免费的;仅需要商业许可证才能将 OmniVoice 作为竞争产品或服务(托管或按使用付费的 API、转售或白标应用程序)提供给其他人。定价等级即将推出 - 请同时联系我们。"
|
||||
},
|
||||
"exportModal": {
|
||||
"export": "导出",
|
||||
@@ -903,7 +1057,17 @@
|
||||
"dubbing_title": "查看配音效果",
|
||||
"dubbing_sync": "同步播放",
|
||||
"dubbing_picker": "试试其他语言:",
|
||||
"dubbing_cta": "在你自己的视频上运行 →"
|
||||
"dubbing_cta": "在你自己的视频上运行 →",
|
||||
"dubbing_loading": "正在加载配音演示...",
|
||||
"dubbing_dismiss": "关闭配音演示",
|
||||
"original_tag": "原创",
|
||||
"dubbed_tag": "配音",
|
||||
"script_conversational": "会话型",
|
||||
"script_technical": "技术词汇",
|
||||
"script_french": "非英语(法语)",
|
||||
"aria_pause": "暂停{{label}}",
|
||||
"aria_hear": "听到{{label}}",
|
||||
"aria_replay": "通过转录器重播 {{label}}"
|
||||
},
|
||||
"bootstrap": {
|
||||
"title": "OmniVoice Studio",
|
||||
@@ -927,7 +1091,475 @@
|
||||
"waiting_output": "正在等待输出…",
|
||||
"auto_detect": "自动检测",
|
||||
"suggest_lang": "切换到中文?",
|
||||
"select_lang": "语言:"
|
||||
"select_lang": "语言:",
|
||||
"region_global": "全球(直接)",
|
||||
"region_china": "中国(镜子)",
|
||||
"region_russia": "俄罗斯(镜子)",
|
||||
"region_restricted": "受限(镜像)",
|
||||
"unknown_error": "未知错误",
|
||||
"retrying": "正在重试..."
|
||||
},
|
||||
"direction": {
|
||||
"title": "段 #{{id}} 的方向",
|
||||
"desc": "告诉管道这条线的感觉如何。简单的英语有效 — 系统将您的单词映射到稳定的分类法(能量/情感/节奏/亲密/正式),然后通过电影翻译、TTS 和插槽配合将分类法串联起来。",
|
||||
"label": "方向",
|
||||
"lineHint": "行:“{{text}}”",
|
||||
"placeholder": "例如紧急而惊讶/温暖、充满希望/低声、亲密",
|
||||
"previewParse": "预览解析",
|
||||
"previewFailed": "预览失败:{{message}}",
|
||||
"clear": "清除",
|
||||
"cancel": "取消",
|
||||
"saveDirection": "保存方向",
|
||||
"ttsInstruct": "TTS 指示:",
|
||||
"nothingParsed": "—(没有解析)",
|
||||
"translateHint": "翻译提示:",
|
||||
"rateBias": "率偏差:",
|
||||
"speedsUp": "加速",
|
||||
"slowsDown": "放慢速度",
|
||||
"taxonomyTokens": "分类标记"
|
||||
},
|
||||
"errors": {
|
||||
"title": "这个选项卡遇到了障碍。",
|
||||
"desc": "别担心——应用程序的其余部分仍然有效。您可以切换选项卡,或在下面重试。",
|
||||
"tryAgain": "再试一次",
|
||||
"openDocs": "打开此错误的文档"
|
||||
},
|
||||
"keyboard": {
|
||||
"title": "键盘快捷键",
|
||||
"footer": "随时按 <1>?</1> 打开此文件。",
|
||||
"or": "或",
|
||||
"nav": "导航",
|
||||
"nav_cheatsheet": "显示此备忘单",
|
||||
"nav_closeModal": "关闭模式/取消",
|
||||
"nav_save": "保存项目/提交修剪",
|
||||
"segmentEditor": "段编辑器",
|
||||
"seg_split": "在光标处分割段",
|
||||
"seg_merge": "与下一个片段合并",
|
||||
"seg_undo": "撤消",
|
||||
"seg_redo": "重做",
|
||||
"seg_click": "主要行动",
|
||||
"seg_shiftClick": "范围选择",
|
||||
"trimmer": "音频修剪器",
|
||||
"trim_playPause": "预览播放/暂停",
|
||||
"trim_nudgeStart": "轻推启动手柄",
|
||||
"trim_nudgeEnd": "轻推末端手柄",
|
||||
"trim_fineNudge": "精细推动",
|
||||
"trim_coarseNudge": "粗推",
|
||||
"trim_zoomIn": "放大/缩小",
|
||||
"trim_fitAll": "适合全部/适合选择",
|
||||
"trim_confirm": "确认修剪",
|
||||
"dub": "配音",
|
||||
"dub_generate": "生成配音",
|
||||
"dub_sidebar": "切换侧边栏"
|
||||
},
|
||||
"network": {
|
||||
"sharing_on_title": "分享于——点击查看详情",
|
||||
"share_on_network": "在您的网络上分享",
|
||||
"switching": "切换…",
|
||||
"network": "网络",
|
||||
"local": "本地",
|
||||
"share_confirm_title": "在您的网络上分享?",
|
||||
"share_confirm_hint": "Wi-Fi/以太网上的其他设备将能够使用打开后显示的访问 PIN 码来访问 OmniVoice。",
|
||||
"enabling": "启用...",
|
||||
"enable": "启用",
|
||||
"shared_title": "在您的网络上共享",
|
||||
"no_interface": "没有可访问的网络接口 - 连接到 Wi-Fi/以太网。",
|
||||
"copy_link": "复制链接",
|
||||
"open_in_browser": "在浏览器中打开",
|
||||
"qr_alt": "{{ip}} 的二维码",
|
||||
"pin": "密码:",
|
||||
"stop_sharing": "停止分享",
|
||||
"copied": "已复制",
|
||||
"enable_error": "无法启用共享:{{message}}",
|
||||
"disable_error": "无法禁用:{{message}}"
|
||||
},
|
||||
"readiness": {
|
||||
"checking_system": "检查系统...",
|
||||
"all_ready": "所有系统准备就绪",
|
||||
"system_readiness": "系统准备情况",
|
||||
"asr_model": "ASR模型",
|
||||
"loaded_ready": "已加载并准备就绪",
|
||||
"loading_first_run": "正在加载...(第一次运行可能需要 1-2 分钟)",
|
||||
"failed_to_load": "加载失败",
|
||||
"not_loaded_yet": "尚未加载 - 将在第一次转录时加载",
|
||||
"error_check_logs": "错误:{{error}}。检查日志并尝试重新启动。",
|
||||
"check_logs_restart": "检查日志中是否有模型加载错误。尝试重新启动。",
|
||||
"llm_cinematic": "法学硕士(电影)",
|
||||
"llm_configure": "配置 TRANSLATE_BASE_URL 以获得电影级翻译质量",
|
||||
"llm_set_env": "设置 TRANSLATE_BASE_URL 和 TRANSLATE_API_KEY 环境变量。可与 Ollama、OpenAI、LM Studio 等配合使用。",
|
||||
"llm_optional": "可选 — 设置 TRANSLATE_BASE_URL 以获得电影质量"
|
||||
},
|
||||
"license": {
|
||||
"title": "Supertonic-3 — 许可证接受",
|
||||
"intro": "Supertonic-3 拥有两个不同的许可证。请在启用引擎之前检查两者。",
|
||||
"sdk_heading": "SDK代码·麻省理工学院",
|
||||
"sdk_desc": "Python 推理 SDK (supertonic) 已获得 MIT 许可。允许使用,包括商业用途。",
|
||||
"read_mit": "阅读 MIT 许可证 →",
|
||||
"model_heading": "模型重量 · OpenRAIL-M",
|
||||
"model_desc": "Supertonic-3 模型权重在 OpenRAIL-M 许可证下发布。该许可证限制用于非恶意目的 - 请参阅链接许可证以了解全套基于使用的限制。",
|
||||
"read_openrail": "阅读 OpenRAIL-M 许可证 →",
|
||||
"footer": "单击“接受”会在 OmniVoice 的本地设置中记录您的接受情况并启用引擎。您的接受仅存储在本机上 — 不会向 Supertone Inc. 或任何第三方报告任何内容。",
|
||||
"saving": "正在保存...",
|
||||
"accept": "接受",
|
||||
"accepted_toast": "已接受 Supertonic-3 许可证。",
|
||||
"accept_error": "未能记录许可证接受情况:{{message}}"
|
||||
},
|
||||
"voicePreview": {
|
||||
"title": "语音预览",
|
||||
"close": "关闭预览",
|
||||
"default_text": "你好!这是我的声音的预览。",
|
||||
"default_voice": "默认语音",
|
||||
"clone_profiles": "克隆配置文件",
|
||||
"designed_voices": "设计的声音",
|
||||
"presets": "预设",
|
||||
"placeholder": "输入要听的内容...",
|
||||
"stop": "停止",
|
||||
"regenerate": "再生",
|
||||
"preview": "预览",
|
||||
"hint": "8步·快速预览"
|
||||
},
|
||||
"header": {
|
||||
"kicker_studio": "工作室",
|
||||
"kicker_library": "图书馆",
|
||||
"kicker_preferences": "偏好设置",
|
||||
"kicker_licensing": "许可",
|
||||
"label_launchpad": "发射台",
|
||||
"label_clone": "语音克隆",
|
||||
"label_design": "声音设计",
|
||||
"label_dub": "配音",
|
||||
"label_projects": "全方位驱动器",
|
||||
"label_gallery": "画廊",
|
||||
"label_transcriptions": "转录",
|
||||
"label_settings": "设置",
|
||||
"label_enterprise": "商业许可",
|
||||
"status_ready": "准备好",
|
||||
"status_loading": "加载中...",
|
||||
"status_idle": "空闲",
|
||||
"memory_management": "内存管理",
|
||||
"flush": "冲洗",
|
||||
"loaded_models": "加载模型",
|
||||
"no_models": "没有加载模型",
|
||||
"unload": "卸载",
|
||||
"flush_caches": "刷新缓存",
|
||||
"unload_all_flush": "全部卸载+冲洗"
|
||||
},
|
||||
"sidebar": {
|
||||
"tab_drive": "驱动器",
|
||||
"tab_history": "历史",
|
||||
"tab_exports": "出口",
|
||||
"save_project": "保存配音项目",
|
||||
"save_new_project": "另存为新配音项目",
|
||||
"dub_projects": "配音项目",
|
||||
"voice_clones": "语音克隆",
|
||||
"designed_voices": "设计的声音",
|
||||
"no_dub_projects": "没有保存的配音项目",
|
||||
"no_dub_hint": "上传视频并单击“保存”以保留您的作品。",
|
||||
"no_clones": "还没有声音克隆",
|
||||
"no_clones_hint": "录制或上传音频,然后单击“另存为语音配置文件”。",
|
||||
"no_designs": "还没有设计声音",
|
||||
"no_designs_hint": "生成声音并将其从历史记录中保存。",
|
||||
"history_subtitle": "生成历史 · 存储在 SQLite 中",
|
||||
"no_history": "无世代历史",
|
||||
"no_history_hint": "合成音频或配音视频 - 结果将显示在此处。",
|
||||
"clear_history": "清除历史记录",
|
||||
"clear_confirm": "清除所有 {{count}} 历史记录项目吗?此操作无法撤消。",
|
||||
"history_cleared": "历史记录已清除",
|
||||
"recent_exports": "最近的出口",
|
||||
"no_exports": "没有下载的输出",
|
||||
"no_exports_hint": "通过 Tauri 导出文件以在此处查看其跟踪情况。",
|
||||
"show_in_folder": "在文件夹中显示",
|
||||
"open": "打开",
|
||||
"select": "选择",
|
||||
"try_voice": "尝试一下",
|
||||
"consistent": "一致的",
|
||||
"locked": "锁定",
|
||||
"clone_label": "克隆",
|
||||
"design_label": "设计",
|
||||
"dub_label": "配音",
|
||||
"save_label": "保存",
|
||||
"lock_identity": "锁定语音身份",
|
||||
"in_folder": "在 {{folder}} 中"
|
||||
},
|
||||
"trimmer": {
|
||||
"title": "修剪参考音频",
|
||||
"decoding": "解码音频...",
|
||||
"meta_length": "长度 {{duration}} · {{sampleRate}} Hz",
|
||||
"meta_rendering": "渲染波形{{percent}}%",
|
||||
"keyboard_hint": "滚动 = 缩放 · Shift+滚动 = 平移 · alt+拖动 = 平移 · ⏐ ⟵ ⟶ ⏐ 键调整手柄",
|
||||
"zoom_in": "放大 (+)",
|
||||
"zoom_out": "缩小 (-)",
|
||||
"fit_all": "适合所有人(首页)",
|
||||
"fit_selection": "适合选择(完)",
|
||||
"fit_sel_btn": "适合选择",
|
||||
"view_range": "查看 {{start}} → {{end}} ({{duration}})",
|
||||
"start_label": "开始",
|
||||
"end_label": "结束",
|
||||
"length_label": "长度",
|
||||
"too_long": ">{{max}}s",
|
||||
"too_short": "太短",
|
||||
"length_ok": "好的",
|
||||
"loop_preview": "循环预览",
|
||||
"pause": "暂停",
|
||||
"preview_selection": "预览选择",
|
||||
"play_hint": "空格键播放 · Enter 确认 · Esc 取消",
|
||||
"cancel": "取消",
|
||||
"use_trimmed": "使用修剪过的",
|
||||
"decode_failed": "解码失败:{{message}}",
|
||||
"playback_failed": "播放失败:{{message}}",
|
||||
"audio_load_failed": "音频加载失败",
|
||||
"unit_seconds": "s"
|
||||
},
|
||||
"casting": {
|
||||
"title": "扬声器选角",
|
||||
"auto_assign_title": "自动分配提取的说话者克隆的声音",
|
||||
"auto_cast": "自动施法",
|
||||
"all_cast": "所有演员",
|
||||
"segments_count": "{{count}} 段",
|
||||
"assign_voice": "指定语音...",
|
||||
"from_video": "🎤 来自视频 ({{name}})",
|
||||
"no_profiles": "尚未保存语音配置文件。",
|
||||
"preview_voice": "预览语音"
|
||||
},
|
||||
"checkpoint": {
|
||||
"asr_title": "成绩单已准备好",
|
||||
"asr_cta": "翻译",
|
||||
"asr_hint": "立即修复所有 ASR 错误 - 严格的措辞可以节省以后的 TTS 尝试。",
|
||||
"translate_title": "翻译准备就绪",
|
||||
"translate_cta": "生成配音",
|
||||
"translate_hint": "浏览目标文本。超长线路速度得到提升;您也可以直接编辑。",
|
||||
"done_title": "配音完成",
|
||||
"done_hint": "检查时序和同步比率。调整任何线条并点击“Regen Changed”即可快速部分重做。",
|
||||
"segment_one": "{{count}} 段",
|
||||
"segment_other": "{{count}} 段",
|
||||
"dismiss_title": "关闭 — 在重新加载之前不会再次出现在该阶段"
|
||||
},
|
||||
"compare": {
|
||||
"title": "A/B 语音比较",
|
||||
"close": "近距离比较",
|
||||
"desc": "并排比较两个声音来做出选角决定。应用程序保持互动。",
|
||||
"test_phrase": "测试短语",
|
||||
"voice_a": "声音A",
|
||||
"voice_b": "声音B",
|
||||
"select_voice": "— 选择语音 —",
|
||||
"preset_suffix": "(预设)",
|
||||
"no_audio": "还没有音频",
|
||||
"close_btn": "关闭",
|
||||
"comparing": "比较……",
|
||||
"compare_btn": "比较",
|
||||
"preparing_voice": "正在准备语音...",
|
||||
"generating_voice_a": "生成语音...",
|
||||
"generating_voice_b": "生成语音 B...",
|
||||
"comparison_complete": "对比完毕!",
|
||||
"play_failed": "播放失败:{{message}}"
|
||||
},
|
||||
"models": {
|
||||
"hf_token_set_toast": "HuggingFace 令牌集 — 启用更快的下载",
|
||||
"hf_token_save_failed": "保存令牌失败",
|
||||
"install_started": "安装已开始 — 行中的进度",
|
||||
"delete_confirm": "删除{{repoId}}?您可以稍后重新安装。",
|
||||
"delete_confirm_title": "删除模型",
|
||||
"deleted": "已删除 {{repoId}}",
|
||||
"reinstall_confirm": "重新安装{{repoId}}?这将删除当前副本并再次下载。",
|
||||
"reinstall_confirm_title": "重新安装模型",
|
||||
"reinstalling": "重新安装",
|
||||
"recommended_installed": "推荐型号已安装。",
|
||||
"started_downloading_one": "开始下载 {{count}} 模型",
|
||||
"started_downloading_other": "开始下载 {{count}} 模型",
|
||||
"install_failed": "安装失败:{{message}}",
|
||||
"removing_cached": "正在删除缓存的修订版本...",
|
||||
"resolving_metadata": "解析存储库元数据",
|
||||
"retry_attempt": "重试尝试 {{attempt}} — {{error}}",
|
||||
"connecting_hf": "正在连接到 HuggingFace...",
|
||||
"resolving_files_one": "正在解析 {{count}} 文件...",
|
||||
"resolving_files_other": "正在解析 {{count}} 文件...",
|
||||
"resolving_files_active": "正在解析 {{count}} 文件... · {{file}}",
|
||||
"measuring": "测量…",
|
||||
"files_progress": "{{done}}/{{total}} 文件",
|
||||
"install_error": "安装失败:{{error}}",
|
||||
"view_on_hf": "在 HuggingFace 上查看",
|
||||
"install_btn": "安装",
|
||||
"reinstall_btn": "重新安装",
|
||||
"downloading": "下载",
|
||||
"deleting": "删除",
|
||||
"working": "工作",
|
||||
"installed": "已安装",
|
||||
"not_installed": "未安装",
|
||||
"required_tag": "必填",
|
||||
"delete_btn": "删除",
|
||||
"hf_token_btn": "HF代币",
|
||||
"hf_set_title": "设置 HuggingFace 令牌以加快下载速度",
|
||||
"get_token": "获取代币 →",
|
||||
"reco_installed_for": "为 {{device}} 安装的推荐捆绑包",
|
||||
"reco_for": "推荐用于 {{device}}",
|
||||
"starting": "开始…",
|
||||
"required_size": "需要 ~{{size}} GB",
|
||||
"all_size": "全部 ~{{size}} GB",
|
||||
"req_tag": "要求",
|
||||
"search_placeholder": "搜索型号...",
|
||||
"search_label": "搜索型号",
|
||||
"no_matches": "没有型号与您的过滤器匹配。",
|
||||
"sort_by": "按 {{column}} 排序",
|
||||
"column_model": "型号",
|
||||
"column_role": "角色",
|
||||
"column_size": "尺寸",
|
||||
"column_status": "状态",
|
||||
"ready_badge": "准备好",
|
||||
"loading_badge": "加载中...",
|
||||
"idle_badge": "空闲",
|
||||
"started_downloading_required_one": "开始下载 {{count}} 所需模型",
|
||||
"started_downloading_required_other": "开始下载 {{count}} 所需模型"
|
||||
},
|
||||
"enterprise_faq": {
|
||||
"q_internal_tools": "我需要内部工具的许可证吗?",
|
||||
"a_internal_tools": "您的员工和承包商的内部使用是 FSL 允许的目的 — 无需许可。当您将 OmniVoice 作为竞争产品或服务(转售、托管 SaaS、白标)的一部分提供给其他人时,需要商业许可证。",
|
||||
"q_try_before": "我可以在提交之前尝试一下吗?",
|
||||
"a_try_before": "是的。完整的应用程序可免费下载并在本地运行,以便在 FSL 下进行评估。当您准备好讨论商业部署时,请给我们发送电子邮件,我们将共同解决细节问题。",
|
||||
"q_watermark": "水印呢?",
|
||||
"a_watermark": "默认情况下嵌入不可见的 AudioSeal 水印。商业许可持有者可以在“设置”→“隐私”中禁用它。免费/个人使用始终包含水印。",
|
||||
"q_apache": "源代码会变成 Apache 2.0 吗?",
|
||||
"a_apache": "是的。每个版本都会在其发布两周年时自动转换为 Apache 许可证 2.0 版。这意味着今天发布的版本是两年后的 Apache 2.0,我们不需要采取任何行动 - FSL 保证它不可撤销。"
|
||||
},
|
||||
"gallery_extra": {
|
||||
"save_prompt": "输入此语音配置文件的名称:",
|
||||
"saved_as_profile": "语音已保存为个人资料!",
|
||||
"save_profile_failed": "保存个人资料失败",
|
||||
"download_failed": "下载失败:{{message}}",
|
||||
"trim_load_failed": "无法加载音频进行修剪:{{message}}",
|
||||
"upload_crop_failed": "无法上传裁剪后的语音:{{message}}"
|
||||
},
|
||||
"dub_workflow": {
|
||||
"preparing_audio": "正在准备音频...",
|
||||
"preparing_video": "正在准备视频...",
|
||||
"extracting_audio_scenes": "提取音频和场景...",
|
||||
"transcribing_audio": "正在转录音频...",
|
||||
"transcription_complete": "转录完成",
|
||||
"upload_cancelled": "上传已取消",
|
||||
"upload_failed": "上传失败:{{message}}",
|
||||
"downloading_video": "正在下载视频...",
|
||||
"ingested": "摄入{{url}}",
|
||||
"ingest_cancelled": "摄取已取消",
|
||||
"ingest_failed": "URL 提取失败:{{message}}",
|
||||
"retry_cancelled": "重试已取消",
|
||||
"transcription_failed": "转录失败:{{message}}",
|
||||
"import_srt_no_job": "首先上传或摄取视频 - 没有附加字幕的作业。",
|
||||
"imported_cues": "从 {{file}} 导入了 {{count}} 提示",
|
||||
"skipped_malformed": "{{count}} 已跳过(格式错误)",
|
||||
"dropped_overlap": "{{count}} 掉落(重叠)",
|
||||
"clamped_to_duration": "{{count}} 夹紧到介质长度",
|
||||
"srt_import_failed": "SRT 导入失败",
|
||||
"cleaned_one": "已清理的 {{count}} 片段",
|
||||
"cleaned_other": "已清理的 {{count}} 碎片",
|
||||
"segments_clean": "段已经干净",
|
||||
"cleanup_failed": "清理失败:{{message}}",
|
||||
"cinematic_no_llm": "电影质量需要 LLM — 设置 TRANSLATE_BASE_URL + TRANSLATE_API_KEY(Ollama 在本地工作)。回落到快速。",
|
||||
"translate_errors": "{{errorCount}}/{{totalCount}} 段失败:{{firstError}}",
|
||||
"translated_segments": "翻译的 {{count}} 段 → {{lang}}",
|
||||
"translated_cinematic_suffix": "(电影)",
|
||||
"translation_failed": "翻译失败:{{message}}",
|
||||
"regenerating": "正在重新生成 {{count}} 段...",
|
||||
"generating_dub": "生成配音...",
|
||||
"generating_progress": "生成配音... {{current}}/{{total}}",
|
||||
"generation_aborted": "世代中止。",
|
||||
"dubbing_aborted": "配音中止",
|
||||
"generation_stream_ended": "生成流在完成之前结束",
|
||||
"dub_complete": "配音完成",
|
||||
"stop_failed": "未能停下来",
|
||||
"save_first": "请先单击“上传和转录”,以便在保存之前在服务器上处理视频。",
|
||||
"project_saved": "项目已保存",
|
||||
"project_created": "项目已创建",
|
||||
"save_failed": "保存失败:{{message}}",
|
||||
"opened_project": "开放时间:{{name}}",
|
||||
"delete_project_confirm": "删除这个项目吗?此操作无法撤消。",
|
||||
"project_deleted": "项目已删除",
|
||||
"delete_history_confirm": "删除该历史记录吗?",
|
||||
"history_deleted": "历史记录项已删除",
|
||||
"restored_state": "恢复了上一代状态",
|
||||
"upgrading_preview": "正在将 {{count}} 预览质量片段升级为完整质量…"
|
||||
},
|
||||
"tts_errors": {
|
||||
"enter_text": "请输入文字",
|
||||
"upload_or_select": "上传音频或选择语音配置文件",
|
||||
"trim_hint": "音频为 {{duration}}s — 修剪至 ≤{{max}}s 以获得最佳克隆效果",
|
||||
"timeout": "生成超时 - 模型可能仍在下载。检查设置 → 日志,然后重试。",
|
||||
"error_prefix": "错误:{{message}}",
|
||||
"ignored_unsupported": "忽略不支持的指令:{{items}}",
|
||||
"ignored_duplicate": "忽略(类别已设置):{{items}}"
|
||||
},
|
||||
"sharing": {
|
||||
"title": "共享和远程访问",
|
||||
"help": "将此正在运行的 OmniVoice 实例公开给您的其他计算机,而无需重新启动它。仅环回是默认设置 - 除非您在此处将其打开,否则不会共享任何内容。",
|
||||
"local_network": "本地网络",
|
||||
"local_help": "使用一次性访问 PIN 在 Wi-Fi/以太网上共享。其他设备扫描二维码或打开链接。",
|
||||
"ports_title": "港口",
|
||||
"ports_help": "这些是通过启动时读取的环境变量设置的。通过设置变量并重新启动 OmniVoice 来更改后端或 UI 端口。",
|
||||
"backend_port": "后端端口",
|
||||
"ui_port": "用户界面端口",
|
||||
"lan_share_port": "LAN共享端口",
|
||||
"port_error": "输入 1024 到 65535 之间的端口",
|
||||
"port_saved": "已保存 LAN 共享端口 — 下次启用共享时应用",
|
||||
"port_save_failed": "无法保存端口:{{message}}",
|
||||
"saving": "正在保存...",
|
||||
"ports_note": "后端和 UI 端口在重新启动时应用。 LAN 共享端口将在您下次启用共享时应用。",
|
||||
"tailscale_title": "Tailscale(私人远程访问)",
|
||||
"tailscale_checking": "检查尾鳞...",
|
||||
"tailscale_absent": "未检测到尾鳞。安装它即可从私人尾网上的任何地方安全地访问 OmniVoice。",
|
||||
"tailscale_install": "安装尾秤",
|
||||
"tailscale_running": "尾鳞正在运行。通过您的私人尾网提供 OmniVoice。",
|
||||
"tailscale_not_logged_in": "Tailscale 已安装但未登录。首先启动并登录 Tailscale。",
|
||||
"tailscale_enabled": "Tailscale 服务已启用",
|
||||
"tailscale_enable_failed": "无法启用 Tailscale",
|
||||
"tailscale_enable_error": "无法启用 Tailscale:{{message}}",
|
||||
"tailscale_disabled": "尾盘服务已禁用",
|
||||
"tailscale_disable_failed": "无法禁用 Tailscale",
|
||||
"tailscale_disable_error": "无法禁用 Tailscale:{{message}}",
|
||||
"tailscale_enabling": "启用...",
|
||||
"tailscale_enable_btn": "启用 Tailscale 服务",
|
||||
"tailscale_disabling": "禁用...",
|
||||
"tailscale_disable_btn": "停止 Tailscale 服务",
|
||||
"tailscale_copy": "复制链接",
|
||||
"tailscale_open": "在浏览器中打开",
|
||||
"copied": "已复制",
|
||||
"tailscale_qr_alt": "Tailscale URL 的二维码"
|
||||
},
|
||||
"reportBug": {
|
||||
"label": "报告错误",
|
||||
"title": "在浏览器中打开预填充的 GitHub 问题页面。在您单击“提交”之前,不会发送任何内容。"
|
||||
},
|
||||
"app": {
|
||||
"loading": "加载中...",
|
||||
"trimmed_loaded": "已加载修剪后的音频",
|
||||
"toast_exported": "导出:{{name}}",
|
||||
"toast_export_failed": "导出失败:{{message}}",
|
||||
"toast_open_folder_failed": "无法打开文件夹:{{message}}",
|
||||
"toast_saving": "正在保存 {{name}}...",
|
||||
"toast_saved": "已保存:{{path}}",
|
||||
"toast_save_error": "保存错误:{{message}}",
|
||||
"toast_processing": "正在处理 {{name}}...",
|
||||
"toast_downloaded": "已下载{{name}}",
|
||||
"toast_download_error": "下载错误:{{message}}",
|
||||
"toast_upload_first": "请先单击“上传和转录”,以便在保存之前在服务器上处理视频。",
|
||||
"toast_save_failed": "保存失败:{{message}}",
|
||||
"toast_opened": "开放时间:{{name}}",
|
||||
"toast_project_deleted": "项目已删除",
|
||||
"toast_restored_state": "恢复了上一代状态",
|
||||
"toast_history_deleted": "历史记录项已删除",
|
||||
"toast_flushed": "刷新 — RAM {{ram}}G · VRAM {{vram}}G{{unloaded}}",
|
||||
"toast_model_unloaded": "· 模型卸载",
|
||||
"toast_flush_failed": "刷新失败:{{message}}",
|
||||
"toast_project_saved": "项目已保存",
|
||||
"toast_project_created": "项目已创建"
|
||||
},
|
||||
"update": {
|
||||
"available": "有可用更新 {{version}}",
|
||||
"install": "安装并重启",
|
||||
"install_hint": "下载更新并重启到新版本",
|
||||
"downloading": "更新中… {{pct}}%",
|
||||
"restart": "重启以更新",
|
||||
"busy": "请先完成配音,再安装更新。",
|
||||
"whats_new": "更新内容",
|
||||
"failed": "更新失败",
|
||||
"retry": "重试",
|
||||
"dismiss": "解雇"
|
||||
},
|
||||
"archetypes": {
|
||||
"featured": "精选",
|
||||
@@ -945,5 +1577,25 @@
|
||||
"facet_accent": "口音",
|
||||
"facet_lang": "语言",
|
||||
"facet_whisper": "耳语"
|
||||
},
|
||||
"support": {
|
||||
"tab_support": "支持",
|
||||
"tab_license": "商业许可",
|
||||
"toggle_label": "支持或商业许可",
|
||||
"other_ways": "其他帮助方法",
|
||||
"star_github": "在 GitHub 上加星标",
|
||||
"join_discord": "加入不和谐"
|
||||
},
|
||||
"updates": {
|
||||
"tab": "更新",
|
||||
"up_to_date": "最新·v{{version}}",
|
||||
"check_now": "立即查看",
|
||||
"releases": "发布",
|
||||
"current": "当前",
|
||||
"prerelease": "预览",
|
||||
"loading": "正在加载版本...",
|
||||
"none": "未找到任何版本",
|
||||
"load_error": "无法加载版本(离线?)",
|
||||
"retry_load": "重试"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,4 @@
|
||||
{
|
||||
"update": {
|
||||
"available": "有可用更新 {{version}}",
|
||||
"install": "安裝並重新啟動",
|
||||
"install_hint": "下載更新並重新啟動至新版本",
|
||||
"downloading": "更新中… {{pct}}%",
|
||||
"restart": "重新啟動以更新",
|
||||
"busy": "請先完成配音,再安裝更新。"
|
||||
},
|
||||
"nav": {
|
||||
"clone": "複製聲音",
|
||||
"design": "設計聲音",
|
||||
@@ -16,7 +8,10 @@
|
||||
"launchpad": "發射台",
|
||||
"gallery": "畫廊",
|
||||
"transcripts": "成績單",
|
||||
"omnidrive": "全方位驅動器"
|
||||
"omnidrive": "全方位驅動器",
|
||||
"move_rail_right": "將軌道移至右側",
|
||||
"move_rail_left": "將導軌移至左側",
|
||||
"flip_rail": "翻轉導軌側"
|
||||
},
|
||||
"settings": {
|
||||
"general": "一般",
|
||||
@@ -45,7 +40,40 @@
|
||||
"ffmpeg_missing": "未找到",
|
||||
"ffmpeg_current": "目前路徑",
|
||||
"ffmpeg_desc": "如果自動偵測失敗,請設定自訂 ffmpeg 路徑。",
|
||||
"ffmpeg_saved": "FFmpeg 路徑設定 — 重新啟動後端以套用。"
|
||||
"ffmpeg_saved": "FFmpeg 路徑設定 — 重新啟動後端以套用。",
|
||||
"diagnostics_copied": "已複製診斷資訊 — 貼上到您的問題報告中。",
|
||||
"updater_desktop": "更新程式僅在桌面應用程式中運行。",
|
||||
"latest_version": "您使用的是最新版本。",
|
||||
"logs_load_failed": "無法載入日誌:{{message}}",
|
||||
"clear_frontend_confirm": "清除記憶體中的前端日誌緩衝區嗎?",
|
||||
"clear_frontend_title": "清除日誌",
|
||||
"frontend_logs_cleared": "前端日誌已清除",
|
||||
"clear_tauri_confirm": "截斷 Tauri 端日誌檔?作業系統將繼續寫入新條目。",
|
||||
"clear_tauri_title": "清除 Tauri 日誌",
|
||||
"nothing_to_clear": "沒有什麼需要清除的 — 磁碟上還沒有 Tauri 日誌檔。",
|
||||
"cleared_tauri_one": "已清除 {{count}} Tauri 日誌文件",
|
||||
"cleared_tauri_other": "已清除 {{count}} Tauri 日誌文件",
|
||||
"clear_tauri_failed": "無法清除 Tauri 日誌:{{message}}",
|
||||
"clear_backend_confirm": "清除後端運行時+崩潰日誌?此操作無法撤銷。",
|
||||
"clear_backend_title": "清除日誌",
|
||||
"backend_logs_cleared": "後台日誌清除",
|
||||
"clear_backend_failed": "清除日誌失敗",
|
||||
"copy_failed": "複製失敗:{{message}}",
|
||||
"update_check_failed": "更新檢查失敗:{{message}}",
|
||||
"save_failed": "儲存失敗:{{message}}",
|
||||
"clear_failed": "清除失敗:{{message}}",
|
||||
"engine_switched": "{{family}} → {{engine}}",
|
||||
"channel_set_failed": "無法設定頻道:{{message}}",
|
||||
"updater_downloading": "正在下載 {{version}}...",
|
||||
"updater_installed": "已安裝 - 重新啟動。",
|
||||
"updater_available_title": "可用更新",
|
||||
"updater_available_body": "版本 {{version}} 可用。\n\n{{notes}}\n\n立即下載並安裝?",
|
||||
"updater_notes_fallback": "請參閱 GitHub 上的發行說明。",
|
||||
"shortcut_load_failed": "無法載入捷徑:{{message}}",
|
||||
"shortcut_set": "聽寫快捷鍵設定為 {{shortcut}}",
|
||||
"shortcut_register_failed": "無法註冊:{{message}}",
|
||||
"shortcut_reset": "重設為預設值",
|
||||
"shortcut_reset_failed": "重置失敗:{{message}}"
|
||||
},
|
||||
"bootstrap": {
|
||||
"title": "OmniVoice Studio",
|
||||
@@ -69,7 +97,13 @@
|
||||
"waiting_output": "正在等待輸出…",
|
||||
"auto_detect": "自動偵測",
|
||||
"suggest_lang": "切換到繁體中文?",
|
||||
"select_lang": "語言:"
|
||||
"select_lang": "語言:",
|
||||
"region_global": "全球(直接)",
|
||||
"region_china": "中國(鏡子)",
|
||||
"region_russia": "俄羅斯(鏡子)",
|
||||
"region_restricted": "受限(鏡像)",
|
||||
"unknown_error": "未知錯誤",
|
||||
"retrying": "正在重試..."
|
||||
},
|
||||
"stories": {
|
||||
"title": "故事編輯器",
|
||||
@@ -158,7 +192,17 @@
|
||||
"reload": "強制重新載入使用者介面",
|
||||
"backend": "後端",
|
||||
"frontend": "前端",
|
||||
"tauri": "金牛座"
|
||||
"tauri": "金牛座",
|
||||
"cancelOp": "取消操作",
|
||||
"dismiss": "解僱",
|
||||
"dismissStatus": "解除狀態",
|
||||
"search": "搜尋...",
|
||||
"no_matches": "沒有匹配項",
|
||||
"recent_and_popular": "最近和熱門",
|
||||
"popular_label": "熱門",
|
||||
"showing_of": "顯示 {{total}} 的 {{shown}}。輸入搜尋...",
|
||||
"yes": "是的",
|
||||
"no": "否"
|
||||
},
|
||||
"launchpad": {
|
||||
"greeting": "你好",
|
||||
@@ -180,7 +224,16 @@
|
||||
"locked": "鎖定",
|
||||
"open": "打開",
|
||||
"try_it": "嘗試一下",
|
||||
"audio_only": "僅音訊"
|
||||
"audio_only": "僅音訊",
|
||||
"stories_title": "故事",
|
||||
"stories_desc": "多語音有聲書 — 塑造角色、插入劇本、依章節導出。",
|
||||
"gallery_title": "語音畫廊",
|
||||
"gallery_desc": "按口音、年齡和風格瀏覽現成的設計聲音 - 無需設定。",
|
||||
"transcripts_title": "成績單",
|
||||
"transcripts_desc": "將音訊或視訊轉換為可編輯、可搜尋的文字 - 涵蓋 646 種語言。",
|
||||
"recent_files": "最近的文件",
|
||||
"view_all_files": "查看所有文件",
|
||||
"file": "文件"
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "提示",
|
||||
@@ -271,11 +324,6 @@
|
||||
"outputs": "輸出",
|
||||
"crash_log": "崩潰日誌",
|
||||
"update_endpoint": "更新端點",
|
||||
"update_channel": "更新通道",
|
||||
"channel_stable": "穩定版",
|
||||
"channel_preview": "預覽版",
|
||||
"channel_set": "更新通道已設定為 {{channel}}",
|
||||
"channel_preview_hint": "預覽版追蹤最新的 main 組建——功能較新,但測試較少。若穩定版較新,則回退至穩定版。",
|
||||
"yes": "是的",
|
||||
"no": "不",
|
||||
"web_preview": "網頁預覽",
|
||||
@@ -284,7 +332,12 @@
|
||||
"copy_diagnostics": "複製診斷",
|
||||
"github": "GitHub 上的 OmniVoice",
|
||||
"model_card": "型號卡",
|
||||
"commercial_license": "商業許可"
|
||||
"commercial_license": "商業許可",
|
||||
"update_channel": "更新通道",
|
||||
"channel_stable": "穩定版",
|
||||
"channel_preview": "預覽版",
|
||||
"channel_set": "更新通道已設定為 {{channel}}",
|
||||
"channel_preview_hint": "預覽版追蹤最新的 main 組建——功能較新,但測試較少。若穩定版較新,則回退至穩定版。"
|
||||
},
|
||||
"privacy": {
|
||||
"desc": "一切都在<1>這台機器</1>上運作。除非您明確使用線上翻譯器(Google、DeepL 等)或推送到 HuggingFace,否則您的音訊、視訊和文字記錄永遠不會離開您的電腦。",
|
||||
@@ -340,7 +393,30 @@
|
||||
"unavailable": "不可用",
|
||||
"use": "使用",
|
||||
"loading": "正在載入引擎...",
|
||||
"refresh": "重新整理"
|
||||
"refresh": "重新整理",
|
||||
"matrixTitle": "引擎相容性矩陣",
|
||||
"loadFailed": "無法載入引擎:{{message}}",
|
||||
"couldNotLoad": "無法載入引擎:{{message}}",
|
||||
"retry": "重試",
|
||||
"activeEngine": "活動 {{family}}:{{engine}}",
|
||||
"engineCompatLabel": "{{family}} 引擎相容性",
|
||||
"active": "活躍的",
|
||||
"whyUnavailable": "為什麼不可用?",
|
||||
"lastError": "最後一個錯誤:{{error}}",
|
||||
"installedAndReady": "安裝並準備就緒",
|
||||
"notInstalled": "未安裝",
|
||||
"available": "可用",
|
||||
"subprocessTitle": "在自己的子進程+venv中運行",
|
||||
"inProcessTitle": "在 OmniVoice Python 進程中執行",
|
||||
"testEngine": "測試引擎",
|
||||
"testing": "測試...",
|
||||
"recheck": "重新檢查",
|
||||
"rechecking": "重新檢查…",
|
||||
"latencyMs": "{{ms}} 毫秒",
|
||||
"failed": "失敗了",
|
||||
"acceptLicense": "接受許可",
|
||||
"noBackends": "沒有註冊後端。",
|
||||
"switch_failed": "切換引擎失敗"
|
||||
},
|
||||
"capture": {
|
||||
"desc": "全域熱鍵僅適用於桌面應用程式。當視窗具有焦點時,Web UI 使用頁內 <1>Ctrl+Shift+Space</1> 快速鍵。",
|
||||
@@ -352,13 +428,55 @@
|
||||
"record_shortcut": "記錄快捷方式",
|
||||
"recording": "錄音…",
|
||||
"save": "儲存",
|
||||
"reset_default": "重設為預設值"
|
||||
"reset_default": "重設為預設值",
|
||||
"listening_label": "正在聽…",
|
||||
"transcribing_label": "正在抄寫…",
|
||||
"pasted": "貼上的",
|
||||
"no_speech": "未偵測到語音",
|
||||
"mic_denied": "麥克風存取被拒絕",
|
||||
"mic_denied_toast": "麥克風存取被拒絕。 {{hint}}",
|
||||
"mic_hint_mac": "macOS:開啟系統設定 → 隱私和安全 → 麥克風並啟用 OmniVoice。",
|
||||
"mic_hint_windows": "Windows:開啟設定 → 隱私和安全性 → 麥克風並允許 OmniVoice。",
|
||||
"mic_hint_linux": "Linux:檢查您的使用者是否在音訊群組中且 WebView 是否具有麥克風存取權限。",
|
||||
"transcription_failed": "轉錄失敗:{{message}}"
|
||||
},
|
||||
"logs": {
|
||||
"no_tauri_log": "磁碟上尚未有 Tauri 登入 — 透過桌面建置啟動以產生一個",
|
||||
"empty_frontend": "尚未捕獲任何前端控制台條目。與應用程式互動 - 每個控制台。 * 將顯示在此處。",
|
||||
"empty_tauri": "沒有可用的 Tauri 日誌。僅在桌面 shell 中運行。",
|
||||
"empty_backend": "運行時日誌為空。當後端記錄活動時,活動將顯示在此。"
|
||||
"empty_backend": "運行時日誌為空。當後端記錄活動時,活動將顯示在此。",
|
||||
"title": "紀錄",
|
||||
"source_backend": "後端",
|
||||
"source_frontend": "前端",
|
||||
"source_tauri": "金牛座",
|
||||
"expand": "展開日誌",
|
||||
"collapse": "折疊日誌",
|
||||
"expand_aria": "展開日誌面板",
|
||||
"collapse_aria": "折疊日誌面板",
|
||||
"drag_resize": "拖曳以調整大小",
|
||||
"refresh": "重新整理",
|
||||
"refresh_aria": "刷新日誌",
|
||||
"copy_visible": "複製可見日誌",
|
||||
"copy_visible_aria": "複製可見日誌",
|
||||
"clear": "清除",
|
||||
"clear_aria": "清除日誌",
|
||||
"report_issue": "報告問題(複製診斷)",
|
||||
"report_issue_aria": "報告問題",
|
||||
"close": "關閉",
|
||||
"close_aria": "關閉日誌面板",
|
||||
"join_discord": "加入我們的不和諧",
|
||||
"join_discord_aria": "加入我們的 Discord 社區",
|
||||
"support_project": "支持這個項目",
|
||||
"support_project_aria": "支持這個項目",
|
||||
"empty_frontend_short": "還沒有前端控制台輸出。",
|
||||
"empty_lines": "沒有線路。",
|
||||
"all_clear": "✅ 一切正常 — 未偵測到任何問題",
|
||||
"log_cleared": "{{source}} 日誌已清除",
|
||||
"clear_failed": "清除失敗:{{message}}",
|
||||
"log_copied": "已複製 {{source}} 日誌",
|
||||
"copy_failed": "複製失敗:{{message}}",
|
||||
"report_copied": "已複製診斷報告 — 將其貼上到 GitHub 問題中。",
|
||||
"report_failed": "報告失敗:{{message}}"
|
||||
},
|
||||
"voice_profile": {
|
||||
"test_text": "你好——這是對這個聲音的測試。",
|
||||
@@ -526,7 +644,17 @@
|
||||
"install_already": "{{engine}} 已安裝",
|
||||
"install_ok": "{{engine}} 已安裝",
|
||||
"install_failed": "安裝失敗:{{message}}",
|
||||
"prep_elapsed": "{{time}} 已過"
|
||||
"prep_elapsed": "{{time}} 已過",
|
||||
"add_language": "新增語言",
|
||||
"search_languages": "搜尋語言...",
|
||||
"languages_selected_one": "已選擇 {{count}} 語言",
|
||||
"languages_selected_other": "已選擇 {{count}} 種語言",
|
||||
"more_to_narrow": "+{{count}} more — 鍵入縮小範圍",
|
||||
"no_matches": "沒有匹配項",
|
||||
"diagnostic_copied": "診斷複製",
|
||||
"copy_failed": "複製失敗",
|
||||
"open_docs": "開啟文件",
|
||||
"copy_diagnostic": "複製診斷"
|
||||
},
|
||||
"glossary": {
|
||||
"title": "詞彙表",
|
||||
@@ -601,7 +729,17 @@
|
||||
"more_actions_title": "更多行動",
|
||||
"speaker_pick": "選擇…",
|
||||
"speaker_title_detected": "揚聲器 — 從偵測到的名稱中選擇,或輸入自訂名稱",
|
||||
"speaker_title_custom": "揚聲器 — 輸入名稱(未偵測到二值化複製)"
|
||||
"speaker_title_custom": "揚聲器 — 輸入名稱(未偵測到二值化複製)",
|
||||
"time_edit_title": "按一下可編輯開始時間 (m:ss.s)。 Enter 提交,Esc 取消。",
|
||||
"fit_fits": "適合",
|
||||
"fit_fits_title": "自然速率音訊適合插槽內。",
|
||||
"fit_overflows": "溢出 +{{seconds}}s",
|
||||
"fit_overflows_title": "翻譯後的文字比原始文字長 {{seconds}}s。音訊經過嚴格修剪;縮短文字或將計時切換為“拉伸視訊”。",
|
||||
"fit_stretched": "視訊{{ratio}}×",
|
||||
"fit_stretched_title": "拉伸視訊模式:此片段的視訊速度減慢至 {{ratio}}× 以適應自然的配音音訊。",
|
||||
"fit_compressed_title": "TTS 音訊佔插槽的 {{pct}}% — 被嚴重壓縮。",
|
||||
"fit_audio_title": "音訊適合插槽內。",
|
||||
"fit_ratio_title": "TTS 音訊佔插槽的 {{pct}}%。"
|
||||
},
|
||||
"voice": {
|
||||
"personality": "個性",
|
||||
@@ -696,10 +834,24 @@
|
||||
"status_running": "跑步",
|
||||
"status_done": "完成",
|
||||
"status_failed": "失敗了",
|
||||
"status_cancelled": "取消"
|
||||
"status_cancelled": "取消",
|
||||
"add_to_queue_title": "將影片新增至隊列",
|
||||
"drop_hint_text": "將影片檔案拖放到此處或點擊瀏覽",
|
||||
"drop_formats": "MP4·MOV·MKV·WEBM",
|
||||
"files_kicker": "文件 ({{count}})",
|
||||
"file_size_mb": "{{size}} MB",
|
||||
"target_languages": "目標語言",
|
||||
"voice_kicker": "語音",
|
||||
"default_option": "預設",
|
||||
"clone_profiles": "克隆設定檔",
|
||||
"presets": "預設",
|
||||
"preserve_bg": "保留背景音訊(音樂/FX)",
|
||||
"estimate": "{{videos}} 影片 × {{langs}} 語言 = {{jobs}} 作業",
|
||||
"select_files_langs": "選擇文件和語言",
|
||||
"add_to_queue": "添加到隊列"
|
||||
},
|
||||
"gallery": {
|
||||
"title": "畫廊",
|
||||
"title": "OmniVoice 畫廊",
|
||||
"search_placeholder": "搜尋 YouTube...",
|
||||
"all_voices": "所有聲音 ({{count}})",
|
||||
"no_voices": "還沒有聲音",
|
||||
@@ -714,6 +866,14 @@
|
||||
"youtube_results": "YouTube 結果 ({{count}})",
|
||||
"clone_profile": "克隆設定檔",
|
||||
"crop_audio": "裁切音訊",
|
||||
"cat_disney": "迪士尼",
|
||||
"cat_anime": "動漫",
|
||||
"cat_marvel": "漫威/DC",
|
||||
"cat_celebs": "名人",
|
||||
"cat_politicians": "政治家",
|
||||
"cat_news": "新聞主播",
|
||||
"cat_gaming": "遊戲",
|
||||
"cat_books": "書籍/電影",
|
||||
"subtitle": "數百種現成的設計聲音 - 選擇一種即可使用。",
|
||||
"zone_archetypes": "原型",
|
||||
"zone_imports": "我的進口",
|
||||
@@ -818,7 +978,6 @@
|
||||
"badge": "商業許可",
|
||||
"hero_title": "在生產中傳送人工智慧聲音",
|
||||
"hero_desc": "OmniVoice Studio 可根據功能來源許可證 (FSL) 取得原始碼。大多數用戶可以在沒有商業協議的情況下進行評估、原型設計甚至內部部署。只有當您正在建立競爭產品或服務,或您的用例超出 FSL 範圍時,您才需要商業許可證。",
|
||||
"hero_note": "建立競爭產品或服務或大規模部署(例如,提供按使用付費的 API)需要商業許可證。定價等級即將推出 - 請同時聯絡我們。",
|
||||
"why_title": "為什麼企業選擇 OmniVoice",
|
||||
"pricing_title": "定價",
|
||||
"faq_title": "常見問題",
|
||||
@@ -838,7 +997,8 @@
|
||||
"benefit_source": "可用原始碼核心",
|
||||
"benefit_source_desc": "對堆疊的完全可見性。在許可條款範圍內進行審核、分叉和調整。",
|
||||
"benefit_lang": "646 種語言",
|
||||
"benefit_lang_desc": "以人類層次的品質轉錄、翻譯和配音 646 種語言。"
|
||||
"benefit_lang_desc": "以人類層次的品質轉錄、翻譯和配音 646 種語言。",
|
||||
"hero_note": "根據 FSL,內部使用(即使是大規模使用)也是免費的;僅需要商業許可證才能將 OmniVoice 作為競爭產品或服務(託管或按使用付費的 API、轉售或白標應用程式)提供給其他人。定價等級即將推出 - 請同時聯絡我們。"
|
||||
},
|
||||
"exportModal": {
|
||||
"export": "出口",
|
||||
@@ -927,7 +1087,479 @@
|
||||
"dubbing_title": "看實際配音",
|
||||
"dubbing_sync": "同步播放",
|
||||
"dubbing_picker": "嘗試另一種語言:",
|
||||
"dubbing_cta": "在您自己的影片上運行此→"
|
||||
"dubbing_cta": "在您自己的影片上運行此→",
|
||||
"dubbing_loading": "正在載入配音演示...",
|
||||
"dubbing_dismiss": "關閉配音演示",
|
||||
"original_tag": "原創",
|
||||
"dubbed_tag": "配音",
|
||||
"script_conversational": "會話型",
|
||||
"script_technical": "技術詞彙",
|
||||
"script_french": "非英語(法語)",
|
||||
"aria_pause": "暫停{{label}}",
|
||||
"aria_hear": "聽到{{label}}",
|
||||
"aria_replay": "透過轉錄器重播 {{label}}"
|
||||
},
|
||||
"direction": {
|
||||
"title": "段 #{{id}} 的方向",
|
||||
"desc": "告訴管道這條線的感覺如何。簡單的英語有效 — 系統將您的單字映射到穩定的分類法(能量/情感/節奏/親密/正式),然後透過電影翻譯、TTS 和插槽配合將分類法串聯起來。",
|
||||
"label": "方向",
|
||||
"lineHint": "行:“{{text}}”",
|
||||
"placeholder": "例如緊急而驚訝/溫暖、充滿希望/低聲、親密",
|
||||
"previewParse": "預覽解析",
|
||||
"previewFailed": "預覽失敗:{{message}}",
|
||||
"clear": "清除",
|
||||
"cancel": "取消",
|
||||
"saveDirection": "保存方向",
|
||||
"ttsInstruct": "TTS 指示:",
|
||||
"nothingParsed": "—(沒有解析)",
|
||||
"translateHint": "翻譯提示:",
|
||||
"rateBias": "率偏差:",
|
||||
"speedsUp": "加速",
|
||||
"slowsDown": "放慢速度",
|
||||
"taxonomyTokens": "分類標記"
|
||||
},
|
||||
"errors": {
|
||||
"title": "這個選項卡遇到了障礙。",
|
||||
"desc": "別擔心——應用程式的其餘部分仍然有效。您可以切換選項卡,或在下面重試。",
|
||||
"tryAgain": "再試一次",
|
||||
"openDocs": "開啟此錯誤的文檔"
|
||||
},
|
||||
"keyboard": {
|
||||
"title": "鍵盤快速鍵",
|
||||
"footer": "隨時按 <1>?</1> 開啟此檔案。",
|
||||
"or": "或",
|
||||
"nav": "導航",
|
||||
"nav_cheatsheet": "顯示此備忘單",
|
||||
"nav_closeModal": "關閉模式/取消",
|
||||
"nav_save": "保存項目/提交修剪",
|
||||
"segmentEditor": "段編輯器",
|
||||
"seg_split": "在遊標處分割段",
|
||||
"seg_merge": "與下一個片段合併",
|
||||
"seg_undo": "撤銷",
|
||||
"seg_redo": "重做",
|
||||
"seg_click": "主要行動",
|
||||
"seg_shiftClick": "範圍選擇",
|
||||
"trimmer": "音訊修剪器",
|
||||
"trim_playPause": "預覽播放/暫停",
|
||||
"trim_nudgeStart": "輕推啟動手柄",
|
||||
"trim_nudgeEnd": "輕推末端手柄",
|
||||
"trim_fineNudge": "精細推動",
|
||||
"trim_coarseNudge": "粗推",
|
||||
"trim_zoomIn": "放大/縮小",
|
||||
"trim_fitAll": "適合全部/適合選擇",
|
||||
"trim_confirm": "確認修剪",
|
||||
"dub": "配音",
|
||||
"dub_generate": "生成配音",
|
||||
"dub_sidebar": "切換側邊欄"
|
||||
},
|
||||
"network": {
|
||||
"sharing_on_title": "分享於——點擊看詳情",
|
||||
"share_on_network": "在您的網路上分享",
|
||||
"switching": "切换…",
|
||||
"network": "網路",
|
||||
"local": "本地",
|
||||
"share_confirm_title": "在您的网络上分享?",
|
||||
"share_confirm_hint": "Wi-Fi/乙太網路上的其他裝置將能夠使用開啟後顯示的存取 PIN 碼來存取 OmniVoice。",
|
||||
"enabling": "启用...",
|
||||
"enable": "啟用",
|
||||
"shared_title": "在您的網路上分享",
|
||||
"no_interface": "沒有可存取的網路介面 - 連接到 Wi-Fi/乙太網路。",
|
||||
"copy_link": "複製連結",
|
||||
"open_in_browser": "在瀏覽器中開啟",
|
||||
"qr_alt": "{{ip}} 的二維碼",
|
||||
"pin": "密碼:",
|
||||
"stop_sharing": "停止分享",
|
||||
"copied": "已複製",
|
||||
"enable_error": "无法启用共享:{{message}}",
|
||||
"disable_error": "無法停用:{{message}}"
|
||||
},
|
||||
"readiness": {
|
||||
"checking_system": "檢查系統...",
|
||||
"all_ready": "所有系統準備就緒",
|
||||
"system_readiness": "系統準備狀況",
|
||||
"asr_model": "ASR模型",
|
||||
"loaded_ready": "已載入並準備就緒",
|
||||
"loading_first_run": "正在加載...(第一次運行可能需要 1-2 分鐘)",
|
||||
"failed_to_load": "載入失敗",
|
||||
"not_loaded_yet": "尚未加載 - 將在第一次轉錄時加載",
|
||||
"error_check_logs": "錯誤:{{error}}。檢查日誌並嘗試重新啟動。",
|
||||
"check_logs_restart": "檢查日誌中是否有模型載入錯誤。嘗試重新啟動。",
|
||||
"llm_cinematic": "法學碩士(電影)",
|
||||
"llm_configure": "配置 TRANSLATE_BASE_URL 以獲得電影級翻譯質量",
|
||||
"llm_set_env": "設定 TRANSLATE_BASE_URL 和 TRANSLATE_API_KEY 環境變數。可與 Ollama、OpenAI、LM Studio 等搭配使用。",
|
||||
"llm_optional": "可選 — 設定 TRANSLATE_BASE_URL 以獲得電影質量"
|
||||
},
|
||||
"license": {
|
||||
"title": "Supertonic-3 — 許可證接受",
|
||||
"intro": "Supertonic-3 擁有兩個不同的許可證。請在啟用引擎之前檢查兩者。",
|
||||
"sdk_heading": "SDK程式碼·麻省理工學院",
|
||||
"sdk_desc": "Python 推理 SDK (supertonic) 已獲得 MIT 許可。允許使用,包括商業用途。",
|
||||
"read_mit": "閱讀 MIT 許可證 →",
|
||||
"model_heading": "模型重量 · OpenRAIL-M",
|
||||
"model_desc": "Supertonic-3 模型權重在 OpenRAIL-M 許可證下發布。此許可證限制用於非惡意目的 - 請參閱連結許可證以了解全套基於使用的限制。",
|
||||
"read_openrail": "閱讀 OpenRAIL-M 許可證 →",
|
||||
"footer": "按一下「接受」會在 OmniVoice 的本機設定中記錄您的接受情況並啟用引擎。您的接受僅儲存在本機上 — 不會向 Supertone Inc. 或任何第三方報告任何內容。",
|
||||
"saving": "正在儲存...",
|
||||
"accept": "接受",
|
||||
"accepted_toast": "已接受 Supertonic-3 許可證。",
|
||||
"accept_error": "未能記錄許可證接受情況:{{message}}"
|
||||
},
|
||||
"voicePreview": {
|
||||
"title": "語音預覽",
|
||||
"close": "關閉預覽",
|
||||
"default_text": "你好!這是我的聲音的預覽。",
|
||||
"default_voice": "預設語音",
|
||||
"clone_profiles": "克隆設定檔",
|
||||
"designed_voices": "設計的聲音",
|
||||
"presets": "預設",
|
||||
"placeholder": "輸入要聽的內容...",
|
||||
"stop": "停止",
|
||||
"regenerate": "再生",
|
||||
"preview": "預覽",
|
||||
"hint": "8步·快速預覽"
|
||||
},
|
||||
"header": {
|
||||
"kicker_studio": "工作室",
|
||||
"kicker_library": "圖書館",
|
||||
"kicker_preferences": "偏好設定",
|
||||
"kicker_licensing": "授權",
|
||||
"label_launchpad": "發射台",
|
||||
"label_clone": "語音克隆",
|
||||
"label_design": "聲音設計",
|
||||
"label_dub": "配音",
|
||||
"label_projects": "全方位驅動器",
|
||||
"label_gallery": "畫廊",
|
||||
"label_transcriptions": "轉錄",
|
||||
"label_settings": "設定",
|
||||
"label_enterprise": "商業許可",
|
||||
"status_ready": "準備好",
|
||||
"status_loading": "加載中...",
|
||||
"status_idle": "空閒",
|
||||
"memory_management": "記憶體管理",
|
||||
"flush": "沖洗",
|
||||
"loaded_models": "載入模型",
|
||||
"no_models": "沒有載入模型",
|
||||
"unload": "解除安裝",
|
||||
"flush_caches": "刷新快取",
|
||||
"unload_all_flush": "全部卸載+沖洗"
|
||||
},
|
||||
"sidebar": {
|
||||
"tab_drive": "驅動器",
|
||||
"tab_history": "歷史",
|
||||
"tab_exports": "出口",
|
||||
"save_project": "儲存配音項目",
|
||||
"save_new_project": "另存為新配音項目",
|
||||
"dub_projects": "配音項目",
|
||||
"voice_clones": "語音克隆",
|
||||
"designed_voices": "設計的聲音",
|
||||
"no_dub_projects": "沒有存檔的配音項目",
|
||||
"no_dub_hint": "上傳影片並點擊“儲存”以保留您的作品。",
|
||||
"no_clones": "還沒有聲音克隆",
|
||||
"no_clones_hint": "錄製或上傳音頻,然後按一下「另存為語音設定檔」。",
|
||||
"no_designs": "還沒設計聲音",
|
||||
"no_designs_hint": "產生聲音並將其從歷史記錄中保存。",
|
||||
"history_subtitle": "生成歷史 · 儲存在 SQLite 中",
|
||||
"no_history": "無世代史",
|
||||
"no_history_hint": "合成音訊或配音視訊 - 結果將顯示在此。",
|
||||
"clear_history": "清除歷史記錄",
|
||||
"clear_confirm": "清除所有 {{count}} 歷史記錄項目嗎?此操作無法撤銷。",
|
||||
"history_cleared": "歷史記錄已清除",
|
||||
"recent_exports": "最近的出口",
|
||||
"no_exports": "沒有下載的輸出",
|
||||
"no_exports_hint": "透過 Tauri 匯出文件以在此處查看其追蹤情況。",
|
||||
"show_in_folder": "在資料夾中顯示",
|
||||
"open": "打開",
|
||||
"select": "選擇",
|
||||
"try_voice": "嘗試一下",
|
||||
"consistent": "一致的",
|
||||
"locked": "鎖定",
|
||||
"clone_label": "複製",
|
||||
"design_label": "設計",
|
||||
"dub_label": "配音",
|
||||
"save_label": "儲存",
|
||||
"lock_identity": "鎖定語音身份",
|
||||
"in_folder": "在 {{folder}} 中"
|
||||
},
|
||||
"trimmer": {
|
||||
"title": "修剪參考音訊",
|
||||
"decoding": "解碼音訊...",
|
||||
"meta_length": "長度 {{duration}} · {{sampleRate}} Hz",
|
||||
"meta_rendering": "渲染波形{{percent}}%",
|
||||
"keyboard_hint": "滾動 = 縮放 · Shift+滾動 = 平移 · alt+拖曳 = 平移 · ⏐ ⟵ ⟶ ⏐ 鍵調整手柄",
|
||||
"zoom_in": "放大 (+)",
|
||||
"zoom_out": "縮小 (-)",
|
||||
"fit_all": "適合所有人(首頁)",
|
||||
"fit_selection": "適合選擇(完)",
|
||||
"fit_sel_btn": "適合選擇",
|
||||
"view_range": "查看 {{start}} → {{end}} ({{duration}})",
|
||||
"start_label": "開始",
|
||||
"end_label": "結束",
|
||||
"length_label": "長度",
|
||||
"too_long": ">{{max}}s",
|
||||
"too_short": "太短",
|
||||
"length_ok": "好的",
|
||||
"loop_preview": "循環預覽",
|
||||
"pause": "暫停",
|
||||
"preview_selection": "預覽選擇",
|
||||
"play_hint": "空白鍵播放 · Enter 確認 · Esc 取消",
|
||||
"cancel": "取消",
|
||||
"use_trimmed": "使用修剪過的",
|
||||
"decode_failed": "解碼失敗:{{message}}",
|
||||
"playback_failed": "播放失敗:{{message}}",
|
||||
"audio_load_failed": "音訊加載失敗",
|
||||
"unit_seconds": "s"
|
||||
},
|
||||
"casting": {
|
||||
"title": "揚聲器選角",
|
||||
"auto_assign_title": "自動分配提取的說話者克隆的聲音",
|
||||
"auto_cast": "自動施法",
|
||||
"all_cast": "所有演員",
|
||||
"segments_count": "{{count}} 段",
|
||||
"assign_voice": "指定語音...",
|
||||
"from_video": "🎤 來自影片 ({{name}})",
|
||||
"no_profiles": "尚未儲存語音設定檔。",
|
||||
"preview_voice": "預覽語音"
|
||||
},
|
||||
"checkpoint": {
|
||||
"asr_title": "成績單已準備好",
|
||||
"asr_cta": "翻譯",
|
||||
"asr_hint": "立即修復所有 ASR 錯誤 - 嚴格的措辭可以節省以後的 TTS 嘗試。",
|
||||
"translate_title": "翻譯準備就緒",
|
||||
"translate_cta": "生成配音",
|
||||
"translate_hint": "瀏覽目標文字。超長線路速度提升;您也可以直接編輯。",
|
||||
"done_title": "配音完成",
|
||||
"done_hint": "檢查時序和同步比率。調整任何線條並點擊“Regen Changed”即可快速部分重做。",
|
||||
"segment_one": "{{count}} 段",
|
||||
"segment_other": "{{count}} 段",
|
||||
"dismiss_title": "關閉 — 在重新載入之前不會再次出現在該階段"
|
||||
},
|
||||
"compare": {
|
||||
"title": "A/B 語音比較",
|
||||
"close": "近距離比較",
|
||||
"desc": "並排比較兩個聲音來做出選角決定。應用程式保持互動。",
|
||||
"test_phrase": "測試短語",
|
||||
"voice_a": "聲音A",
|
||||
"voice_b": "聲音B",
|
||||
"select_voice": "— 選擇語音 —",
|
||||
"preset_suffix": "(預設)",
|
||||
"no_audio": "還沒有音訊",
|
||||
"close_btn": "關閉",
|
||||
"comparing": "比較…",
|
||||
"compare_btn": "比較",
|
||||
"preparing_voice": "正在準備語音...",
|
||||
"generating_voice_a": "生成語音...",
|
||||
"generating_voice_b": "生成語音 B...",
|
||||
"comparison_complete": "對比完畢!",
|
||||
"play_failed": "播放失敗:{{message}}"
|
||||
},
|
||||
"models": {
|
||||
"hf_token_set_toast": "HuggingFace 令牌集 — 啟用更快的下載",
|
||||
"hf_token_save_failed": "保存令牌失敗",
|
||||
"install_started": "安裝已開始 — 行中的進度",
|
||||
"delete_confirm": "刪除{{repoId}}?您可以稍後重新安裝。",
|
||||
"delete_confirm_title": "刪除模型",
|
||||
"deleted": "已刪除 {{repoId}}",
|
||||
"reinstall_confirm": "重新安裝{{repoId}}?這將刪除當前副本並再次下載。",
|
||||
"reinstall_confirm_title": "重新安裝模型",
|
||||
"reinstalling": "重新安裝",
|
||||
"recommended_installed": "建議型號已安裝。",
|
||||
"started_downloading_one": "開始下載 {{count}} 模型",
|
||||
"started_downloading_other": "開始下載 {{count}} 模型",
|
||||
"install_failed": "安裝失敗:{{message}}",
|
||||
"removing_cached": "正在刪除快取的修訂版本...",
|
||||
"resolving_metadata": "解析儲存庫元數據",
|
||||
"retry_attempt": "重試嘗試 {{attempt}} — {{error}}",
|
||||
"connecting_hf": "正在連接到 HuggingFace...",
|
||||
"resolving_files_one": "正在解析 {{count}} 檔案...",
|
||||
"resolving_files_other": "正在解析 {{count}} 檔案...",
|
||||
"resolving_files_active": "正在解析 {{count}} 檔案... · {{file}}",
|
||||
"measuring": "測量…",
|
||||
"files_progress": "{{done}}/{{total}} 文件",
|
||||
"install_error": "安裝失敗:{{error}}",
|
||||
"view_on_hf": "在 HuggingFace 上查看",
|
||||
"install_btn": "安裝",
|
||||
"reinstall_btn": "重新安裝",
|
||||
"downloading": "下載",
|
||||
"deleting": "刪除",
|
||||
"working": "工作",
|
||||
"installed": "已安裝",
|
||||
"not_installed": "未安裝",
|
||||
"required_tag": "必填",
|
||||
"delete_btn": "刪除",
|
||||
"hf_token_btn": "HF代幣",
|
||||
"hf_set_title": "設定 HuggingFace 令牌以加快下載速度",
|
||||
"get_token": "獲取代幣 →",
|
||||
"reco_installed_for": "為 {{device}} 安裝的建議捆綁包",
|
||||
"reco_for": "推薦用於 {{device}}",
|
||||
"starting": "開始…",
|
||||
"required_size": "需要 ~{{size}} GB",
|
||||
"all_size": "全部 ~{{size}} GB",
|
||||
"req_tag": "要求",
|
||||
"search_placeholder": "搜尋型號...",
|
||||
"search_label": "搜尋型號",
|
||||
"no_matches": "沒有型號與您的過濾器相符。",
|
||||
"sort_by": "按 {{column}} 排序",
|
||||
"column_model": "型號",
|
||||
"column_role": "角色",
|
||||
"column_size": "尺寸",
|
||||
"column_status": "狀態",
|
||||
"ready_badge": "準備好",
|
||||
"loading_badge": "加載中...",
|
||||
"idle_badge": "空閒",
|
||||
"started_downloading_required_one": "開始下載 {{count}} 所需模型",
|
||||
"started_downloading_required_other": "開始下載 {{count}} 所需模型"
|
||||
},
|
||||
"enterprise_faq": {
|
||||
"q_internal_tools": "我需要內部工具的授權嗎?",
|
||||
"a_internal_tools": "您的員工和承包商的內部使用是 FSL 允許的目的 — 無需許可。當您將 OmniVoice 作為競爭產品或服務(轉售、託管 SaaS、白標)的一部分提供給其他人時,需要商業許可證。",
|
||||
"q_try_before": "我可以在提交之前嘗試一下嗎?",
|
||||
"a_try_before": "是的。完整的應用程式可免費下載並在本地運行,以便在 FSL 下進行評估。當您準備好討論商業部署時,請給我們發送電子郵件,我們將共同解決細節問題。",
|
||||
"q_watermark": "水印呢?",
|
||||
"a_watermark": "預設嵌入不可見的 AudioSeal 水印。商業許可持有者可以在「設定」→「隱私權」中停用它。免費/個人使用始終包含浮水印。",
|
||||
"q_apache": "原始碼會變成 Apache 2.0 嗎?",
|
||||
"a_apache": "是的。每個版本都會在其發布兩週年時自動轉換為 Apache 授權 2.0 版。這意味著今天發布的版本是兩年後的 Apache 2.0,我們不需要採取任何行動 - FSL 保證它不可撤銷。"
|
||||
},
|
||||
"gallery_extra": {
|
||||
"save_prompt": "輸入此語音設定檔的名稱:",
|
||||
"saved_as_profile": "語音已儲存為個人檔案!",
|
||||
"save_profile_failed": "儲存個人資料失敗",
|
||||
"download_failed": "下載失敗:{{message}}",
|
||||
"trim_load_failed": "無法載入音訊進行修剪:{{message}}",
|
||||
"upload_crop_failed": "無法上傳裁剪後的語音:{{message}}"
|
||||
},
|
||||
"dub_workflow": {
|
||||
"preparing_audio": "正在準備音訊...",
|
||||
"preparing_video": "正在準備影片...",
|
||||
"extracting_audio_scenes": "提取音訊和場景...",
|
||||
"transcribing_audio": "正在轉錄音訊...",
|
||||
"transcription_complete": "轉錄完成",
|
||||
"upload_cancelled": "上傳已取消",
|
||||
"upload_failed": "上傳失敗:{{message}}",
|
||||
"downloading_video": "正在下載影片...",
|
||||
"ingested": "攝取{{url}}",
|
||||
"ingest_cancelled": "攝取已取消",
|
||||
"ingest_failed": "URL 擷取失敗:{{message}}",
|
||||
"retry_cancelled": "重試已取消",
|
||||
"transcription_failed": "轉錄失敗:{{message}}",
|
||||
"import_srt_no_job": "首先上传或摄取视频 - 没有附加字幕的作业。",
|
||||
"imported_cues": "從 {{file}} 導入了 {{count}} 提示",
|
||||
"skipped_malformed": "{{count}} 已跳過(格式錯誤)",
|
||||
"dropped_overlap": "{{count}} 掉落(重疊)",
|
||||
"clamped_to_duration": "{{count}} 夾緊到介質長度",
|
||||
"srt_import_failed": "SRT 導入失敗",
|
||||
"cleaned_one": "已清理的 {{count}} 片段",
|
||||
"cleaned_other": "已清理的 {{count}} 碎片",
|
||||
"segments_clean": "段已經乾淨",
|
||||
"cleanup_failed": "清理失敗:{{message}}",
|
||||
"cinematic_no_llm": "电影质量需要 LLM — 设置 TRANSLATE_BASE_URL + TRANSLATE_API_KEY(Ollama 在本地工作)。回落到快速。",
|
||||
"translate_errors": "{{errorCount}}/{{totalCount}} 段失敗:{{firstError}}",
|
||||
"translated_segments": "翻譯的 {{count}} 段 → {{lang}}",
|
||||
"translated_cinematic_suffix": "(電影)",
|
||||
"translation_failed": "翻譯失敗:{{message}}",
|
||||
"regenerating": "正在重新產生 {{count}} 段...",
|
||||
"generating_dub": "生成配音...",
|
||||
"generating_progress": "產生配音... {{current}}/{{total}}",
|
||||
"generation_aborted": "世代中止。",
|
||||
"dubbing_aborted": "配音中止",
|
||||
"generation_stream_ended": "生成流在完成之前結束",
|
||||
"dub_complete": "配音完成",
|
||||
"stop_failed": "未能停下來",
|
||||
"save_first": "请先单击“上传和转录”,以便在保存之前在服务器上处理视频。",
|
||||
"project_saved": "項目已儲存",
|
||||
"project_created": "專案已建立",
|
||||
"save_failed": "儲存失敗:{{message}}",
|
||||
"opened_project": "開放時間:{{name}}",
|
||||
"delete_project_confirm": "刪除這個項目嗎?此操作無法撤銷。",
|
||||
"project_deleted": "項目已刪除",
|
||||
"delete_history_confirm": "刪除該歷史記錄嗎?",
|
||||
"history_deleted": "歷史記錄項目已刪除",
|
||||
"restored_state": "恢復了上一代狀態",
|
||||
"upgrading_preview": "正在將 {{count}} 預覽品質片段升級為完整品質…"
|
||||
},
|
||||
"tts_errors": {
|
||||
"enter_text": "請輸入文字",
|
||||
"upload_or_select": "上傳音訊或選擇語音設定文件",
|
||||
"trim_hint": "音訊為 {{duration}}s — 修剪至 ≤{{max}}s 以獲得最佳克隆效果",
|
||||
"timeout": "產生超時 - 模型可能仍在下載。檢查設定 → 日誌,然後重試。",
|
||||
"error_prefix": "錯誤:{{message}}",
|
||||
"ignored_unsupported": "忽略不支援的指令:{{items}}",
|
||||
"ignored_duplicate": "忽略(類別已設定):{{items}}"
|
||||
},
|
||||
"sharing": {
|
||||
"title": "共享和遠端存取",
|
||||
"help": "將此正在運行的 OmniVoice 實例公開給您的其他計算機,而無需重新啟動它。僅環回是預設設定 - 除非您在此處將其打開,否則不會共享任何內容。",
|
||||
"local_network": "本地網路",
|
||||
"local_help": "使用一次性存取 PIN 在 Wi-Fi/乙太網路上共用。其他設備掃描二維碼或開啟連結。",
|
||||
"ports_title": "港口",
|
||||
"ports_help": "這些是透過啟動時讀取的環境變數所設定的。透過設定變數並重新啟動 OmniVoice 來變更後端或 UI 連接埠。",
|
||||
"backend_port": "後端連接埠",
|
||||
"ui_port": "使用者介面連接埠",
|
||||
"lan_share_port": "LAN共用連接埠",
|
||||
"port_error": "輸入 1024 到 65535 之間的端口",
|
||||
"port_saved": "已儲存 LAN 共用連接埠 — 下次啟用共用時套用",
|
||||
"port_save_failed": "無法儲存連接埠:{{message}}",
|
||||
"saving": "正在儲存...",
|
||||
"ports_note": "後端和 UI 連接埠在重新啟動時會套用。 LAN 共用連接埠將在您下次啟用共用時套用。",
|
||||
"tailscale_title": "Tailscale(私人遠端存取)",
|
||||
"tailscale_checking": "檢查尾鱗...",
|
||||
"tailscale_absent": "未檢測到尾鱗。安裝它即可從私人尾網上的任何地方安全地存取 OmniVoice。",
|
||||
"tailscale_install": "安裝尾秤",
|
||||
"tailscale_running": "尾鱗正在運行。透過您的私人尾網提供 OmniVoice。",
|
||||
"tailscale_not_logged_in": "Tailscale 已安裝但未登入。首先啟動並登入 Tailscale。",
|
||||
"tailscale_enabled": "Tailscale 服務已啟用",
|
||||
"tailscale_enable_failed": "無法啟用 Tailscale",
|
||||
"tailscale_enable_error": "無法啟用 Tailscale:{{message}}",
|
||||
"tailscale_disabled": "尾碟服務已停用",
|
||||
"tailscale_disable_failed": "無法禁用 Tailscale",
|
||||
"tailscale_disable_error": "無法停用 Tailscale:{{message}}",
|
||||
"tailscale_enabling": "啟用...",
|
||||
"tailscale_enable_btn": "啟用 Tailscale 服務",
|
||||
"tailscale_disabling": "禁用...",
|
||||
"tailscale_disable_btn": "停止 Tailscale 服務",
|
||||
"tailscale_copy": "複製連結",
|
||||
"tailscale_open": "在瀏覽器中開啟",
|
||||
"copied": "已複製",
|
||||
"tailscale_qr_alt": "Tailscale URL 的二維碼"
|
||||
},
|
||||
"reportBug": {
|
||||
"label": "報告錯誤",
|
||||
"title": "在浏览器中打开预填充的 GitHub 问题页面。在您单击“提交”之前,不会发送任何内容。"
|
||||
},
|
||||
"app": {
|
||||
"loading": "加載中...",
|
||||
"trimmed_loaded": "已載入修剪後的音頻",
|
||||
"toast_exported": "匯出:{{name}}",
|
||||
"toast_export_failed": "匯出失敗:{{message}}",
|
||||
"toast_open_folder_failed": "無法開啟資料夾:{{message}}",
|
||||
"toast_saving": "正在儲存 {{name}}...",
|
||||
"toast_saved": "已儲存:{{path}}",
|
||||
"toast_save_error": "儲存錯誤:{{message}}",
|
||||
"toast_processing": "正在處理 {{name}}...",
|
||||
"toast_downloaded": "已下載{{name}}",
|
||||
"toast_download_error": "下載錯誤:{{message}}",
|
||||
"toast_upload_first": "請先點擊“上傳和轉錄”,以便在儲存之前在伺服器上處理影片。",
|
||||
"toast_save_failed": "儲存失敗:{{message}}",
|
||||
"toast_opened": "開放時間:{{name}}",
|
||||
"toast_project_deleted": "項目已刪除",
|
||||
"toast_restored_state": "恢復了上一代狀態",
|
||||
"toast_history_deleted": "歷史記錄項目已刪除",
|
||||
"toast_flushed": "刷新 — RAM {{ram}}G · VRAM {{vram}}G{{unloaded}}",
|
||||
"toast_model_unloaded": "· 模型卸載",
|
||||
"toast_flush_failed": "刷新失敗:{{message}}",
|
||||
"toast_project_saved": "項目已儲存",
|
||||
"toast_project_created": "專案已建立"
|
||||
},
|
||||
"update": {
|
||||
"available": "有可用更新 {{version}}",
|
||||
"install": "安裝並重新啟動",
|
||||
"install_hint": "下載更新並重新啟動至新版本",
|
||||
"downloading": "更新中… {{pct}}%",
|
||||
"restart": "重新啟動以更新",
|
||||
"busy": "請先完成配音,再安裝更新。",
|
||||
"whats_new": "有什麼新消息",
|
||||
"failed": "更新失敗",
|
||||
"retry": "重試",
|
||||
"dismiss": "解僱"
|
||||
},
|
||||
"archetypes": {
|
||||
"featured": "精選",
|
||||
@@ -945,5 +1577,25 @@
|
||||
"facet_accent": "口音",
|
||||
"facet_lang": "語言",
|
||||
"facet_whisper": "耳語"
|
||||
},
|
||||
"support": {
|
||||
"tab_support": "支援",
|
||||
"tab_license": "商業許可",
|
||||
"toggle_label": "支持或商業許可",
|
||||
"other_ways": "其他幫助方法",
|
||||
"star_github": "在 GitHub 上加星標",
|
||||
"join_discord": "加入不和諧"
|
||||
},
|
||||
"updates": {
|
||||
"tab": "更新",
|
||||
"up_to_date": "最新·v{{version}}",
|
||||
"check_now": "立即檢查",
|
||||
"releases": "發布",
|
||||
"current": "目前",
|
||||
"prerelease": "預覽",
|
||||
"loading": "正在載入版本...",
|
||||
"none": "未找到任何版本",
|
||||
"load_error": "無法載入版本(離線?)",
|
||||
"retry_load": "重試"
|
||||
}
|
||||
}
|
||||
@@ -1330,7 +1330,30 @@ select:focus:not(:focus-visible) { outline: none; box-shadow: none; }
|
||||
.lp-proj-icon--design { background: rgba(142,192,124,0.1); }
|
||||
.lp-proj-icon--locked { background: rgba(184,187,38,0.1); }
|
||||
.lp-proj-icon--dub { background: rgba(254,128,25,0.1); overflow: hidden; }
|
||||
.lp-proj-icon--file { background: rgba(250,189,47,0.1); }
|
||||
.lp-proj-meta--italic { font-style: italic; }
|
||||
|
||||
/* Recent files (OmniDrive) strip on the Launchpad */
|
||||
.lp-files__head {
|
||||
display: flex; align-items: center; justify-content: space-between; gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.lp-files__head .lp-section-title { margin: 0; }
|
||||
.lp-files__head .lp-section-title::after { display: none; }
|
||||
.lp-view-all {
|
||||
display: inline-flex; align-items: center; gap: 5px; flex-shrink: 0;
|
||||
border: none; background: transparent; cursor: pointer; padding: 2px 4px;
|
||||
font-family: var(--chrome-font-mono); font-size: var(--chrome-label-size);
|
||||
font-weight: 600; text-transform: uppercase; letter-spacing: var(--chrome-label-track);
|
||||
color: var(--chrome-fg-muted); transition: color 0.15s ease;
|
||||
}
|
||||
.lp-view-all:hover { color: #fabd2f; }
|
||||
.lp-files__grid {
|
||||
display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 8px;
|
||||
}
|
||||
.lp-file-card {
|
||||
width: 100%; font: inherit; color: inherit; text-align: left; cursor: pointer;
|
||||
}
|
||||
.lp-locked-badge {
|
||||
font-family: var(--chrome-font-mono);
|
||||
font-size: var(--chrome-label-size);
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Heart, ExternalLink, ArrowLeft, Building2 } from 'lucide-react';
|
||||
import { Button } from '../ui';
|
||||
import { openExternal } from '../api/external';
|
||||
import './DonatePage.css';
|
||||
|
||||
const METHODS = [
|
||||
{
|
||||
id: 'github',
|
||||
label: 'GitHub Sponsors',
|
||||
descriptionKey: 'donate.github_desc',
|
||||
url: 'https://github.com/debpalash',
|
||||
icon: '🐙',
|
||||
},
|
||||
{
|
||||
id: 'kofi',
|
||||
label: 'Ko-fi',
|
||||
descriptionKey: 'donate.coffee_desc',
|
||||
url: 'https://ko-fi.com/debpalash',
|
||||
icon: '☕',
|
||||
},
|
||||
{
|
||||
id: 'paypal',
|
||||
label: 'PayPal',
|
||||
descriptionKey: 'donate.paypal_desc',
|
||||
url: 'https://paypal.me/palashCoder',
|
||||
icon: '💳',
|
||||
},
|
||||
];
|
||||
|
||||
function LinkCard({ method, style }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="donate-card donate-card--link lp-glow-card"
|
||||
style={style}
|
||||
onClick={() => openExternal(method.url)}
|
||||
>
|
||||
<span className="donate-card__glow" aria-hidden="true" />
|
||||
<div className="donate-card__icon">{method.icon}</div>
|
||||
<div className="donate-card__body">
|
||||
<div className="donate-card__label">{method.label}</div>
|
||||
<div className="donate-card__desc">{t(method.descriptionKey)}</div>
|
||||
</div>
|
||||
<div className="donate-card__arrow">
|
||||
<ExternalLink size={14} />
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export default function DonatePage({ onBack, onEnterprise }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="donate-page">
|
||||
{/* Aurora backdrop — same as Launchpad */}
|
||||
<div className="lp-aurora" aria-hidden="true">
|
||||
<span className="lp-aurora__blob lp-aurora__blob--pink" />
|
||||
<span className="lp-aurora__blob lp-aurora__blob--green" />
|
||||
<span className="lp-aurora__blob lp-aurora__blob--amber" />
|
||||
</div>
|
||||
|
||||
{/* Top bar: Back (left) + Commercial License (right) */}
|
||||
<div className="donate-page__topbar">
|
||||
<Button
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
onClick={onBack}
|
||||
leading={<ArrowLeft size={14} />}
|
||||
>
|
||||
{t('donate.back')}
|
||||
</Button>
|
||||
{onEnterprise && (
|
||||
<Button
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
onClick={onEnterprise}
|
||||
leading={<Building2 size={14} />}
|
||||
trailing={<ExternalLink size={12} />}
|
||||
>
|
||||
{t('donate.commercial')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="donate-page__content">
|
||||
{/* Hero */}
|
||||
<div className="donate-hero">
|
||||
<div className="donate-hero__icon-wrap">
|
||||
<Heart size={24} className="donate-hero__heart" />
|
||||
</div>
|
||||
<h2 className="donate-hero__title">
|
||||
{t('donate.hero_title')}
|
||||
<span className="lp-hero__sweep" aria-hidden="true" />
|
||||
</h2>
|
||||
<p className="donate-hero__subtitle">
|
||||
{t('donate.hero_desc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Platforms */}
|
||||
<section className="donate-section">
|
||||
<div className="donate-section__title">
|
||||
<span>{t('donate.platforms')}</span>
|
||||
</div>
|
||||
<div className="donate-grid donate-grid--links">
|
||||
{METHODS.map((m, i) => (
|
||||
<LinkCard
|
||||
key={m.id}
|
||||
method={m}
|
||||
style={{ '--anim-i': i, '--card-hue': '#d3869b' }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="donate-footer">
|
||||
{t('donate.footer')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user