/* =========================================================
   i18n — Chinese / English switching
   ---------------------------------------------------------
   UI            : dictionary of interface strings { key: {zh, en} }
   LangContext   : current language ("zh" | "en")
   useLangState(): App-level state hook (persists to localStorage)
   useLang()     : read current language inside any component
   useT()        : returns t(key) -> localized UI string
   pick(lang,obj): localize a content object { zh, en } (or a plain string)
   ========================================================= */

const LANG_KEY = "voice_book_lang";

const LangContext = React.createContext("zh");

function useLangState() {
  const [lang, setLangRaw] = React.useState(() => {
    try { return localStorage.getItem(LANG_KEY) || "zh"; } catch (e) { return "zh"; }
  });
  React.useEffect(() => {
    document.documentElement.lang = lang === "zh" ? "zh-Hans" : "en";
    document.documentElement.setAttribute("data-lang", lang);
  }, [lang]);
  const setLang = (l) => {
    try { localStorage.setItem(LANG_KEY, l); } catch (e) {}
    setLangRaw(l);
  };
  const toggle = () => setLang(lang === "zh" ? "en" : "zh");
  return [lang, setLang, toggle];
}

function useLang() { return React.useContext(LangContext); }

function useT() {
  const lang = React.useContext(LangContext);
  return (key) => {
    const e = UI[key];
    if (e === undefined) return key;
    if (typeof e === "object") return e[lang] !== undefined ? e[lang] : e.zh;
    return e;
  };
}

// Localize a { zh, en } object; a bare string is returned as-is.
function pick(lang, obj) {
  if (obj == null) return "";
  if (typeof obj === "string") return obj;
  return obj[lang] !== undefined ? obj[lang] : (obj.zh !== undefined ? obj.zh : obj.en);
}
// The "other" language for a content object (used for the sub-title lines).
function other(lang, obj) { return pick(lang === "zh" ? "en" : "zh", obj); }

// "{n} 章" -> fmt("{n} 章", {n: 3})
function fmt(str, map) {
  return String(str).replace(/\{(\w+)\}/g, (_, k) => (map[k] !== undefined ? map[k] : `{${k}}`));
}

