tonhowtf/omniget · warning

$t('common.error') | mapped: ai_not_configured -> $t('downlo

Error message

$t('common.error') | mapped: ai_not_configured -> $t('downloads.sw.not_configured'), translation_mismatch -> $t('downloads.sw.translate_mismatch'), grammar_mismatch -> $t('downloads.sw.grammar_mismatch')

What it means

SubtitleWorkshop's err() is the shared error-mapper used by all invoke call-sites in the component. It takes the raw error (usually a string error code from the Rust backend), falls back to the localized common.error label when the error isn't a string, and maps known codes like ai_not_configured, translation_mismatch and grammar_mismatch to user-facing localized messages. The 'error message' here is that mapping contract, not a throw itself.

Solutions

  1. Extend err()'s mapping table whenever the backend adds a new error code string.
  2. Ensure invoke rejections are plain strings (backend should return string error codes, not throw Error objects).
  3. Verify downloads.sw.* and common.error keys exist in every locale bundle.
  4. Log the unmapped raw error to console so unknown codes can be discovered and mapped.

Example fix

// before
const raw = typeof e === "string" ? e : ($t("common.error") as string);
// after
const raw = typeof e === "string" ? e : ((e as Error)?.message ?? ($t("common.error") as string));
console.warn("unmapped subtitle workshop error:", e);
Defensive patterns

Strategy: type-guard

Validate before calling

const KNOWN_CODES = ["ai_not_configured", "translation_mismatch", "grammar_mismatch"] as const;
// keep backend error-code list exported from a shared module and diff it against KNOWN_CODES in a test

Type guard

function isBackendErrorCode(v: unknown): v is string {
  return typeof v === "string" && v.length > 0 && !v.includes(" ");
}

Try / catch

function err(e: unknown) {
  const raw = typeof e === "string"
    ? e
    : ((e as Error)?.message ?? ($t("common.error") as string));
  if (!KNOWN_CODES.includes(raw as any)) console.warn("unmapped error code:", raw);
  showToast("error", mapCodeToMessage(raw));
}

Prevention

When it happens

Trigger: err(e) is invoked from loadFile, loadMedia, translateAll, grammarFix or saveAs when a Tauri invoke rejects with a raw code string; if the code is 'ai_not_configured', 'translation_mismatch' or 'grammar_mismatch' it is translated, any non-string error falls back to the generic $t('common.error') text.

Common situations: Backend adds a new error code that err() doesn't map, so users see a generic message; an invoke rejects with an Error object instead of a string, losing the specific reason; translations for the sw.* keys missing from a locale file show raw keys.

Related errors


AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12). Data as JSON: /api/errors/508769ed07db2fb1. Report an issue: GitHub.

Appendix: source

Thrown at src/components/downloads/SubtitleWorkshop.svelte:27

  type Cue = { start_ms: number; end_ms: number; text: string };

  let cues = $state<Cue[]>([]);
  let loadedPath = $state("");
  let busy = $state(false);
  let shiftMs = $state(0);
  let findText = $state("");
  let replaceText = $state("");
  let targetLang = $state("");
  let grammarStyle = $state<"original" | "formal" | "casual">("original");
  let tpOldA = $state("");
  let tpNewA = $state("");
  let tpOldB = $state("");
  let tpNewB = $state("");
  let peaks = $state<number[]>([]);
  let shots = $state<number[]>([]);
  let durationMs = $derived(cues.length ? cues[cues.length - 1].end_ms : 0);

  function err(e: unknown) {
    const raw = typeof e === "string" ? e : ($t("common.error") as string);
    const msg =
      raw === "ai_not_configured"
        ? ($t("downloads.sw.not_configured") as string)
        : raw === "translation_mismatch"
          ? ($t("downloads.sw.translate_mismatch") as string)
          : raw === "grammar_mismatch"
            ? ($t("downloads.sw.grammar_mismatch") as string)
            : raw;
    showToast("error", msg);
  }

  function fmt(ms: number): string {
    const s = Math.floor(ms / 1000);
    const h = Math.floor(s / 3600);
    const m = Math.floor((s % 3600) / 60);
    const sec = s % 60;
    const milli = ms % 1000;

View on GitHub (pinned to 8600b91f42)