const UI = {
  /* nav */
  nav_home:    { zh: "首页", en: "Home" },
  nav_about:   { zh: "关于", en: "About" },
  nav_modules: { zh: "模块", en: "Modules" },
  lang_title:  { zh: "切换语言", en: "Switch language" },
  theme_title: { zh: "切换主题", en: "Toggle theme" },

  /* hero */
  hero_badge:  { zh: "AI 语音客服 · 中英双语 · 从一通没人接的电话到一条能自己成交的语音链路", en: "Voice AI customer service · bilingual · from an unanswered call to a pipeline that books by itself" },
  hero_l1:     { zh: "先把一通真实的电话跑一遍,", en: "First run one real call," },
  hero_l2a:    { zh: "再讲清它为什么", en: "then explain why it" },
  hero_l2b:    { zh: "必须这样接。", en: "has to be wired this way." },
  hero_sub:    {
    zh: "一家按摩、推拿、足疗或养生门店,每天都在丢电话:前台在给客人做理疗,电话响六声没人接,这一单就去了隔壁。AI 语音客服能接住它,但要接得住,你得知道:电话为什么只有 8 kHz、机器凭什么认为你说完了、为什么 5% 的字错率意味着一半的手机号要重听一遍、合成的声音为什么一开口就露馅、大模型不知道价格时会怎么编、一秒钟的延迟预算该怎么分给六个环节、平台该买谁的、以及哪几条合规红线一踩项目就下线。共 {M} 个模块、{C} 章,每章开头是一个可交互的声控台,结尾是 Python / 配置话术 / 对接部署 三个视角的真实代码——所有数字现算,所有代码照着能落地。",
    en: "A massage, tuina, foot-reflexology or wellness shop loses calls every day: the front desk is mid-treatment, the phone rings six times unanswered, and that booking went next door. Voice AI can catch it — but to catch it you need to know why a phone line is only 8 kHz, how the machine decides you have finished speaking, why 5% character error means half your phone numbers must be repeated, why a synthetic voice gives itself away in the first sentence, what an LLM invents when it does not know your prices, how to split a one-second latency budget across six stages, which platform to buy, and which compliance lines end the project the moment you cross them. {M} modules, {C} chapters — each opening with an interactive voice bench and closing with real code from three angles: Python, configuration and scripts, and integration and deployment. Every number is computed live; every listing is meant to ship.",
  },
  cta_start:   { zh: "从第一章开始 →", en: "Start chapter 1 →" },
  cta_howto:   { zh: "如何使用", en: "How it works" },
  cta_roadmap: { zh: "查看路线图", en: "See the roadmap" },

  meta_modules:  { zh: "模块", en: "Modules" },
  meta_chapters: { zh: "章", en: "Chapters" },
  meta_demos:    { zh: "声控台", en: "Benches" },
  meta_hours:    { zh: "小时", en: "Hours" },

  your_progress: { zh: "你的进度", en: "Your progress" },
  synced:        { zh: "本地保存 · 无需登录", en: "Saved locally · no login" },

  /* sections */
  sec01:       { zh: "学习路线图", en: "Learning roadmap" },
  sec01_aside: { zh: "从一本客服账走到一条能上线的语音链路", en: "From a service ledger to a pipeline you can ship" },
  sec02:       { zh: "课程模块", en: "Course modules" },
  sec02_aside: { zh: "点击进入任意模块", en: "Click any module to enter" },
  sec03:       { zh: "学习方法", en: "The method" },
  sec03_aside: { zh: "先跑一遍场景,再读解释,最后自己写一遍", en: "Run the scenario, read why, then write it yourself" },

  rm_notstarted: { zh: "未开始", en: "Not started" },
  rm_done:       { zh: "已完成", en: "Done" },

  hours_unit:    { zh: "小时", en: "h" },
  modules_count: { zh: "章", en: "chapters" },
  done_word:     { zh: "完成", en: "done" },
  enter_word:    { zh: "进入 →", en: "Enter →" },

  phil1_zh: { zh: "先跑一遍场景", en: "Run the scenario" },
  phil1_b:  {
    zh: "每一章开头是一个可交互声控台:把门店的日话量和前台人数拖起来,看高峰时段的呼损率怎么从 5% 冲到 30%、每月漏掉多少钱;把尾点静音阈值从 300 毫秒拖到 1200 毫秒,看误截断率和等待延迟两条曲线怎么交叉;把字错率从 3% 调到 8%,看 11 位手机号一次听对的概率怎么从 71% 掉到 40%;把延迟预算里的六段拖动,看那条 1.4 秒的链路怎么被三个重叠优化压到 0.75 秒;把知识库的召回率调低,看幻觉率怎么爬上来、又怎么被一句「查不到就说不知道」摁回去。语音系统的直觉不是背参数背出来的,是被自己调出来的曲线打服的。",
    en: "Every chapter opens with an interactive voice bench: drag daily call volume and front-desk headcount and watch peak blocking climb from 5% to 30% and the monthly money it costs; drag the tail-silence threshold from 300 ms to 1200 ms and watch false cuts cross average wait; move character error from 3% to 8% and watch the chance of hearing an eleven-digit number correctly fall from 71% to 40%; move the six stages of the latency budget and watch a 1.4-second pipeline compress to 0.75 with three overlaps enabled; lower retrieval recall and watch hallucination climb, then watch one fallback rule push it back down. Intuition about voice systems is not memorised from parameter tables — it is beaten into you by curves you moved yourself.",
  },
  phil2_zh: { zh: "再读解释", en: "Read the explanation" },
  phil2_b:  {
    zh: "声控台背后是机制:奈奎斯特为什么让电话永远听不清擦音、CTC 的独立性假设为什么会写出读不通的句子、RNN-T 凭什么既能流式又能建模语言、为什么非自回归解码快得多、尾点阈值为什么是个概率问题而不是常数、神经声码器把频谱还原成波形的那一步为什么最吃算力、流式合成的缓冲欠载是怎么发生的、RAG 的召回率和幻觉率之间是什么因果、重试没有幂等键为什么会变成两条预约、回声消除失效时系统为什么会把自己打断、以及为什么端到端语音模型体验更好却更难审。「解释」把每个设计选择的代价和适用边界讲清楚。",
    en: "Behind each bench sits a mechanism: why Nyquist means a phone line can never hear fricatives clearly; why CTC's independence assumption writes sentences that do not read; how RNN-T streams and models language at the same time; why non-autoregressive decoding is so much faster; why the endpoint threshold is a probability problem rather than a constant; why turning a spectrogram back into a waveform is the most compute-hungry step; how buffer underrun happens in streaming synthesis; what causally connects retrieval recall to hallucination; why a retry without an idempotency key becomes two bookings; why a system with failing echo cancellation interrupts itself; and why end-to-end speech models feel better and audit worse. The explanation gives every design choice its price and its boundary.",
  },
  phil3_zh: { zh: "最后自己写一遍", en: "Then write it yourself" },
  phil3_b:  {
    zh: "每章结尾是同一件事的三个视角:Python 是能跑的实现(流式 ASR 的 WebSocket 客户端、Silero VAD 的端点判定、流式 TTS 的分块播放、工具调用的幂等键、RAG 的检索与兜底);配置与话术是那几行真正决定行为的东西(热词表、SSML、系统提示词、意图与工具的 JSON Schema、质检评分规则);对接与部署是把它接到真实世界的那一层(FreeSWITCH 拨号方案、SIP 与 WebSocket 网关、Docker 与 GPU 部署、微信与平台 IM 的回调、通话记录表结构)。代码都是可读长度的完整片段,不是伪代码——照着能在自己的机器上跑起来。练习会把你赶到真环境里:录十通自己门店的电话、算一次 CER、把尾点阈值调三档听差别、把知识库删掉一条看模型怎么编。",
    en: "Every chapter closes with the same thing from three angles. Python is a runnable implementation (a streaming ASR WebSocket client, Silero VAD endpointing, chunked streaming TTS playback, idempotency keys for tool calls, retrieval with a fallback). Configuration and scripts are the handful of lines that actually decide behaviour (hotword lists, SSML, the system prompt, JSON schemas for intents and tools, QA scoring rules). Integration and deployment is the layer that touches the real world (a FreeSWITCH dialplan, a SIP-to-WebSocket gateway, Docker and GPU deployment, WeChat and platform IM callbacks, the call-record schema). The listings are complete, readable fragments rather than pseudocode, so you can run them on your own machine. The exercises push you into a real environment: record ten of your own shop's calls, compute a CER, listen to three endpoint thresholds back to back, delete one knowledge entry and watch what the model invents.",
  },

  footer_tag:  { zh: "ears & voices · AI 语音客服 · 2026", en: "ears & voices · voice AI customer service · 2026" },
  footer_sync: { zh: "进度本地保存", en: "progress saved locally" },

  /* module page */
  bc_home:    { zh: "首页", en: "Home" },
  bc_modules: { zh: "模块", en: "Modules" },
  module_word:{ zh: "模块", en: "Module" },
  of_word:    { zh: "共", en: "of" },
  m_meta_chapters: { zh: "章数", en: "Chapters" },
  m_meta_hours:    { zh: "预计小时", en: "Est. hours" },
  m_meta_level:    { zh: "难度", en: "Level" },
  m_meta_progress: { zh: "进度", en: "Progress" },
  chapter_list: { zh: "本模块章节", en: "Chapters in this module" },
  click_enter:  { zh: "点击任意章节进入", en: "Click a chapter to enter" },
  no_prereq:    { zh: "无先修", en: "No prereq" },
  prereq_n:     { zh: "{n} 项先修", en: "{n} prereq" },
  not_found_m:  { zh: "未找到该模块。", en: "Module not found." },

  diff_1: { zh: "入门", en: "Intro" },
  diff_2: { zh: "进阶", en: "Core" },
  diff_3: { zh: "挑战", en: "Advanced" },

  /* chapter page */
  ch_sec_intro:   { zh: "本章导读", en: "Overview" },
  ch_sec_obj:     { zh: "学习目标", en: "Objectives" },
  ch_sec_outline: { zh: "内容大纲", en: "Outline" },
  ch_sec_viz:     { zh: "声控台 · 可交互模拟", en: "The voice bench · live model" },
  ch_sec_notes:   { zh: "解释 · 核心讲义", en: "The explanation · core notes" },
  ch_sec_code:    { zh: "代码 · Python / 配置话术 / 对接部署", en: "Code · Python / config & scripts / integration" },
  viz_hint:     { zh: "改动参数,亲眼看呼损率、字错率、延迟、召回率、成本和满意度如何联动;这里的每一个数字都是现算的,大胆试。", en: "Change the parameters and watch blocking, error rate, latency, recall, cost and satisfaction move together; every number here is computed live — experiment freely." },
  code_hint:    { zh: "切换标签看同一件事的三个视角:Python 实现、配置与话术、对接与部署;点右上角复制。代码为可读而写,去掉了无关样板,但接口名、参数与关键常量都是真的。", en: "Switch tabs for three angles on the same thing: the Python implementation, the configuration and scripts, and the integration or deployment. Copy from the corner button. The listings are written to be read — unrelated boilerplate is trimmed — but every API name, parameter and constant that matters is real." },
  key_badge:    { zh: "重点", en: "Key" },
  code_badge:   { zh: "动手", en: "Hands-on" },
  copy_btn:     { zh: "复制", en: "Copy" },
  copied_btn:   { zh: "已复制", en: "Copied" },
  langs_word:   { zh: "代码", en: "Code" },
  loading_notes:{ zh: "正在加载讲义……", en: "Loading notes…" },
  notes_soon:   { zh: "本章深度讲义正在编写中。以上目标与大纲即为本章脉络,先把上面的声控台玩透。", en: "The deep-dive notes for this chapter are being written. Use the objectives and outline above as your map — and play with the bench first." },
  back_to:      { zh: "返回", en: "Back to" },
  est_word:     { zh: "预计", en: "Est." },
  level_word:   { zh: "难度", en: "Level" },
  props_word:   { zh: "关键概念", en: "Key concepts" },
  mark_done_btn:{ zh: "标记为已完成", en: "Mark as complete" },
  marked_done:  { zh: "已完成", en: "Completed" },
  not_found_c:  { zh: "未找到该章节。", en: "Chapter not found." },

  /* about */
  about_kicker: { zh: "关于本站", en: "About" },
  about_q:      { zh: "为什么写这门课?", en: "Why this course?" },
  about_sub:    { zh: "把 AI 语音客服讲成「声控台 + 解释 + 代码」,而不是一张画满方框和箭头的产品架构图。", en: "Teach voice AI as a bench, an explanation and code — not as a product diagram full of boxes and arrows." },
  about_h1: { zh: "这是什么", en: "What this is" },
  about_p1: {
    zh: "一门「AI 语音客服」的自学课程,共 {M} 个模块、{C} 章,面向要给按摩、推拿、足疗、养生门店(或任何靠电话预约吃饭的本地生意)做 AI 客服的人:可能是想省一个前台的老板,也可能是接了这个单子的开发者。第一个模块讲生意本身:用排队模型算出漏接的电话每月值多少钱,把十二类咨询分出哪些该交给 AI,再把一通预约电话拆成轮次看连乘成功率。然后是听清——采样率与电话窄带、CTC/RNN-T/Paraformer/Whisper 四种识别模型、VAD 与端点检测、CER 评测与热词优化。接着是说好——声学模型与声码器、文本规范化与 SSML、音色克隆与它的法律边界、流式合成的首包延迟。中段是会想——意图与槽位、门店知识库与 RAG、工具调用的幂等、话术人设与行业护栏。再往上是接得住:端到端延迟预算、打断与回声消除、级联与端到端语音模型的取舍。最后三个模块回到现实:平台选型与成本账、电话与微信等渠道接入、以及质检闭环、合规红线和落地路线图,并以两个完整案例收口——一本三店连锁的账,和一通 2 分 17 秒电话的逐跳复盘。",
    en: "A self-study course on voice AI customer service — {M} modules, {C} chapters — for whoever has to build it for a massage, tuina, foot-reflexology or wellness business (or any local business that lives on phone bookings): the owner hoping to save a front-desk salary, or the developer who took the job. The first module is the business itself: a queueing model for what missed calls cost per month, a triage of twelve enquiry types into what should and should not go to the AI, and a booking call decomposed into turns whose success probabilities multiply. Then hearing — sample rates and narrowband telephony, the four recognition architectures (CTC, RNN-T, Paraformer, Whisper), VAD and endpointing, CER evaluation and hotword tuning. Then speaking — acoustic models and vocoders, text normalisation and SSML, voice cloning and its legal boundary, streaming synthesis and first-packet latency. The middle is thinking — intents and slots, the shop knowledge base and RAG, idempotent tool calls, persona and industry guardrails. Above that, holding the line: the end-to-end latency budget, barge-in and echo cancellation, and the cascade-versus-end-to-end decision. The last three modules return to reality: platform selection and the cost ledger, phone and WeChat channel integration, and the QA loop, compliance red lines and rollout roadmap — closing with two complete cases, a three-shop chain's ledger and a hop-by-hop post-mortem of one 2-minute-17-second call.",
  },
  about_p1b: { zh: "全部内容中英双语,代码为 Python / 配置与话术 / 对接与部署三个视角,支持浅色/深色主题,进度保存在你自己的浏览器里,无需注册。合规章节为工程与管理提示,不构成法律意见。", en: "Everything is bilingual (Chinese/English); code comes from three angles — Python, configuration and scripts, integration and deployment. Light and dark themes, progress kept in your own browser, no signup. The compliance chapter is engineering and management guidance, not legal advice." },
  about_h2: { zh: "「声控台与解释」是什么意思", en: "What 'the voice bench & the explanation' means" },
  about_p2: {
    zh: "语音 AI 的材料通常走两个极端:要么是论文和厂商文档(准确,但在你需要一个直觉的时候毫无帮助),要么是「十分钟搭一个 AI 电话机器人」(搭起来了,然后在第一次抢话、第一次把手机号听错、第一次编造价格时全线崩溃)。本站每章拆成三块:「声控台」是可交互模拟器,呼损率、字错率、槽位准确率、延迟瀑布、召回率、幻觉率、成本瀑布、回收期全部在你的浏览器里现算,改一个参数就看到后果;「解释」讲清机制、代价和边界;「代码」给出 Python、配置话术和对接部署三个视角,让你能立刻在自己的机器上跑一遍。",
    en: "Material on voice AI runs to two extremes: papers and vendor docs (accurate, and no help at all when what you need is an intuition), or 'build a phone bot in ten minutes' (built, then comprehensively broken the first time it talks over someone, mishears a phone number, or invents a price). Every chapter here splits into three. The voice bench is a live model — blocking, character error, slot accuracy, the latency waterfall, recall, hallucination, the cost waterfall and payback, all computed in your browser — where one changed parameter shows the consequence. The explanation covers the mechanism, its price and its boundary. The code gives three angles — Python, configuration and scripts, integration and deployment — so you can run it today.",
  },
  about_h3: { zh: "技术是手段,那通电话才是目的", en: "The technology is a means; the call is the point" },
  about_p3: {
    zh: "很多教程把语音 AI 讲成一份能力清单:ASR 是什么、TTS 怎么调、大模型怎么接。但没有人为了用 ASR 而用 ASR——你用它,是因为晚上八点有个顾客想约明天的肩颈理疗,而你的前台正在忙。本书把顺序倒过来:先给你一个真实场景——一通没人接的电话、一个被听错的手机号、一次抢话、一个被编出来的价格、一条被打爆的外呼名单——再让你看清是哪个模型、哪个参数、哪条规则在决定成败。学完你记住的不是「RNN-T 有几层」,而是「尾点阈值该定在哪、热词该加哪些名字、查不到时该说什么」。",
    en: "Many tutorials teach voice AI as a capability list: what ASR is, how to tune TTS, how to wire an LLM. But nobody uses ASR for its own sake — you use it because at eight in the evening a customer wants tomorrow's neck-and-shoulder session and your front desk is busy. This book inverts the order: it hands you a real scenario first — an unanswered call, a misheard phone number, an interruption, an invented price, an outbound list dialled into the ground — and then shows exactly which model, which parameter and which rule decides the outcome. What you leave with is not how many layers RNN-T has, but where to set the endpoint threshold, which names belong in the hotword list, and what to say when you cannot find the answer.",
  },
  about_h4: { zh: "如何使用", en: "How to use it" },
  about_p4: {
    zh: "按路线图学:生意与账 → 听清(ASR) → 说好(TTS) → 会想(大模型) → 接得住(实时链路) → 平台与选型 → 渠道接入 → 运营合规增长 → 两个案例。如果你已经在做这件事、只想解决具体问题,可以直奔模块 V(延迟与打断)和模块 VIII(合规)——但请至少先读完 AS3(端点检测)和 BR2(知识库与幻觉),因为「什么时候该开口」和「不知道时该说什么」这两件事,是所有体验问题的地基。老板视角的读者可以只读模块 I、VI 和 IX,那三块不需要写代码。每章的练习都要求你离开本站动手:录十通真实通话、算一次字错率、调三档尾点阈值、删一条知识看模型怎么编。本书写于 2026 年,厂商与价格变化很快,凡涉及具体产品与单价的部分请以官网为准;合规部分为工程提示,不构成法律意见。",
    en: "Follow the roadmap: the business and its ledger → hearing (ASR) → speaking (TTS) → thinking (the LLM) → holding the line (realtime) → platforms and selection → channels → operations, compliance and growth → two cases. If you are already building this and want a specific problem solved, jump to module V (latency and barge-in) and module VIII (compliance) — but read AS3 (endpointing) and BR2 (knowledge and hallucination) first, because when to speak and what to say when you do not know are the foundation under every experience problem. Readers wearing the owner's hat can read modules I, VI and IX alone; none of those require code. Every chapter's exercises send you away from this site: record ten real calls, compute a character error rate, listen to three endpoint thresholds, delete one knowledge entry and watch what gets invented. This book was written in 2026; vendors and prices move quickly, so treat any specific product or unit price as indicative and check the vendor's own pages. The compliance material is engineering guidance, not legal advice.",
  },
};

window.LangContext = LangContext;
window.useLangState = useLangState;
window.useLang = useLang;
window.useT = useT;
window.pick = pick;
window.other = other;
window.fmt = fmt;
window.UI = UI;
