reasonix 0.23.1 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dashboard/dist/app.js +1723 -574
  2. package/dashboard/dist/app.js.map +1 -1
  3. package/dist/cli/.-3G6VX5S7.js +327 -0
  4. package/dist/cli/.-6YRPB2C7.js +329 -0
  5. package/dist/cli/.-6YRPB2C7.js.map +1 -0
  6. package/dist/cli/.-EYSVINK3.js +317 -0
  7. package/dist/cli/.-EYSVINK3.js.map +1 -0
  8. package/dist/cli/banner-demo-QKOPDSTL.js +77 -0
  9. package/dist/cli/banner-demo-QKOPDSTL.js.map +1 -0
  10. package/dist/cli/card-demo-5TVXJISK.js +944 -0
  11. package/dist/cli/card-demo-5TVXJISK.js.map +1 -0
  12. package/dist/cli/chunk-2H7UOFLK.js +11 -0
  13. package/dist/cli/chunk-2H7UOFLK.js.map +1 -0
  14. package/dist/cli/chunk-BGTXZKNY.js +197 -0
  15. package/dist/cli/chunk-BGTXZKNY.js.map +1 -0
  16. package/dist/cli/chunk-JHXQDL7B.js +2056 -0
  17. package/dist/cli/chunk-JHXQDL7B.js.map +1 -0
  18. package/dist/cli/flicker-demo-MOB6GAW4.js +165 -0
  19. package/dist/cli/flicker-demo-MOB6GAW4.js.map +1 -0
  20. package/dist/cli/index.js +3654 -2035
  21. package/dist/cli/index.js.map +1 -1
  22. package/dist/cli/preview-4FOEEE5G.js +224 -0
  23. package/dist/cli/preview-4FOEEE5G.js.map +1 -0
  24. package/dist/cli/{prompt-YUL7CYKY.js → prompt-VZQ2CPID.js} +2 -1
  25. package/dist/cli/prompt-VZQ2CPID.js.map +1 -0
  26. package/dist/cli/renderer-demo-2BIGEV2T.js +95 -0
  27. package/dist/cli/renderer-demo-2BIGEV2T.js.map +1 -0
  28. package/dist/cli/select-demo-OA5N34BJ.js +107 -0
  29. package/dist/cli/select-demo-OA5N34BJ.js.map +1 -0
  30. package/dist/cli/stress-demo-I7XRPQMM.js +211 -0
  31. package/dist/cli/stress-demo-I7XRPQMM.js.map +1 -0
  32. package/dist/index.d.ts +3 -2
  33. package/dist/index.js +22 -9
  34. package/dist/index.js.map +1 -1
  35. package/package.json +6 -4
  36. /package/dist/cli/{prompt-YUL7CYKY.js.map → .-3G6VX5S7.js.map} +0 -0
@@ -1355,8 +1355,8 @@ var require_core = __commonJS({
1355
1355
  result.secondBest = secondBest;
1356
1356
  return result;
1357
1357
  }
1358
- function updateClassName(element, currentLang, resultLang) {
1359
- const language = currentLang && aliases[currentLang] || resultLang;
1358
+ function updateClassName(element, currentLang2, resultLang) {
1359
+ const language = currentLang2 && aliases[currentLang2] || resultLang;
1360
1360
  element.classList.add("hljs");
1361
1361
  element.classList.add(`language-${language}`);
1362
1362
  }
@@ -14118,8 +14118,8 @@ function join(tables, nullModes) {
14118
14118
  }
14119
14119
  let xVals = /* @__PURE__ */ new Set();
14120
14120
  for (let ti = 0; ti < tables.length; ti++) {
14121
- let t4 = tables[ti];
14122
- let xs = t4[0];
14121
+ let t5 = tables[ti];
14122
+ let xs = t5[0];
14123
14123
  let len = xs.length;
14124
14124
  for (let i3 = 0; i3 < len; i3++)
14125
14125
  xVals.add(xs[i3]);
@@ -14130,10 +14130,10 @@ function join(tables, nullModes) {
14130
14130
  for (let i3 = 0; i3 < alignedLen; i3++)
14131
14131
  xIdxs.set(data[0][i3], i3);
14132
14132
  for (let ti = 0; ti < tables.length; ti++) {
14133
- let t4 = tables[ti];
14134
- let xs = t4[0];
14135
- for (let si = 1; si < t4.length; si++) {
14136
- let ys = t4[si];
14133
+ let t5 = tables[ti];
14134
+ let xs = t5[0];
14135
+ for (let si = 1; si < t5.length; si++) {
14136
+ let ys = t5[si];
14137
14137
  let yVals = Array(alignedLen).fill(void 0);
14138
14138
  let nullMode = nullModes ? nullModes[ti][si] : NULL_RETAIN;
14139
14139
  let nullIdxs = [];
@@ -14580,7 +14580,7 @@ function orient(u3, seriesIdx, cb) {
14580
14580
  const data = mode == 2 ? u3._data[seriesIdx] : u3._data;
14581
14581
  const scales = u3.scales;
14582
14582
  const bbox = u3.bbox;
14583
- let dx = data[0], dy = mode == 2 ? data[1] : data[seriesIdx], sx = mode == 2 ? scales[series.facets[0].scale] : scales[u3.series[0].scale], sy = mode == 2 ? scales[series.facets[1].scale] : scales[series.scale], l3 = bbox.left, t4 = bbox.top, w3 = bbox.width, h3 = bbox.height, H = u3.valToPosH, V2 = u3.valToPosV;
14583
+ let dx = data[0], dy = mode == 2 ? data[1] : data[seriesIdx], sx = mode == 2 ? scales[series.facets[0].scale] : scales[u3.series[0].scale], sy = mode == 2 ? scales[series.facets[1].scale] : scales[series.scale], l3 = bbox.left, t5 = bbox.top, w3 = bbox.width, h3 = bbox.height, H = u3.valToPosH, V2 = u3.valToPosV;
14584
14584
  return sx.ori == 0 ? cb(
14585
14585
  series,
14586
14586
  dx,
@@ -14590,7 +14590,7 @@ function orient(u3, seriesIdx, cb) {
14590
14590
  H,
14591
14591
  V2,
14592
14592
  l3,
14593
- t4,
14593
+ t5,
14594
14594
  w3,
14595
14595
  h3,
14596
14596
  moveToH,
@@ -14606,7 +14606,7 @@ function orient(u3, seriesIdx, cb) {
14606
14606
  sy,
14607
14607
  V2,
14608
14608
  H,
14609
- t4,
14609
+ t5,
14610
14610
  l3,
14611
14611
  h3,
14612
14612
  w3,
@@ -18019,12 +18019,12 @@ var init_uPlot_esm = __esm({
18019
18019
  });
18020
18020
 
18021
18021
  // node_modules/htm/dist/htm.module.js
18022
- var n = function(t4, s3, r3, e3) {
18022
+ var n = function(t5, s3, r3, e3) {
18023
18023
  var u3;
18024
18024
  s3[0] = 0;
18025
18025
  for (var h3 = 1; h3 < s3.length; h3++) {
18026
18026
  var p3 = s3[h3++], a3 = s3[h3] ? (s3[0] |= p3 ? 1 : 2, r3[s3[h3++]]) : s3[++h3];
18027
- 3 === p3 ? e3[0] = a3 : 4 === p3 ? e3[1] = Object.assign(e3[1] || {}, a3) : 5 === p3 ? (e3[1] = e3[1] || {})[s3[++h3]] = a3 : 6 === p3 ? e3[1][s3[++h3]] += a3 + "" : p3 ? (u3 = t4.apply(a3, n(t4, a3, r3, ["", null])), e3.push(u3), a3[0] ? s3[0] |= 2 : (s3[h3 - 2] = 0, s3[h3] = u3)) : e3.push(a3);
18027
+ 3 === p3 ? e3[0] = a3 : 4 === p3 ? e3[1] = Object.assign(e3[1] || {}, a3) : 5 === p3 ? (e3[1] = e3[1] || {})[s3[++h3]] = a3 : 6 === p3 ? e3[1][s3[++h3]] += a3 + "" : p3 ? (u3 = t5.apply(a3, n(t5, a3, r3, ["", null])), e3.push(u3), a3[0] ? s3[0] |= 2 : (s3[h3 - 2] = 0, s3[h3] = u3)) : e3.push(a3);
18028
18028
  }
18029
18029
  return e3;
18030
18030
  };
@@ -18032,11 +18032,11 @@ var t = /* @__PURE__ */ new Map();
18032
18032
  function htm_module_default(s3) {
18033
18033
  var r3 = t.get(this);
18034
18034
  return r3 || (r3 = /* @__PURE__ */ new Map(), t.set(this, r3)), (r3 = n(this, r3.get(s3) || (r3.set(s3, r3 = (function(n3) {
18035
- for (var t4, s4, r4 = 1, e3 = "", u3 = "", h3 = [0], p3 = function(n4) {
18035
+ for (var t5, s4, r4 = 1, e3 = "", u3 = "", h3 = [0], p3 = function(n4) {
18036
18036
  1 === r4 && (n4 || (e3 = e3.replace(/^\s*\n\s*|\s*\n\s*$/g, ""))) ? h3.push(0, n4, e3) : 3 === r4 && (n4 || e3) ? (h3.push(3, n4, e3), r4 = 2) : 2 === r4 && "..." === e3 && n4 ? h3.push(4, n4, 0) : 2 === r4 && e3 && !n4 ? h3.push(5, 0, true, e3) : r4 >= 5 && ((e3 || !n4 && 5 === r4) && (h3.push(r4, 0, e3, s4), r4 = 6), n4 && (h3.push(r4, n4, 0, s4), r4 = 6)), e3 = "";
18037
18037
  }, a3 = 0; a3 < n3.length; a3++) {
18038
18038
  a3 && (1 === r4 && p3(), p3(a3));
18039
- for (var l3 = 0; l3 < n3[a3].length; l3++) t4 = n3[a3][l3], 1 === r4 ? "<" === t4 ? (p3(), h3 = [h3], r4 = 3) : e3 += t4 : 4 === r4 ? "--" === e3 && ">" === t4 ? (r4 = 1, e3 = "") : e3 = t4 + e3[0] : u3 ? t4 === u3 ? u3 = "" : e3 += t4 : '"' === t4 || "'" === t4 ? u3 = t4 : ">" === t4 ? (p3(), r4 = 1) : r4 && ("=" === t4 ? (r4 = 5, s4 = e3, e3 = "") : "/" === t4 && (r4 < 5 || ">" === n3[a3][l3 + 1]) ? (p3(), 3 === r4 && (h3 = h3[0]), r4 = h3, (h3 = h3[0]).push(2, 0, r4), r4 = 0) : " " === t4 || " " === t4 || "\n" === t4 || "\r" === t4 ? (p3(), r4 = 2) : e3 += t4), 3 === r4 && "!--" === e3 && (r4 = 4, h3 = h3[0]);
18039
+ for (var l3 = 0; l3 < n3[a3].length; l3++) t5 = n3[a3][l3], 1 === r4 ? "<" === t5 ? (p3(), h3 = [h3], r4 = 3) : e3 += t5 : 4 === r4 ? "--" === e3 && ">" === t5 ? (r4 = 1, e3 = "") : e3 = t5 + e3[0] : u3 ? t5 === u3 ? u3 = "" : e3 += t5 : '"' === t5 || "'" === t5 ? u3 = t5 : ">" === t5 ? (p3(), r4 = 1) : r4 && ("=" === t5 ? (r4 = 5, s4 = e3, e3 = "") : "/" === t5 && (r4 < 5 || ">" === n3[a3][l3 + 1]) ? (p3(), 3 === r4 && (h3 = h3[0]), r4 = h3, (h3 = h3[0]).push(2, 0, r4), r4 = 0) : " " === t5 || " " === t5 || "\n" === t5 || "\r" === t5 ? (p3(), r4 = 2) : e3 += t5), 3 === r4 && "!--" === e3 && (r4 = 4, h3 = h3[0]);
18040
18040
  }
18041
18041
  return p3(), h3;
18042
18042
  })(s3)), r3), arguments, [])).length > 1 ? r3 : r3[0];
@@ -18067,14 +18067,14 @@ function w(n3) {
18067
18067
  var l3 = n3.parentNode;
18068
18068
  l3 && l3.removeChild(n3);
18069
18069
  }
18070
- function _(l3, u3, t4) {
18070
+ function _(l3, u3, t5) {
18071
18071
  var i3, o3, r3, f3 = {};
18072
18072
  for (r3 in u3) "key" == r3 ? i3 = u3[r3] : "ref" == r3 ? o3 = u3[r3] : f3[r3] = u3[r3];
18073
- if (arguments.length > 2 && (f3.children = arguments.length > 3 ? n2.call(arguments, 2) : t4), "function" == typeof l3 && null != l3.defaultProps) for (r3 in l3.defaultProps) void 0 === f3[r3] && (f3[r3] = l3.defaultProps[r3]);
18073
+ if (arguments.length > 2 && (f3.children = arguments.length > 3 ? n2.call(arguments, 2) : t5), "function" == typeof l3 && null != l3.defaultProps) for (r3 in l3.defaultProps) void 0 === f3[r3] && (f3[r3] = l3.defaultProps[r3]);
18074
18074
  return g(l3, f3, i3, o3, null);
18075
18075
  }
18076
- function g(n3, t4, i3, o3, r3) {
18077
- var f3 = { type: n3, props: t4, key: i3, ref: o3, __k: null, __: null, __b: 0, __e: null, __d: void 0, __c: null, constructor: void 0, __v: null == r3 ? ++u : r3, __i: -1, __u: 0 };
18076
+ function g(n3, t5, i3, o3, r3) {
18077
+ var f3 = { type: n3, props: t5, key: i3, ref: o3, __k: null, __: null, __b: 0, __e: null, __d: void 0, __c: null, constructor: void 0, __v: null == r3 ? ++u : r3, __i: -1, __u: 0 };
18078
18078
  return null == r3 && null != l.vnode && l.vnode(f3), f3;
18079
18079
  }
18080
18080
  function k(n3) {
@@ -18102,24 +18102,24 @@ function M(n3) {
18102
18102
  (!n3.__d && (n3.__d = true) && i.push(n3) && !P.__r++ || o !== l.debounceRendering) && ((o = l.debounceRendering) || r)(P);
18103
18103
  }
18104
18104
  function P() {
18105
- var n3, u3, t4, o3, r3, e3, c3, s3;
18106
- for (i.sort(f); n3 = i.shift(); ) n3.__d && (u3 = i.length, o3 = void 0, e3 = (r3 = (t4 = n3).__v).__e, c3 = [], s3 = [], t4.__P && ((o3 = d({}, r3)).__v = r3.__v + 1, l.vnode && l.vnode(o3), O(t4.__P, o3, r3, t4.__n, t4.__P.namespaceURI, 32 & r3.__u ? [e3] : null, c3, null == e3 ? x(r3) : e3, !!(32 & r3.__u), s3), o3.__v = r3.__v, o3.__.__k[o3.__i] = o3, j(c3, o3, s3), o3.__e != e3 && C(o3)), i.length > u3 && i.sort(f));
18105
+ var n3, u3, t5, o3, r3, e3, c3, s3;
18106
+ for (i.sort(f); n3 = i.shift(); ) n3.__d && (u3 = i.length, o3 = void 0, e3 = (r3 = (t5 = n3).__v).__e, c3 = [], s3 = [], t5.__P && ((o3 = d({}, r3)).__v = r3.__v + 1, l.vnode && l.vnode(o3), O(t5.__P, o3, r3, t5.__n, t5.__P.namespaceURI, 32 & r3.__u ? [e3] : null, c3, null == e3 ? x(r3) : e3, !!(32 & r3.__u), s3), o3.__v = r3.__v, o3.__.__k[o3.__i] = o3, j(c3, o3, s3), o3.__e != e3 && C(o3)), i.length > u3 && i.sort(f));
18107
18107
  P.__r = 0;
18108
18108
  }
18109
- function S(n3, l3, u3, t4, i3, o3, r3, f3, e3, c3, s3) {
18110
- var a3, v3, y3, d3, w3, _4 = t4 && t4.__k || p, g2 = l3.length;
18109
+ function S(n3, l3, u3, t5, i3, o3, r3, f3, e3, c3, s3) {
18110
+ var a3, v3, y3, d3, w3, _4 = t5 && t5.__k || p, g2 = l3.length;
18111
18111
  for (u3.__d = e3, $(u3, l3, _4), e3 = u3.__d, a3 = 0; a3 < g2; a3++) null != (y3 = u3.__k[a3]) && "boolean" != typeof y3 && "function" != typeof y3 && (v3 = -1 === y3.__i ? h : _4[y3.__i] || h, y3.__i = a3, O(n3, y3, v3, i3, o3, r3, f3, e3, c3, s3), d3 = y3.__e, y3.ref && v3.ref != y3.ref && (v3.ref && N(v3.ref, null, y3), s3.push(y3.ref, y3.__c || d3, y3)), null == w3 && null != d3 && (w3 = d3), 65536 & y3.__u || v3.__k === y3.__k ? (e3 && !e3.isConnected && (e3 = x(v3)), e3 = I(y3, e3, n3)) : "function" == typeof y3.type && void 0 !== y3.__d ? e3 = y3.__d : d3 && (e3 = d3.nextSibling), y3.__d = void 0, y3.__u &= -196609);
18112
18112
  u3.__d = e3, u3.__e = w3;
18113
18113
  }
18114
18114
  function $(n3, l3, u3) {
18115
- var t4, i3, o3, r3, f3, e3 = l3.length, c3 = u3.length, s3 = c3, a3 = 0;
18116
- for (n3.__k = [], t4 = 0; t4 < e3; t4++) r3 = t4 + a3, null != (i3 = n3.__k[t4] = null == (i3 = l3[t4]) || "boolean" == typeof i3 || "function" == typeof i3 ? null : "string" == typeof i3 || "number" == typeof i3 || "bigint" == typeof i3 || i3.constructor == String ? g(null, i3, null, null, null) : y(i3) ? g(k, { children: i3 }, null, null, null) : void 0 === i3.constructor && i3.__b > 0 ? g(i3.type, i3.props, i3.key, i3.ref ? i3.ref : null, i3.__v) : i3) ? (i3.__ = n3, i3.__b = n3.__b + 1, f3 = L(i3, u3, r3, s3), i3.__i = f3, o3 = null, -1 !== f3 && (s3--, (o3 = u3[f3]) && (o3.__u |= 131072)), null == o3 || null === o3.__v ? (-1 == f3 && a3--, "function" != typeof i3.type && (i3.__u |= 65536)) : f3 !== r3 && (f3 === r3 + 1 ? a3++ : f3 > r3 ? s3 > e3 - r3 ? a3 += f3 - r3 : a3-- : f3 < r3 ? f3 == r3 - 1 && (a3 = f3 - r3) : a3 = 0, f3 !== t4 + a3 && (i3.__u |= 65536))) : (o3 = u3[r3]) && null == o3.key && o3.__e && 0 == (131072 & o3.__u) && (o3.__e == n3.__d && (n3.__d = x(o3)), V(o3, o3, false), u3[r3] = null, s3--);
18117
- if (s3) for (t4 = 0; t4 < c3; t4++) null != (o3 = u3[t4]) && 0 == (131072 & o3.__u) && (o3.__e == n3.__d && (n3.__d = x(o3)), V(o3, o3));
18115
+ var t5, i3, o3, r3, f3, e3 = l3.length, c3 = u3.length, s3 = c3, a3 = 0;
18116
+ for (n3.__k = [], t5 = 0; t5 < e3; t5++) r3 = t5 + a3, null != (i3 = n3.__k[t5] = null == (i3 = l3[t5]) || "boolean" == typeof i3 || "function" == typeof i3 ? null : "string" == typeof i3 || "number" == typeof i3 || "bigint" == typeof i3 || i3.constructor == String ? g(null, i3, null, null, null) : y(i3) ? g(k, { children: i3 }, null, null, null) : void 0 === i3.constructor && i3.__b > 0 ? g(i3.type, i3.props, i3.key, i3.ref ? i3.ref : null, i3.__v) : i3) ? (i3.__ = n3, i3.__b = n3.__b + 1, f3 = L(i3, u3, r3, s3), i3.__i = f3, o3 = null, -1 !== f3 && (s3--, (o3 = u3[f3]) && (o3.__u |= 131072)), null == o3 || null === o3.__v ? (-1 == f3 && a3--, "function" != typeof i3.type && (i3.__u |= 65536)) : f3 !== r3 && (f3 === r3 + 1 ? a3++ : f3 > r3 ? s3 > e3 - r3 ? a3 += f3 - r3 : a3-- : f3 < r3 ? f3 == r3 - 1 && (a3 = f3 - r3) : a3 = 0, f3 !== t5 + a3 && (i3.__u |= 65536))) : (o3 = u3[r3]) && null == o3.key && o3.__e && 0 == (131072 & o3.__u) && (o3.__e == n3.__d && (n3.__d = x(o3)), V(o3, o3, false), u3[r3] = null, s3--);
18117
+ if (s3) for (t5 = 0; t5 < c3; t5++) null != (o3 = u3[t5]) && 0 == (131072 & o3.__u) && (o3.__e == n3.__d && (n3.__d = x(o3)), V(o3, o3));
18118
18118
  }
18119
18119
  function I(n3, l3, u3) {
18120
- var t4, i3;
18120
+ var t5, i3;
18121
18121
  if ("function" == typeof n3.type) {
18122
- for (t4 = n3.__k, i3 = 0; t4 && i3 < t4.length; i3++) t4[i3] && (t4[i3].__ = n3, l3 = I(t4[i3], l3, u3));
18122
+ for (t5 = n3.__k, i3 = 0; t5 && i3 < t5.length; i3++) t5[i3] && (t5[i3].__ = n3, l3 = I(t5[i3], l3, u3));
18123
18123
  return l3;
18124
18124
  }
18125
18125
  n3.__e != l3 && (u3.insertBefore(n3.__e, l3 || null), l3 = n3.__e);
@@ -18128,10 +18128,10 @@ function I(n3, l3, u3) {
18128
18128
  } while (null != l3 && 8 === l3.nodeType);
18129
18129
  return l3;
18130
18130
  }
18131
- function L(n3, l3, u3, t4) {
18131
+ function L(n3, l3, u3, t5) {
18132
18132
  var i3 = n3.key, o3 = n3.type, r3 = u3 - 1, f3 = u3 + 1, e3 = l3[u3];
18133
18133
  if (null === e3 || e3 && i3 == e3.key && o3 === e3.type && 0 == (131072 & e3.__u)) return u3;
18134
- if (t4 > (null != e3 && 0 == (131072 & e3.__u) ? 1 : 0)) for (; r3 >= 0 || f3 < l3.length; ) {
18134
+ if (t5 > (null != e3 && 0 == (131072 & e3.__u) ? 1 : 0)) for (; r3 >= 0 || f3 < l3.length; ) {
18135
18135
  if (r3 >= 0) {
18136
18136
  if ((e3 = l3[r3]) && 0 == (131072 & e3.__u) && i3 == e3.key && o3 === e3.type) return r3;
18137
18137
  r3--;
@@ -18146,14 +18146,14 @@ function L(n3, l3, u3, t4) {
18146
18146
  function T(n3, l3, u3) {
18147
18147
  "-" === l3[0] ? n3.setProperty(l3, null == u3 ? "" : u3) : n3[l3] = null == u3 ? "" : "number" != typeof u3 || v.test(l3) ? u3 : u3 + "px";
18148
18148
  }
18149
- function A(n3, l3, u3, t4, i3) {
18149
+ function A(n3, l3, u3, t5, i3) {
18150
18150
  var o3;
18151
18151
  n: if ("style" === l3) if ("string" == typeof u3) n3.style.cssText = u3;
18152
18152
  else {
18153
- if ("string" == typeof t4 && (n3.style.cssText = t4 = ""), t4) for (l3 in t4) u3 && l3 in u3 || T(n3.style, l3, "");
18154
- if (u3) for (l3 in u3) t4 && u3[l3] === t4[l3] || T(n3.style, l3, u3[l3]);
18153
+ if ("string" == typeof t5 && (n3.style.cssText = t5 = ""), t5) for (l3 in t5) u3 && l3 in u3 || T(n3.style, l3, "");
18154
+ if (u3) for (l3 in u3) t5 && u3[l3] === t5[l3] || T(n3.style, l3, u3[l3]);
18155
18155
  }
18156
- else if ("o" === l3[0] && "n" === l3[1]) o3 = l3 !== (l3 = l3.replace(/(PointerCapture)$|Capture$/i, "$1")), l3 = l3.toLowerCase() in n3 || "onFocusOut" === l3 || "onFocusIn" === l3 ? l3.toLowerCase().slice(2) : l3.slice(2), n3.l || (n3.l = {}), n3.l[l3 + o3] = u3, u3 ? t4 ? u3.u = t4.u : (u3.u = e, n3.addEventListener(l3, o3 ? s : c, o3)) : n3.removeEventListener(l3, o3 ? s : c, o3);
18156
+ else if ("o" === l3[0] && "n" === l3[1]) o3 = l3 !== (l3 = l3.replace(/(PointerCapture)$|Capture$/i, "$1")), l3 = l3.toLowerCase() in n3 || "onFocusOut" === l3 || "onFocusIn" === l3 ? l3.toLowerCase().slice(2) : l3.slice(2), n3.l || (n3.l = {}), n3.l[l3 + o3] = u3, u3 ? t5 ? u3.u = t5.u : (u3.u = e, n3.addEventListener(l3, o3 ? s : c, o3)) : n3.removeEventListener(l3, o3 ? s : c, o3);
18157
18157
  else {
18158
18158
  if ("http://www.w3.org/2000/svg" == i3) l3 = l3.replace(/xlink(H|:h)/, "h").replace(/sName$/, "s");
18159
18159
  else if ("width" != l3 && "height" != l3 && "href" != l3 && "list" != l3 && "form" != l3 && "tabIndex" != l3 && "download" != l3 && "rowSpan" != l3 && "colSpan" != l3 && "role" != l3 && l3 in n3) try {
@@ -18167,22 +18167,22 @@ function A(n3, l3, u3, t4, i3) {
18167
18167
  function F(n3) {
18168
18168
  return function(u3) {
18169
18169
  if (this.l) {
18170
- var t4 = this.l[u3.type + n3];
18170
+ var t5 = this.l[u3.type + n3];
18171
18171
  if (null == u3.t) u3.t = e++;
18172
- else if (u3.t < t4.u) return;
18173
- return t4(l.event ? l.event(u3) : u3);
18172
+ else if (u3.t < t5.u) return;
18173
+ return t5(l.event ? l.event(u3) : u3);
18174
18174
  }
18175
18175
  };
18176
18176
  }
18177
- function O(n3, u3, t4, i3, o3, r3, f3, e3, c3, s3) {
18177
+ function O(n3, u3, t5, i3, o3, r3, f3, e3, c3, s3) {
18178
18178
  var a3, h3, p3, v3, w3, _4, g2, m2, x3, C3, M3, P2, $2, I2, H, L2 = u3.type;
18179
18179
  if (void 0 !== u3.constructor) return null;
18180
- 128 & t4.__u && (c3 = !!(32 & t4.__u), r3 = [e3 = u3.__e = t4.__e]), (a3 = l.__b) && a3(u3);
18180
+ 128 & t5.__u && (c3 = !!(32 & t5.__u), r3 = [e3 = u3.__e = t5.__e]), (a3 = l.__b) && a3(u3);
18181
18181
  n: if ("function" == typeof L2) try {
18182
- if (m2 = u3.props, x3 = (a3 = L2.contextType) && i3[a3.__c], C3 = a3 ? x3 ? x3.props.value : a3.__ : i3, t4.__c ? g2 = (h3 = u3.__c = t4.__c).__ = h3.__E : ("prototype" in L2 && L2.prototype.render ? u3.__c = h3 = new L2(m2, C3) : (u3.__c = h3 = new b(m2, C3), h3.constructor = L2, h3.render = q), x3 && x3.sub(h3), h3.props = m2, h3.state || (h3.state = {}), h3.context = C3, h3.__n = i3, p3 = h3.__d = true, h3.__h = [], h3._sb = []), null == h3.__s && (h3.__s = h3.state), null != L2.getDerivedStateFromProps && (h3.__s == h3.state && (h3.__s = d({}, h3.__s)), d(h3.__s, L2.getDerivedStateFromProps(m2, h3.__s))), v3 = h3.props, w3 = h3.state, h3.__v = u3, p3) null == L2.getDerivedStateFromProps && null != h3.componentWillMount && h3.componentWillMount(), null != h3.componentDidMount && h3.__h.push(h3.componentDidMount);
18182
+ if (m2 = u3.props, x3 = (a3 = L2.contextType) && i3[a3.__c], C3 = a3 ? x3 ? x3.props.value : a3.__ : i3, t5.__c ? g2 = (h3 = u3.__c = t5.__c).__ = h3.__E : ("prototype" in L2 && L2.prototype.render ? u3.__c = h3 = new L2(m2, C3) : (u3.__c = h3 = new b(m2, C3), h3.constructor = L2, h3.render = q), x3 && x3.sub(h3), h3.props = m2, h3.state || (h3.state = {}), h3.context = C3, h3.__n = i3, p3 = h3.__d = true, h3.__h = [], h3._sb = []), null == h3.__s && (h3.__s = h3.state), null != L2.getDerivedStateFromProps && (h3.__s == h3.state && (h3.__s = d({}, h3.__s)), d(h3.__s, L2.getDerivedStateFromProps(m2, h3.__s))), v3 = h3.props, w3 = h3.state, h3.__v = u3, p3) null == L2.getDerivedStateFromProps && null != h3.componentWillMount && h3.componentWillMount(), null != h3.componentDidMount && h3.__h.push(h3.componentDidMount);
18183
18183
  else {
18184
- if (null == L2.getDerivedStateFromProps && m2 !== v3 && null != h3.componentWillReceiveProps && h3.componentWillReceiveProps(m2, C3), !h3.__e && (null != h3.shouldComponentUpdate && false === h3.shouldComponentUpdate(m2, h3.__s, C3) || u3.__v === t4.__v)) {
18185
- for (u3.__v !== t4.__v && (h3.props = m2, h3.state = h3.__s, h3.__d = false), u3.__e = t4.__e, u3.__k = t4.__k, u3.__k.forEach(function(n4) {
18184
+ if (null == L2.getDerivedStateFromProps && m2 !== v3 && null != h3.componentWillReceiveProps && h3.componentWillReceiveProps(m2, C3), !h3.__e && (null != h3.shouldComponentUpdate && false === h3.shouldComponentUpdate(m2, h3.__s, C3) || u3.__v === t5.__v)) {
18185
+ for (u3.__v !== t5.__v && (h3.props = m2, h3.state = h3.__s, h3.__d = false), u3.__e = t5.__e, u3.__k = t5.__k, u3.__k.forEach(function(n4) {
18186
18186
  n4 && (n4.__ = u3);
18187
18187
  }), M3 = 0; M3 < h3._sb.length; M3++) h3.__h.push(h3._sb[M3]);
18188
18188
  h3._sb = [], h3.__h.length && f3.push(h3);
@@ -18198,16 +18198,16 @@ function O(n3, u3, t4, i3, o3, r3, f3, e3, c3, s3) {
18198
18198
  } else do {
18199
18199
  h3.__d = false, P2 && P2(u3), a3 = h3.render(h3.props, h3.state, h3.context), h3.state = h3.__s;
18200
18200
  } while (h3.__d && ++$2 < 25);
18201
- h3.state = h3.__s, null != h3.getChildContext && (i3 = d(d({}, i3), h3.getChildContext())), p3 || null == h3.getSnapshotBeforeUpdate || (_4 = h3.getSnapshotBeforeUpdate(v3, w3)), S(n3, y(H = null != a3 && a3.type === k && null == a3.key ? a3.props.children : a3) ? H : [H], u3, t4, i3, o3, r3, f3, e3, c3, s3), h3.base = u3.__e, u3.__u &= -161, h3.__h.length && f3.push(h3), g2 && (h3.__E = h3.__ = null);
18201
+ h3.state = h3.__s, null != h3.getChildContext && (i3 = d(d({}, i3), h3.getChildContext())), p3 || null == h3.getSnapshotBeforeUpdate || (_4 = h3.getSnapshotBeforeUpdate(v3, w3)), S(n3, y(H = null != a3 && a3.type === k && null == a3.key ? a3.props.children : a3) ? H : [H], u3, t5, i3, o3, r3, f3, e3, c3, s3), h3.base = u3.__e, u3.__u &= -161, h3.__h.length && f3.push(h3), g2 && (h3.__E = h3.__ = null);
18202
18202
  } catch (n4) {
18203
- u3.__v = null, c3 || null != r3 ? (u3.__e = e3, u3.__u |= c3 ? 160 : 32, r3[r3.indexOf(e3)] = null) : (u3.__e = t4.__e, u3.__k = t4.__k), l.__e(n4, u3, t4);
18203
+ u3.__v = null, c3 || null != r3 ? (u3.__e = e3, u3.__u |= c3 ? 160 : 32, r3[r3.indexOf(e3)] = null) : (u3.__e = t5.__e, u3.__k = t5.__k), l.__e(n4, u3, t5);
18204
18204
  }
18205
- else null == r3 && u3.__v === t4.__v ? (u3.__k = t4.__k, u3.__e = t4.__e) : u3.__e = z(t4.__e, u3, t4, i3, o3, r3, f3, c3, s3);
18205
+ else null == r3 && u3.__v === t5.__v ? (u3.__k = t5.__k, u3.__e = t5.__e) : u3.__e = z(t5.__e, u3, t5, i3, o3, r3, f3, c3, s3);
18206
18206
  (a3 = l.diffed) && a3(u3);
18207
18207
  }
18208
- function j(n3, u3, t4) {
18208
+ function j(n3, u3, t5) {
18209
18209
  u3.__d = void 0;
18210
- for (var i3 = 0; i3 < t4.length; i3++) N(t4[i3], t4[++i3], t4[++i3]);
18210
+ for (var i3 = 0; i3 < t5.length; i3++) N(t5[i3], t5[++i3], t5[++i3]);
18211
18211
  l.__c && l.__c(u3, n3), n3.some(function(u4) {
18212
18212
  try {
18213
18213
  n3 = u4.__h, u4.__h = [], n3.some(function(n4) {
@@ -18218,8 +18218,8 @@ function j(n3, u3, t4) {
18218
18218
  }
18219
18219
  });
18220
18220
  }
18221
- function z(l3, u3, t4, i3, o3, r3, f3, e3, c3) {
18222
- var s3, a3, p3, v3, d3, _4, g2, m2 = t4.props, k3 = u3.props, b2 = u3.type;
18221
+ function z(l3, u3, t5, i3, o3, r3, f3, e3, c3) {
18222
+ var s3, a3, p3, v3, d3, _4, g2, m2 = t5.props, k3 = u3.props, b2 = u3.type;
18223
18223
  if ("svg" === b2 ? o3 = "http://www.w3.org/2000/svg" : "math" === b2 ? o3 = "http://www.w3.org/1998/Math/MathML" : o3 || (o3 = "http://www.w3.org/1999/xhtml"), null != r3) {
18224
18224
  for (s3 = 0; s3 < r3.length; s3++) if ((d3 = r3[s3]) && "setAttribute" in d3 == !!b2 && (b2 ? d3.localName === b2 : 3 === d3.nodeType)) {
18225
18225
  l3 = d3, r3[s3] = null;
@@ -18232,7 +18232,7 @@ function z(l3, u3, t4, i3, o3, r3, f3, e3, c3) {
18232
18232
  }
18233
18233
  if (null === b2) m2 === k3 || e3 && l3.data === k3 || (l3.data = k3);
18234
18234
  else {
18235
- if (r3 = r3 && n2.call(l3.childNodes), m2 = t4.props || h, !e3 && null != r3) for (m2 = {}, s3 = 0; s3 < l3.attributes.length; s3++) m2[(d3 = l3.attributes[s3]).name] = d3.value;
18235
+ if (r3 = r3 && n2.call(l3.childNodes), m2 = t5.props || h, !e3 && null != r3) for (m2 = {}, s3 = 0; s3 < l3.attributes.length; s3++) m2[(d3 = l3.attributes[s3]).name] = d3.value;
18236
18236
  for (s3 in m2) if (d3 = m2[s3], "children" == s3) ;
18237
18237
  else if ("dangerouslySetInnerHTML" == s3) p3 = d3;
18238
18238
  else if ("key" !== s3 && !(s3 in k3)) {
@@ -18241,19 +18241,19 @@ function z(l3, u3, t4, i3, o3, r3, f3, e3, c3) {
18241
18241
  }
18242
18242
  for (s3 in k3) d3 = k3[s3], "children" == s3 ? v3 = d3 : "dangerouslySetInnerHTML" == s3 ? a3 = d3 : "value" == s3 ? _4 = d3 : "checked" == s3 ? g2 = d3 : "key" === s3 || e3 && "function" != typeof d3 || m2[s3] === d3 || A(l3, s3, d3, m2[s3], o3);
18243
18243
  if (a3) e3 || p3 && (a3.__html === p3.__html || a3.__html === l3.innerHTML) || (l3.innerHTML = a3.__html), u3.__k = [];
18244
- else if (p3 && (l3.innerHTML = ""), S(l3, y(v3) ? v3 : [v3], u3, t4, i3, "foreignObject" === b2 ? "http://www.w3.org/1999/xhtml" : o3, r3, f3, r3 ? r3[0] : t4.__k && x(t4, 0), e3, c3), null != r3) for (s3 = r3.length; s3--; ) null != r3[s3] && w(r3[s3]);
18244
+ else if (p3 && (l3.innerHTML = ""), S(l3, y(v3) ? v3 : [v3], u3, t5, i3, "foreignObject" === b2 ? "http://www.w3.org/1999/xhtml" : o3, r3, f3, r3 ? r3[0] : t5.__k && x(t5, 0), e3, c3), null != r3) for (s3 = r3.length; s3--; ) null != r3[s3] && w(r3[s3]);
18245
18245
  e3 || (s3 = "value", void 0 !== _4 && (_4 !== l3[s3] || "progress" === b2 && !_4 || "option" === b2 && _4 !== m2[s3]) && A(l3, s3, _4, m2[s3], o3), s3 = "checked", void 0 !== g2 && g2 !== l3[s3] && A(l3, s3, g2, m2[s3], o3));
18246
18246
  }
18247
18247
  return l3;
18248
18248
  }
18249
- function N(n3, u3, t4) {
18249
+ function N(n3, u3, t5) {
18250
18250
  try {
18251
18251
  "function" == typeof n3 ? n3(u3) : n3.current = u3;
18252
18252
  } catch (n4) {
18253
- l.__e(n4, t4);
18253
+ l.__e(n4, t5);
18254
18254
  }
18255
18255
  }
18256
- function V(n3, u3, t4) {
18256
+ function V(n3, u3, t5) {
18257
18257
  var i3, o3;
18258
18258
  if (l.unmount && l.unmount(n3), (i3 = n3.ref) && (i3.current && i3.current !== n3.__e || N(i3, null, u3)), null != (i3 = n3.__c)) {
18259
18259
  if (i3.componentWillUnmount) try {
@@ -18263,19 +18263,19 @@ function V(n3, u3, t4) {
18263
18263
  }
18264
18264
  i3.base = i3.__P = null;
18265
18265
  }
18266
- if (i3 = n3.__k) for (o3 = 0; o3 < i3.length; o3++) i3[o3] && V(i3[o3], u3, t4 || "function" != typeof n3.type);
18267
- t4 || null == n3.__e || w(n3.__e), n3.__c = n3.__ = n3.__e = n3.__d = void 0;
18266
+ if (i3 = n3.__k) for (o3 = 0; o3 < i3.length; o3++) i3[o3] && V(i3[o3], u3, t5 || "function" != typeof n3.type);
18267
+ t5 || null == n3.__e || w(n3.__e), n3.__c = n3.__ = n3.__e = n3.__d = void 0;
18268
18268
  }
18269
18269
  function q(n3, l3, u3) {
18270
18270
  return this.constructor(n3, u3);
18271
18271
  }
18272
- function B(u3, t4, i3) {
18272
+ function B(u3, t5, i3) {
18273
18273
  var o3, r3, f3, e3;
18274
- l.__ && l.__(u3, t4), r3 = (o3 = "function" == typeof i3) ? null : i3 && i3.__k || t4.__k, f3 = [], e3 = [], O(t4, u3 = (!o3 && i3 || t4).__k = _(k, null, [u3]), r3 || h, h, t4.namespaceURI, !o3 && i3 ? [i3] : r3 ? null : t4.firstChild ? n2.call(t4.childNodes) : null, f3, !o3 && i3 ? i3 : r3 ? r3.__e : t4.firstChild, o3, e3), j(f3, u3, e3);
18274
+ l.__ && l.__(u3, t5), r3 = (o3 = "function" == typeof i3) ? null : i3 && i3.__k || t5.__k, f3 = [], e3 = [], O(t5, u3 = (!o3 && i3 || t5).__k = _(k, null, [u3]), r3 || h, h, t5.namespaceURI, !o3 && i3 ? [i3] : r3 ? null : t5.firstChild ? n2.call(t5.childNodes) : null, f3, !o3 && i3 ? i3 : r3 ? r3.__e : t5.firstChild, o3, e3), j(f3, u3, e3);
18275
18275
  }
18276
- n2 = p.slice, l = { __e: function(n3, l3, u3, t4) {
18276
+ n2 = p.slice, l = { __e: function(n3, l3, u3, t5) {
18277
18277
  for (var i3, o3, r3; l3 = l3.__; ) if ((i3 = l3.__c) && !i3.__) try {
18278
- if ((o3 = i3.constructor) && null != o3.getDerivedStateFromError && (i3.setState(o3.getDerivedStateFromError(n3)), r3 = i3.__d), null != i3.componentDidCatch && (i3.componentDidCatch(n3, t4 || {}), r3 = i3.__d), r3) return i3.__E = i3;
18278
+ if ((o3 = i3.constructor) && null != o3.getDerivedStateFromError && (i3.setState(o3.getDerivedStateFromError(n3)), r3 = i3.__d), null != i3.componentDidCatch && (i3.componentDidCatch(n3, t5 || {}), r3 = i3.__d), r3) return i3.__E = i3;
18279
18279
  } catch (l4) {
18280
18280
  n3 = l4;
18281
18281
  }
@@ -18306,8 +18306,8 @@ var l2 = e2.diffed;
18306
18306
  var m = e2.__c;
18307
18307
  var s2 = e2.unmount;
18308
18308
  var d2 = e2.__;
18309
- function h2(n3, t4) {
18310
- e2.__h && e2.__h(r2, n3, o2 || t4), o2 = 0;
18309
+ function h2(n3, t5) {
18310
+ e2.__h && e2.__h(r2, n3, o2 || t5), o2 = 0;
18311
18311
  var u3 = r2.__H || (r2.__H = { __: [], __h: [] });
18312
18312
  return n3 >= u3.__.length && u3.__.push({ __V: c2 }), u3.__[n3];
18313
18313
  }
@@ -18317,33 +18317,33 @@ function p2(n3) {
18317
18317
  function y2(n3, u3, i3) {
18318
18318
  var o3 = h2(t3++, 2);
18319
18319
  if (o3.t = n3, !o3.__c && (o3.__ = [i3 ? i3(u3) : D(void 0, u3), function(n4) {
18320
- var t4 = o3.__N ? o3.__N[0] : o3.__[0], r3 = o3.t(t4, n4);
18321
- t4 !== r3 && (o3.__N = [r3, o3.__[1]], o3.__c.setState({}));
18320
+ var t5 = o3.__N ? o3.__N[0] : o3.__[0], r3 = o3.t(t5, n4);
18321
+ t5 !== r3 && (o3.__N = [r3, o3.__[1]], o3.__c.setState({}));
18322
18322
  }], o3.__c = r2, !r2.u)) {
18323
- var f3 = function(n4, t4, r3) {
18323
+ var f3 = function(n4, t5, r3) {
18324
18324
  if (!o3.__c.__H) return true;
18325
18325
  var u4 = o3.__c.__H.__.filter(function(n5) {
18326
18326
  return !!n5.__c;
18327
18327
  });
18328
18328
  if (u4.every(function(n5) {
18329
18329
  return !n5.__N;
18330
- })) return !c3 || c3.call(this, n4, t4, r3);
18330
+ })) return !c3 || c3.call(this, n4, t5, r3);
18331
18331
  var i4 = false;
18332
18332
  return u4.forEach(function(n5) {
18333
18333
  if (n5.__N) {
18334
- var t5 = n5.__[0];
18335
- n5.__ = n5.__N, n5.__N = void 0, t5 !== n5.__[0] && (i4 = true);
18334
+ var t6 = n5.__[0];
18335
+ n5.__ = n5.__N, n5.__N = void 0, t6 !== n5.__[0] && (i4 = true);
18336
18336
  }
18337
- }), !(!i4 && o3.__c.props === n4) && (!c3 || c3.call(this, n4, t4, r3));
18337
+ }), !(!i4 && o3.__c.props === n4) && (!c3 || c3.call(this, n4, t5, r3));
18338
18338
  };
18339
18339
  r2.u = true;
18340
18340
  var c3 = r2.shouldComponentUpdate, e3 = r2.componentWillUpdate;
18341
- r2.componentWillUpdate = function(n4, t4, r3) {
18341
+ r2.componentWillUpdate = function(n4, t5, r3) {
18342
18342
  if (this.__e) {
18343
18343
  var u4 = c3;
18344
- c3 = void 0, f3(n4, t4, r3), c3 = u4;
18344
+ c3 = void 0, f3(n4, t5, r3), c3 = u4;
18345
18345
  }
18346
- e3 && e3.call(this, n4, t4, r3);
18346
+ e3 && e3.call(this, n4, t5, r3);
18347
18347
  }, r2.shouldComponentUpdate = f3;
18348
18348
  }
18349
18349
  return o3.__N || o3.__;
@@ -18361,22 +18361,22 @@ function q2(n3, r3) {
18361
18361
  var u3 = h2(t3++, 7);
18362
18362
  return C2(u3.__H, r3) ? (u3.__V = n3(), u3.i = r3, u3.__h = n3, u3.__V) : u3.__;
18363
18363
  }
18364
- function x2(n3, t4) {
18364
+ function x2(n3, t5) {
18365
18365
  return o2 = 8, q2(function() {
18366
18366
  return n3;
18367
- }, t4);
18367
+ }, t5);
18368
18368
  }
18369
18369
  function j2() {
18370
18370
  for (var n3; n3 = f2.shift(); ) if (n3.__P && n3.__H) try {
18371
18371
  n3.__H.__h.forEach(z2), n3.__H.__h.forEach(B2), n3.__H.__h = [];
18372
- } catch (t4) {
18373
- n3.__H.__h = [], e2.__e(t4, n3.__v);
18372
+ } catch (t5) {
18373
+ n3.__H.__h = [], e2.__e(t5, n3.__v);
18374
18374
  }
18375
18375
  }
18376
18376
  e2.__b = function(n3) {
18377
18377
  r2 = null, a2 && a2(n3);
18378
- }, e2.__ = function(n3, t4) {
18379
- n3 && t4.__k && t4.__k.__m && (n3.__m = t4.__k.__m), d2 && d2(n3, t4);
18378
+ }, e2.__ = function(n3, t5) {
18379
+ n3 && t5.__k && t5.__k.__m && (n3.__m = t5.__k.__m), d2 && d2(n3, t5);
18380
18380
  }, e2.__r = function(n3) {
18381
18381
  v2 && v2(n3), t3 = 0;
18382
18382
  var i3 = (r2 = n3.__c).__H;
@@ -18385,55 +18385,55 @@ e2.__b = function(n3) {
18385
18385
  })) : (i3.__h.forEach(z2), i3.__h.forEach(B2), i3.__h = [], t3 = 0)), u2 = r2;
18386
18386
  }, e2.diffed = function(n3) {
18387
18387
  l2 && l2(n3);
18388
- var t4 = n3.__c;
18389
- t4 && t4.__H && (t4.__H.__h.length && (1 !== f2.push(t4) && i2 === e2.requestAnimationFrame || ((i2 = e2.requestAnimationFrame) || w2)(j2)), t4.__H.__.forEach(function(n4) {
18388
+ var t5 = n3.__c;
18389
+ t5 && t5.__H && (t5.__H.__h.length && (1 !== f2.push(t5) && i2 === e2.requestAnimationFrame || ((i2 = e2.requestAnimationFrame) || w2)(j2)), t5.__H.__.forEach(function(n4) {
18390
18390
  n4.i && (n4.__H = n4.i), n4.__V !== c2 && (n4.__ = n4.__V), n4.i = void 0, n4.__V = c2;
18391
18391
  })), u2 = r2 = null;
18392
- }, e2.__c = function(n3, t4) {
18393
- t4.some(function(n4) {
18392
+ }, e2.__c = function(n3, t5) {
18393
+ t5.some(function(n4) {
18394
18394
  try {
18395
18395
  n4.__h.forEach(z2), n4.__h = n4.__h.filter(function(n5) {
18396
18396
  return !n5.__ || B2(n5);
18397
18397
  });
18398
18398
  } catch (r3) {
18399
- t4.some(function(n5) {
18399
+ t5.some(function(n5) {
18400
18400
  n5.__h && (n5.__h = []);
18401
- }), t4 = [], e2.__e(r3, n4.__v);
18401
+ }), t5 = [], e2.__e(r3, n4.__v);
18402
18402
  }
18403
- }), m && m(n3, t4);
18403
+ }), m && m(n3, t5);
18404
18404
  }, e2.unmount = function(n3) {
18405
18405
  s2 && s2(n3);
18406
- var t4, r3 = n3.__c;
18406
+ var t5, r3 = n3.__c;
18407
18407
  r3 && r3.__H && (r3.__H.__.forEach(function(n4) {
18408
18408
  try {
18409
18409
  z2(n4);
18410
18410
  } catch (n5) {
18411
- t4 = n5;
18411
+ t5 = n5;
18412
18412
  }
18413
- }), r3.__H = void 0, t4 && e2.__e(t4, r3.__v));
18413
+ }), r3.__H = void 0, t5 && e2.__e(t5, r3.__v));
18414
18414
  };
18415
18415
  var k2 = "function" == typeof requestAnimationFrame;
18416
18416
  function w2(n3) {
18417
- var t4, r3 = function() {
18418
- clearTimeout(u3), k2 && cancelAnimationFrame(t4), setTimeout(n3);
18417
+ var t5, r3 = function() {
18418
+ clearTimeout(u3), k2 && cancelAnimationFrame(t5), setTimeout(n3);
18419
18419
  }, u3 = setTimeout(r3, 100);
18420
- k2 && (t4 = requestAnimationFrame(r3));
18420
+ k2 && (t5 = requestAnimationFrame(r3));
18421
18421
  }
18422
18422
  function z2(n3) {
18423
- var t4 = r2, u3 = n3.__c;
18424
- "function" == typeof u3 && (n3.__c = void 0, u3()), r2 = t4;
18423
+ var t5 = r2, u3 = n3.__c;
18424
+ "function" == typeof u3 && (n3.__c = void 0, u3()), r2 = t5;
18425
18425
  }
18426
18426
  function B2(n3) {
18427
- var t4 = r2;
18428
- n3.__c = n3.__(), r2 = t4;
18427
+ var t5 = r2;
18428
+ n3.__c = n3.__(), r2 = t5;
18429
18429
  }
18430
- function C2(n3, t4) {
18431
- return !n3 || n3.length !== t4.length || t4.some(function(t5, r3) {
18432
- return t5 !== n3[r3];
18430
+ function C2(n3, t5) {
18431
+ return !n3 || n3.length !== t5.length || t5.some(function(t6, r3) {
18432
+ return t6 !== n3[r3];
18433
18433
  });
18434
18434
  }
18435
- function D(n3, t4) {
18436
- return "function" == typeof t4 ? t4(n3) : t4;
18435
+ function D(n3, t5) {
18436
+ return "function" == typeof t5 ? t5(n3) : t5;
18437
18437
  }
18438
18438
 
18439
18439
  // dashboard/src/lib/api.ts
@@ -18493,9 +18493,9 @@ function ToastStack() {
18493
18493
  const onToast = (ev) => {
18494
18494
  const detail = ev.detail;
18495
18495
  const id = `${Date.now()}-${Math.random()}`;
18496
- const t4 = { id, ...detail };
18497
- setToasts((prev) => [...prev, t4]);
18498
- setTimeout(() => setToasts((prev) => prev.filter((x3) => x3.id !== id)), t4.ttl);
18496
+ const t5 = { id, ...detail };
18497
+ setToasts((prev) => [...prev, t5]);
18498
+ setTimeout(() => setToasts((prev) => prev.filter((x3) => x3.id !== id)), t5.ttl);
18499
18499
  };
18500
18500
  toastBus.addEventListener("toast", onToast);
18501
18501
  return () => toastBus.removeEventListener("toast", onToast);
@@ -18503,7 +18503,7 @@ function ToastStack() {
18503
18503
  if (toasts.length === 0) return null;
18504
18504
  return html`
18505
18505
  <div class="toast-stack">
18506
- ${toasts.map((t4) => html`<div key=${t4.id} class="toast ${t4.kind}">${t4.text}</div>`)}
18506
+ ${toasts.map((t5) => html`<div key=${t5.id} class="toast ${t5.kind}">${t5.text}</div>`)}
18507
18507
  </div>
18508
18508
  `;
18509
18509
  }
@@ -18636,6 +18636,1094 @@ var ErrorBoundary = class extends b {
18636
18636
  }
18637
18637
  };
18638
18638
 
18639
+ // dashboard/src/lib/i18n.ts
18640
+ var LANG_REGISTRY = [
18641
+ ["en", "EN"],
18642
+ ["zh-CN", "zh-CN"]
18643
+ ];
18644
+ var SUPPORTED = new Set(LANG_REGISTRY.map(([d3]) => d3));
18645
+ var TO_BACKEND = new Map(LANG_REGISTRY);
18646
+ var FROM_BACKEND = new Map(LANG_REGISTRY.map(([d3, b2]) => [b2, d3]));
18647
+ var STORAGE_KEY = "rx.lang";
18648
+ var EXPLICIT_KEY = "rx.langExplicit";
18649
+ var listeners = [];
18650
+ var currentLang = loadFromStorage() ?? "en";
18651
+ function loadFromStorage() {
18652
+ try {
18653
+ const v3 = localStorage.getItem(STORAGE_KEY);
18654
+ if (v3 !== null && SUPPORTED.has(v3)) return v3;
18655
+ } catch {
18656
+ }
18657
+ return null;
18658
+ }
18659
+ function isExplicit() {
18660
+ try {
18661
+ return localStorage.getItem(EXPLICIT_KEY) === "1";
18662
+ } catch {
18663
+ return false;
18664
+ }
18665
+ }
18666
+ function markExplicit() {
18667
+ try {
18668
+ localStorage.setItem(EXPLICIT_KEY, "1");
18669
+ } catch {
18670
+ }
18671
+ }
18672
+ function toBackendLang(lang) {
18673
+ return TO_BACKEND.get(lang) ?? lang;
18674
+ }
18675
+ function fromBackendLang(raw) {
18676
+ return FROM_BACKEND.get(raw) ?? "en";
18677
+ }
18678
+ async function initLangFromServer() {
18679
+ try {
18680
+ const stored = loadFromStorage();
18681
+ const res = await api("/settings");
18682
+ const serverLang = res.lang ? fromBackendLang(res.lang) : null;
18683
+ if (!serverLang || stored === serverLang) return;
18684
+ if (isExplicit() && stored) {
18685
+ api("/settings", { method: "POST", body: { lang: toBackendLang(stored) } }).catch((err) => console.error("[reasonix dashboard] lang sync:", err));
18686
+ return;
18687
+ }
18688
+ currentLang = serverLang;
18689
+ try {
18690
+ localStorage.setItem(STORAGE_KEY, serverLang);
18691
+ } catch {
18692
+ }
18693
+ for (const cb of listeners) cb();
18694
+ } catch {
18695
+ }
18696
+ }
18697
+ function getLang() {
18698
+ return currentLang;
18699
+ }
18700
+ function setLang(lang) {
18701
+ if (!SUPPORTED.has(lang)) return;
18702
+ currentLang = lang;
18703
+ markExplicit();
18704
+ try {
18705
+ localStorage.setItem(STORAGE_KEY, lang);
18706
+ } catch {
18707
+ }
18708
+ for (const cb of listeners) cb();
18709
+ fetch(`/api/settings?token=${TOKEN}`, {
18710
+ method: "POST",
18711
+ headers: { "Content-Type": "application/json", "X-Reasonix-Token": TOKEN },
18712
+ body: JSON.stringify({ lang: toBackendLang(lang) }),
18713
+ keepalive: true
18714
+ }).catch((err) => console.error("[reasonix dashboard] lang persist:", err));
18715
+ }
18716
+ function onLangChange(cb) {
18717
+ listeners.push(cb);
18718
+ return () => {
18719
+ const i3 = listeners.indexOf(cb);
18720
+ if (i3 >= 0) listeners.splice(i3, 1);
18721
+ };
18722
+ }
18723
+ function useLang() {
18724
+ const [lang, setLangState] = p2(currentLang);
18725
+ _2(() => onLangChange(() => setLangState(currentLang)), []);
18726
+ return lang;
18727
+ }
18728
+ function get(translations, path) {
18729
+ let val = translations;
18730
+ for (const part of path.split(".")) {
18731
+ if (val === void 0 || typeof val === "string") return void 0;
18732
+ val = val[part];
18733
+ }
18734
+ return typeof val === "string" ? val : void 0;
18735
+ }
18736
+ function createT(translations) {
18737
+ return function t5(path, params) {
18738
+ let val = get(translations[currentLang] ?? translations.en, path);
18739
+ if (val === void 0) val = get(translations.en, path);
18740
+ if (val === void 0) return path;
18741
+ if (!params) return val;
18742
+ let result = val;
18743
+ for (const [k3, v3] of Object.entries(params)) {
18744
+ result = result.replaceAll(`{${k3}}`, String(v3));
18745
+ }
18746
+ return result;
18747
+ };
18748
+ }
18749
+
18750
+ // dashboard/src/i18n/en.ts
18751
+ var en = {
18752
+ app: {
18753
+ sectionWorkspace: "workspace",
18754
+ sectionObserve: "observe",
18755
+ sectionConfigure: "configure",
18756
+ tabChat: "Chat",
18757
+ tabPlans: "Plans",
18758
+ tabSessions: "Sessions",
18759
+ tabOverview: "Overview",
18760
+ tabUsage: "Usage",
18761
+ tabSystem: "System",
18762
+ tabSemantic: "Semantic",
18763
+ tabTools: "Tools",
18764
+ tabPermissions: "Permissions",
18765
+ tabMcp: "MCP",
18766
+ tabSkills: "Skills",
18767
+ tabMemory: "Memory",
18768
+ tabHooks: "Hooks",
18769
+ tabSettings: "Settings",
18770
+ footer: "127.0.0.1 only \xB7 token-gated"
18771
+ },
18772
+ common: {
18773
+ loading: "loading\u2026",
18774
+ loadingFailed: "{name} failed: {error}",
18775
+ back: "\u2190 back",
18776
+ save: "Save",
18777
+ remove: "remove",
18778
+ cancel: "Cancel",
18779
+ delete: "Delete",
18780
+ add: "Add",
18781
+ confirm: "Confirm",
18782
+ noData: "No {name} yet.",
18783
+ all: "all",
18784
+ yes: "yes",
18785
+ no: "no",
18786
+ on: "ON",
18787
+ off: "off",
18788
+ enabled: "enabled",
18789
+ disabled: "disabled"
18790
+ },
18791
+ settings: {
18792
+ title: "Settings",
18793
+ loading: "loading settings\u2026",
18794
+ saved: "saved: {fields}",
18795
+ sectionApi: "DeepSeek API",
18796
+ apiKey: "API key",
18797
+ notSet: "(not set)",
18798
+ replace: "replace",
18799
+ pasteKey: "paste a fresh sk-\u2026 token",
18800
+ saveKey: "Save key",
18801
+ baseUrl: "base url",
18802
+ baseUrlPlaceholder: "https://api.deepseek.com (default)",
18803
+ sectionDefaults: "Defaults",
18804
+ preset: "preset",
18805
+ presetAuto: "auto \u2014 flash \u2192 pro on hard turns",
18806
+ presetFlash: "flash \u2014 always flash, no auto-escalate",
18807
+ presetPro: "pro \u2014 always pro",
18808
+ appliesNextTurn: "applies next turn",
18809
+ effort: "effort",
18810
+ effortMax: "max (default \u2014 best)",
18811
+ effortHigh: "high (cheaper / faster)",
18812
+ webSearch: "web search",
18813
+ webSearchNote: "web_fetch + web_search tools",
18814
+ sectionRuntime: "Runtime",
18815
+ activeModel: "active model",
18816
+ editMode: "edit mode",
18817
+ editModeNote: "switch from the Chat tab header",
18818
+ sectionLanguage: "Language",
18819
+ language: "language",
18820
+ langEn: "English",
18821
+ langZhCn: "\u7B80\u4F53\u4E2D\u6587"
18822
+ },
18823
+ chat: {
18824
+ modeMirror: "TUI mirror",
18825
+ modeView: "session view",
18826
+ placeholder: "Type a prompt \u2014 Enter sends, Shift+Enter for a newline \xB7 / @ for pickers",
18827
+ placeholderBusy: "wait for the current turn to finish\u2026",
18828
+ send: "Send",
18829
+ new: "New",
18830
+ clear: "Clear",
18831
+ newTitle: "/new \u2014 wipe conversation context (loop log + scrollback)",
18832
+ clearTitle: "/clear \u2014 wipe just visible scrollback (context kept)",
18833
+ noConversation: "No conversation yet. Send a prompt below to begin.",
18834
+ newConfirmBusy: "A turn is in flight. Abort and start a new conversation?",
18835
+ newConfirm: "Clear current conversation and start fresh?",
18836
+ newToast: "new conversation",
18837
+ clearToast: "scrollback cleared",
18838
+ newFailed: "/new failed: {error}",
18839
+ clearFailed: "/clear failed: {error}",
18840
+ eventStreamError: "event stream interrupted \u2014 reconnecting\u2026",
18841
+ semanticBanner: "Semantic search isn't enabled for this project.",
18842
+ semanticBannerDesc: 'Build the index once and the model can find code by meaning ("where do we handle auth failures?") instead of grep on exact strings.',
18843
+ semanticBannerBtn: "Build it \u2192",
18844
+ semanticBannerDismiss: "dismiss (don't show again)",
18845
+ slashCommands: "slash commands",
18846
+ projectFiles: "project files",
18847
+ effortTitle: "reasoning_effort \u2014 applies next turn",
18848
+ effortMaxTitle: "max (default \u2014 best quality)",
18849
+ effortHighTitle: "high (cheaper / faster)",
18850
+ presetTitle: "preset \u2014 model commitment",
18851
+ presetAutoTitle: "auto \u2014 flash baseline; auto-escalates to pro on hard turns (NEEDS_PRO / failure threshold)",
18852
+ presetFlashTitle: "flash \u2014 always flash; no auto-escalate. /pro still works for one-shot manual",
18853
+ presetProTitle: "pro \u2014 always pro; ~3\xD7 flash cost (5/31 discount). Locks in on hard architecture work.",
18854
+ editGateTitle: "edit gate \u2014 Shift+Tab cycles in TUI",
18855
+ editReviewTitle: "review \u2014 both edits and non-allowlisted shell ask first",
18856
+ editAutoTitle: "auto \u2014 edits auto-apply, shell still asks",
18857
+ editYoloTitle: "yolo \u2014 edits AND shell auto-run, allowlist bypassed",
18858
+ railSession: "Session",
18859
+ railTurns: "turns",
18860
+ railPromptTok: "prompt tok",
18861
+ railCost: "cost",
18862
+ railCacheHit: "cache hit",
18863
+ railToolBudget: "Tool budget",
18864
+ railSpend: "spend",
18865
+ railActivePlan: "Active plan",
18866
+ railProgress: "progress",
18867
+ statusModel: "model",
18868
+ statusCtx: "ctx",
18869
+ statusCache: "cache",
18870
+ statusTurn: "turn",
18871
+ statusSession: "session",
18872
+ statusBalance: "balance",
18873
+ statusTurns: "{count} turn{s}",
18874
+ waitingStats: "\xB7 \xB7 \xB7 waiting for live stats",
18875
+ inflightPhase: "{phase}",
18876
+ inflightRunning: "running",
18877
+ inflightThinking: "thinking",
18878
+ inflightStreaming: "streaming",
18879
+ inflightWaiting: "waiting",
18880
+ inflightReasoning: "reasoning {count} ch",
18881
+ inflightOut: "out {count} ch",
18882
+ abortBtn: "Abort (Esc)",
18883
+ confirmBtn: "Apply (y)",
18884
+ rejectBtn: "Reject (n)",
18885
+ applyRestBtn: "Apply rest (a)",
18886
+ flipAutoBtn: "Flip to AUTO (A)"
18887
+ },
18888
+ overview: {
18889
+ loading: "loading overview\u2026",
18890
+ failed: "overview failed: {error}",
18891
+ standaloneTitle: "Standalone mode",
18892
+ standaloneDesc: "Read-only disk view. Start /dashboard from inside reasonix code for live session state, MCP, and tools.",
18893
+ cockpit: "Cockpit",
18894
+ balance: "balance",
18895
+ tokens7d: "tokens \xB7 7d",
18896
+ cacheHit: "cache hit",
18897
+ toolCalls24h: "tool calls \xB7 24h",
18898
+ currentSession: "current session",
18899
+ noSession: "No live session \u2014 /dashboard from inside reasonix code to attach.",
18900
+ promptTok: "prompt tok",
18901
+ completionTok: "completion tok",
18902
+ cost: "cost",
18903
+ costTrend: "cost \xB7 14 day",
18904
+ noUsageYet: "no usage yet",
18905
+ dayAvg: "/day avg",
18906
+ recentPlans: "recent plans",
18907
+ noPlans: "No plans yet \u2014 submit one with submit_plan.",
18908
+ toolActivity: "tool activity",
18909
+ noToolCalls: "No tool calls yet.",
18910
+ toolsLoaded: "tools loaded",
18911
+ mcpServers: "mcp servers",
18912
+ editMode: "edit mode",
18913
+ version: "Reasonix",
18914
+ workingDir: "Working directory",
18915
+ projectRoot: "project root",
18916
+ noPriorData: "no prior data",
18917
+ stable: "\u2014 stable",
18918
+ vsPrior: "{arrow} {pct}% vs prior",
18919
+ active: "active",
18920
+ allUp: "all up",
18921
+ yoloWarning: "all prompts bypassed",
18922
+ checking: "checking",
18923
+ latest: "latest"
18924
+ },
18925
+ usage: {
18926
+ loading: "loading usage\u2026",
18927
+ failed: "usage failed: {error}",
18928
+ records: "{count} records",
18929
+ dailyUsage: "Daily usage",
18930
+ dailyMeta: "cost \xB7 cache saved \xB7 turns",
18931
+ noData: "No usage data yet \u2014 run a turn in reasonix chat / code / run and refresh.",
18932
+ windows: "Rolling windows",
18933
+ colTurns: "turns",
18934
+ colCacheHit: "cache hit",
18935
+ colCost: "cost (USD)",
18936
+ colCacheSaved: "cache saved",
18937
+ colVsClaude: "vs Claude",
18938
+ colSaved: "saved",
18939
+ mostUsed: "Most used models",
18940
+ colModel: "model"
18941
+ },
18942
+ sessions: {
18943
+ loading: "loading sessions\u2026",
18944
+ failed: "sessions failed: {error}",
18945
+ noSessions: "No saved sessions yet.",
18946
+ filterPlaceholder: "filter sessions",
18947
+ msgs: "msgs",
18948
+ pickHint: "Pick a session on the left to read its transcript.",
18949
+ resumeTitle: "Resume in TUI",
18950
+ resumeDesc: "Mid-session swap requires a restart so the message log can rewind cleanly. Quit your current session, then run:",
18951
+ loadingTranscript: "loading transcript\u2026",
18952
+ emptyTranscript: "empty transcript.",
18953
+ messages: "{count} message{s}"
18954
+ },
18955
+ tools: {
18956
+ loading: "loading tools\u2026",
18957
+ failed: "tools failed: {error}",
18958
+ noTools: "No tools registered.",
18959
+ planMode: "plan mode \u2014 writes gated",
18960
+ colTool: "tool",
18961
+ colFlags: "flags",
18962
+ colDesc: "description",
18963
+ readOnly: "read-only",
18964
+ write: "write",
18965
+ flat: "flat"
18966
+ },
18967
+ permissions: {
18968
+ loading: "loading permissions\u2026",
18969
+ failed: "permissions failed: {error}",
18970
+ yoloTitle: "YOLO mode",
18971
+ yoloDesc: "Every shell command auto-runs, allowlist bypassed. Switch back with /mode review in the TUI.",
18972
+ project: "project",
18973
+ builtin: "builtin",
18974
+ addPrefix: "add a prefix",
18975
+ addPlaceholder: 'e.g. "npm run build" or "deploy.sh"',
18976
+ clearAll: "Clear all",
18977
+ alreadyIn: "{prefix} already in list",
18978
+ added: "added: {prefix}",
18979
+ removed: "removed: {prefix}",
18980
+ cleared: "cleared {count} entr{y}",
18981
+ removeConfirm: `Remove "{prefix}" from this project's allowlist?`,
18982
+ clearConfirm: "Wipe every project allowlist entry? Builtin entries are unaffected.",
18983
+ projectAllowlist: "Project allowlist \xB7 {count}",
18984
+ nothingStored: "Nothing stored yet for this project.",
18985
+ colNum: "#",
18986
+ colPrefix: "prefix",
18987
+ builtinTitle: "Builtin \xB7 {count} \xB7 read-only",
18988
+ standaloneWarning: "Mutations require /dashboard from inside an active reasonix code session \u2014 standalone reasonix dashboard can't tell which project's allowlist to edit."
18989
+ },
18990
+ mcp: {
18991
+ loading: "loading MCP\u2026",
18992
+ servers: "MCP servers \xB7 {count} bridged",
18993
+ all: "all",
18994
+ live: "live",
18995
+ unbridged: "unbridged",
18996
+ specPlaceholder: "spec \u2014 e.g. fs=npx -y @modelcontextprotocol/...",
18997
+ saved: "saved",
18998
+ savedRestart: "saved \u2014 restart reasonix code to bridge this server",
18999
+ removed: "removed \u2014 restart to drop the live bridge",
19000
+ removeConfirm: "Remove MCP spec from config?\n\n{spec}",
19001
+ noServers: "No MCP servers in this session.",
19002
+ tools: "tools",
19003
+ inConfig: "in config \xB7 not loaded",
19004
+ unbridgedTitle: "unbridged \xB7 in config",
19005
+ removeBtn: "Remove",
19006
+ spec: "spec",
19007
+ whyUnbridged: "Why unbridged?",
19008
+ whyUnbridgedDesc: "This spec lives in your config.json but isn't bridged into the live session. MCP servers attach when reasonix code starts; the dashboard alone can't spawn the child process.",
19009
+ whyUnbridgedHint: "To activate: restart reasonix code, then refresh this dashboard.",
19010
+ pickHint: "Pick an MCP server on the left to inspect tools / resources / prompts.",
19011
+ toolsTitle: "Tools \xB7 {count}",
19012
+ resourcesTitle: "Resources \xB7 {count}",
19013
+ promptsTitle: "Prompts \xB7 {count}",
19014
+ colName: "name",
19015
+ colDesc: "description",
19016
+ colUri: "uri"
19017
+ },
19018
+ memory: {
19019
+ loading: "loading memory\u2026",
19020
+ files: "memory \xB7 {count} files",
19021
+ exists: "exists",
19022
+ create: "create",
19023
+ noFiles: "No memory files yet.",
19024
+ pickHint: "Pick a memory file on the left.",
19025
+ pickDesc: "Project REASONIX.md is committable; global notes live in ~/.reasonix/memory/.",
19026
+ chars: "{count} chars",
19027
+ saved: "saved {scope}",
19028
+ reloadHint: "re-applied on next /new or session restart"
19029
+ },
19030
+ hooks: {
19031
+ loading: "loading hooks\u2026",
19032
+ resolved: "resolved",
19033
+ eventMatrix: "Event matrix",
19034
+ matrixSub: "{scripts} script{s} \xD7 {events} event{s}",
19035
+ noHooks: "No hooks configured. Edit the JSON below to add some.",
19036
+ colScript: "script",
19037
+ noProject: "No active project \u2014 open /dashboard from reasonix code to edit project hooks.",
19038
+ saveReload: "Save + Reload",
19039
+ discard: "Discard changes",
19040
+ savedReloaded: "saved + reloaded {scope}",
19041
+ recentRuns: "Recent runs",
19042
+ noRuns: "No hook runs in the recent session log.",
19043
+ colWhen: "when",
19044
+ colPhase: "phase",
19045
+ colHook: "hook",
19046
+ colOutcome: "outcome"
19047
+ },
19048
+ skills: {
19049
+ loading: "loading skills\u2026",
19050
+ filterPlaceholder: "filter skills",
19051
+ project: "project",
19052
+ global: "global",
19053
+ builtin: "builtin",
19054
+ newSkill: "new skill",
19055
+ noDescription: "(no description)",
19056
+ runs7d: "runs \xB7 7d",
19057
+ pickHint: "Pick a skill on the left, or create a new one above.",
19058
+ readOnlyBuiltin: "read-only \xB7 builtin",
19059
+ builtinDesc: "Built-in skills ship with Reasonix; the model picks them up automatically. To customize, create a project- or global-scoped skill with the same name.",
19060
+ saved: "saved {scope}/{name}",
19061
+ deleteConfirm: "Delete skill {scope}/{name}?",
19062
+ reloadHint: "re-loaded on next /new or session restart"
19063
+ },
19064
+ system: {
19065
+ loading: "loading health\u2026",
19066
+ failed: "health failed: {error}",
19067
+ healthChecks: "Health checks",
19068
+ version: "version",
19069
+ checking: "checking",
19070
+ latest: "\u25CF latest",
19071
+ outOfDate: "\u25CF out of date",
19072
+ versionPending: "version check pending",
19073
+ upToDate: "up to date",
19074
+ latestVer: "latest: {version}",
19075
+ sessions: "sessions",
19076
+ ok: "\u25CF ok",
19077
+ memory: "memory",
19078
+ semanticIndex: "semantic index",
19079
+ built: "\u25CF built",
19080
+ none: "\u2014 none",
19081
+ runIndex: "run reasonix index to build",
19082
+ usageLog: "usage log",
19083
+ backgroundJobs: "background jobs",
19084
+ noSession: "\u2014 no session",
19085
+ running: "{count} running",
19086
+ attachHint: "attach a session to see jobs",
19087
+ shellSpawn: "shell + spawn",
19088
+ paths: "paths",
19089
+ home: "home",
19090
+ sessionsPath: "sessions",
19091
+ memoryPath: "memory",
19092
+ semanticPath: "semantic",
19093
+ usagePath: "usage"
19094
+ },
19095
+ plans: {
19096
+ loading: "loading plans\u2026",
19097
+ failed: "plans failed: {error}",
19098
+ noPlans: "No archived plans yet \u2014 run a turn that calls submit_plan and mark_step_complete.",
19099
+ filterPlaceholder: "filter plans",
19100
+ active: "active",
19101
+ done: "done",
19102
+ steps: "steps",
19103
+ pickHint: "Pick a plan on the left.",
19104
+ noTitle: "(no title)",
19105
+ stepTimeline: "Step timeline \xB7 {done} / {total}",
19106
+ step: "step {n}"
19107
+ },
19108
+ semantic: {
19109
+ codeRequired: "Semantic \u2014 code-mode required",
19110
+ indexBuilt: "index built",
19111
+ noIndex: "no index yet",
19112
+ ready: "ready",
19113
+ setupNeeded: "setup needed",
19114
+ installOllama: "Install Ollama",
19115
+ installOllamaDesc: "Reasonix doesn't run package managers for you. Install Ollama first, then come back:",
19116
+ macWindows: "macOS / Windows:",
19117
+ download: "download from ollama.com/download",
19118
+ linux: "Linux:",
19119
+ refreshHint: "Refresh after install \u2014 this panel will offer to start the daemon and pull {model}.",
19120
+ daemon: "Daemon",
19121
+ daemonDesc: "ollama is on your PATH but the HTTP daemon isn't reachable.",
19122
+ startDaemon: "Start daemon",
19123
+ runsOllama: "runs ollama serve detached",
19124
+ model: "Model",
19125
+ modelMissing: "{model} isn't installed yet.",
19126
+ modelSize: "~270 MB on first pull.",
19127
+ pulling: "pulling\u2026",
19128
+ pullModel: "Pull {model}",
19129
+ indexStatus: "index status",
19130
+ builtStatus: "\u25CF built",
19131
+ chunks: "chunks",
19132
+ files: "files",
19133
+ dim: "dim",
19134
+ size: "size",
19135
+ lastBuild: "last build",
19136
+ runIndexHint: "Run an index to enable semantic_search.",
19137
+ reIndex: "Re-index",
19138
+ build: "Build",
19139
+ rebuild: "Rebuild",
19140
+ stop: "Stop",
19141
+ ollama: "ollama",
19142
+ binary: "binary",
19143
+ found: "found",
19144
+ missing: "missing",
19145
+ daemonStatus: "daemon",
19146
+ up: "up",
19147
+ down: "down",
19148
+ pulled: "pulled",
19149
+ indexConfig: "index config",
19150
+ reset: "reset",
19151
+ excludeDirs: "exclude dirs",
19152
+ excludeFiles: "exclude files",
19153
+ excludeExts: "exclude exts",
19154
+ excludePatterns: "exclude patterns",
19155
+ glob: "glob",
19156
+ respectGitignore: "respect .gitignore",
19157
+ maxFileBytes: "max file bytes",
19158
+ skipLarger: "skip files larger than ~{size} MiB",
19159
+ preview: "Preview",
19160
+ searchPlaceholder: "describe what to find \u2014 'where do we handle abort signals'",
19161
+ searching: "searching\u2026",
19162
+ results: "{count} result{s} \xB7 {ms}ms \xB7 {model}",
19163
+ noMatches: "No matches above the score threshold.",
19164
+ previewSummary: "Preview \u2014 would index {included} file(s), skip {skipped}",
19165
+ nothingSkipped: "nothing skipped \u2014 all walked files would be indexed.",
19166
+ firstIncluded: "first {count} included file(s)",
19167
+ job: "Job",
19168
+ phaseScan: "scanning files",
19169
+ phaseEmbed: "embedding chunks",
19170
+ phaseWrite: "writing index",
19171
+ phaseDone: "done",
19172
+ phaseError: "error",
19173
+ stopping: "stopping",
19174
+ scanned: "scanned {count}",
19175
+ changed: "changed {count}",
19176
+ skipped: "skipped {count}",
19177
+ chunksProgress: "{done} / {total} ({pct}%)",
19178
+ result: "result",
19179
+ added: "added {count}",
19180
+ removed: "removed {count}",
19181
+ failed: "failed {count}",
19182
+ skippedFiles: "{total} files ({details})",
19183
+ rebuildStarted: "rebuild started",
19184
+ incrementalStarted: "incremental index started",
19185
+ stopRequested: "stopping requested \u2014 current chunk batch will finish first",
19186
+ startingDaemon: "starting ollama daemon (15s timeout)\u2026",
19187
+ daemonUp: "daemon is up",
19188
+ daemonTimeout: "daemon didn't come up in time \u2014 check ollama serve manually",
19189
+ pullingModel: "pulling {model} \u2014 this may take a few minutes on first install",
19190
+ savedConfig: "saved \xB7 {count} fields updated \xB7 re-run index to apply",
19191
+ runningPreview: "running dry walk against project root\u2026",
19192
+ exclude: "exclude"
19193
+ },
19194
+ modal: {
19195
+ shellTitle: "shell command",
19196
+ shellBgTitle: "background process",
19197
+ shellSubtitle: "model wants to run a shell command",
19198
+ shellBgSubtitle: "long-running \u2014 keeps running after approval",
19199
+ runOnce: "Run once",
19200
+ alwaysAllow: 'Always allow "{prefix}"',
19201
+ deny: "Deny",
19202
+ choiceTitle: "model wants you to pick",
19203
+ typeOwn: "Type my own answer",
19204
+ typeOwnSummary: "None of the above fits \u2014 write a free-form reply.",
19205
+ typePlaceholder: "Type a free-form answer\u2026",
19206
+ send: "Send",
19207
+ cancel: "Cancel",
19208
+ cancelSummary: "Drop the question. Model will ask what you actually want.",
19209
+ planTitle: "plan submitted",
19210
+ planSubtitle: "model proposed a plan; review then pick",
19211
+ approveInstructions: "Optional last instructions / answers to open questions (Enter to send blank)",
19212
+ refinePlaceholder: "What needs to change? Be specific.",
19213
+ approve: "Approve",
19214
+ refine: "Refine",
19215
+ sendRefinement: "Send refinement",
19216
+ editTitle: "edit pending review",
19217
+ editSubtitle: "{path} \xB7 {remaining} of {total} blocks remaining",
19218
+ before: "before",
19219
+ after: "after",
19220
+ workspaceTitle: "model wants to switch workspace",
19221
+ workspaceSubtitle: "every subsequent file / shell / memory tool resolves against the new root",
19222
+ switchBtn: "Switch (Enter)",
19223
+ denyBtn: "Deny (Esc)",
19224
+ stepComplete: "step complete{counter}",
19225
+ continueBtn: "Continue",
19226
+ reviseBtn: "Revise\u2026",
19227
+ stopBtn: "Stop",
19228
+ revisionTitle: "model proposed a plan revision",
19229
+ sendRevision: "Send revision",
19230
+ accept: "Accept",
19231
+ reject: "Reject",
19232
+ arguments: "arguments",
19233
+ revisePlaceholder: "What needs to change before the next step? Leave blank to just continue."
19234
+ }
19235
+ };
19236
+
19237
+ // dashboard/src/i18n/zh-CN.ts
19238
+ var zhCN = {
19239
+ app: {
19240
+ sectionWorkspace: "\u5DE5\u4F5C\u533A",
19241
+ sectionObserve: "\u76D1\u63A7",
19242
+ sectionConfigure: "\u914D\u7F6E",
19243
+ tabChat: "\u5BF9\u8BDD",
19244
+ tabPlans: "\u8BA1\u5212",
19245
+ tabSessions: "\u4F1A\u8BDD",
19246
+ tabOverview: "\u6982\u89C8",
19247
+ tabUsage: "\u7528\u91CF",
19248
+ tabSystem: "\u7CFB\u7EDF",
19249
+ tabSemantic: "\u8BED\u4E49",
19250
+ tabTools: "\u5DE5\u5177",
19251
+ tabPermissions: "\u6743\u9650",
19252
+ tabMcp: "MCP",
19253
+ tabSkills: "\u6280\u80FD",
19254
+ tabMemory: "\u8BB0\u5FC6",
19255
+ tabHooks: "\u94A9\u5B50",
19256
+ tabSettings: "\u8BBE\u7F6E",
19257
+ footer: "\u4EC5 127.0.0.1 \xB7 Token \u4FDD\u62A4"
19258
+ },
19259
+ common: {
19260
+ loading: "\u52A0\u8F7D\u4E2D\u2026",
19261
+ loadingFailed: "{name}\u5931\u8D25\uFF1A{error}",
19262
+ back: "\u2190 \u8FD4\u56DE",
19263
+ save: "\u4FDD\u5B58",
19264
+ remove: "\u79FB\u9664",
19265
+ cancel: "\u53D6\u6D88",
19266
+ delete: "\u5220\u9664",
19267
+ add: "\u6DFB\u52A0",
19268
+ confirm: "\u786E\u8BA4",
19269
+ noData: "\u6682\u65E0{name}\u3002",
19270
+ all: "\u5168\u90E8",
19271
+ yes: "\u662F",
19272
+ no: "\u5426",
19273
+ on: "\u5F00\u542F",
19274
+ off: "\u5173\u95ED",
19275
+ enabled: "\u5DF2\u542F\u7528",
19276
+ disabled: "\u5DF2\u7981\u7528"
19277
+ },
19278
+ settings: {
19279
+ title: "\u8BBE\u7F6E",
19280
+ loading: "\u52A0\u8F7D\u8BBE\u7F6E\u2026",
19281
+ saved: "\u5DF2\u4FDD\u5B58\uFF1A{fields}",
19282
+ sectionApi: "DeepSeek API",
19283
+ apiKey: "API \u5BC6\u94A5",
19284
+ notSet: "\uFF08\u672A\u8BBE\u7F6E\uFF09",
19285
+ replace: "\u66FF\u6362",
19286
+ pasteKey: "\u7C98\u8D34\u65B0\u7684 sk-\u2026 \u4EE4\u724C",
19287
+ saveKey: "\u4FDD\u5B58\u5BC6\u94A5",
19288
+ baseUrl: "\u57FA\u7840 URL",
19289
+ baseUrlPlaceholder: "https://api.deepseek.com\uFF08\u9ED8\u8BA4\uFF09",
19290
+ sectionDefaults: "\u9ED8\u8BA4\u8BBE\u7F6E",
19291
+ preset: "\u9884\u8BBE",
19292
+ presetAuto: "auto \u2014 flash \u57FA\u7EBF\uFF0C\u56F0\u96BE\u65F6\u81EA\u52A8\u5347\u7EA7\u4E3A pro",
19293
+ presetFlash: "flash \u2014 \u59CB\u7EC8\u4F7F\u7528 flash\uFF0C\u4E0D\u81EA\u52A8\u5347\u7EA7",
19294
+ presetPro: "pro \u2014 \u59CB\u7EC8\u4F7F\u7528 pro",
19295
+ appliesNextTurn: "\u4E0B\u4E00\u8F6E\u751F\u6548",
19296
+ effort: "\u63A8\u7406\u5F3A\u5EA6",
19297
+ effortMax: "max\uFF08\u9ED8\u8BA4 \u2014 \u6700\u4F73\u8D28\u91CF\uFF09",
19298
+ effortHigh: "high\uFF08\u66F4\u4FBF\u5B9C / \u66F4\u5FEB\uFF09",
19299
+ webSearch: "\u7F51\u9875\u641C\u7D22",
19300
+ webSearchNote: "web_fetch + web_search \u5DE5\u5177",
19301
+ sectionRuntime: "\u8FD0\u884C\u65F6",
19302
+ activeModel: "\u5F53\u524D\u6A21\u578B",
19303
+ editMode: "\u7F16\u8F91\u6A21\u5F0F",
19304
+ editModeNote: "\u5728\u5BF9\u8BDD\u6807\u7B7E\u9875\u5934\u90E8\u5207\u6362",
19305
+ sectionLanguage: "\u8BED\u8A00",
19306
+ language: "\u8BED\u8A00",
19307
+ langEn: "English",
19308
+ langZhCn: "\u7B80\u4F53\u4E2D\u6587"
19309
+ },
19310
+ chat: {
19311
+ modeMirror: "TUI \u955C\u50CF",
19312
+ modeView: "\u4F1A\u8BDD\u89C6\u56FE",
19313
+ placeholder: "\u8F93\u5165\u63D0\u793A\u8BCD \u2014 Enter \u53D1\u9001\uFF0CShift+Enter \u6362\u884C \xB7 / @ \u6253\u5F00\u9009\u62E9\u5668",
19314
+ placeholderBusy: "\u8BF7\u7B49\u5F85\u5F53\u524D\u8F6E\u6B21\u5B8C\u6210\u2026",
19315
+ send: "\u53D1\u9001",
19316
+ new: "\u65B0\u5EFA",
19317
+ clear: "\u6E05\u9664",
19318
+ newTitle: "/new \u2014 \u6E05\u9664\u5BF9\u8BDD\u4E0A\u4E0B\u6587\uFF08\u5FAA\u73AF\u65E5\u5FD7 + \u6EDA\u52A8\u56DE\u653E\uFF09",
19319
+ clearTitle: "/clear \u2014 \u4EC5\u6E05\u9664\u53EF\u89C1\u7684\u6EDA\u52A8\u56DE\u653E\uFF08\u4E0A\u4E0B\u6587\u4FDD\u7559\uFF09",
19320
+ noConversation: "\u6682\u65E0\u5BF9\u8BDD\u3002\u5728\u4E0B\u65B9\u53D1\u9001\u63D0\u793A\u8BCD\u5F00\u59CB\u3002",
19321
+ newConfirmBusy: "\u6709\u8F6E\u6B21\u6B63\u5728\u6267\u884C\u3002\u4E2D\u6B62\u5E76\u5F00\u59CB\u65B0\u5BF9\u8BDD\uFF1F",
19322
+ newConfirm: "\u6E05\u9664\u5F53\u524D\u5BF9\u8BDD\u5E76\u91CD\u65B0\u5F00\u59CB\uFF1F",
19323
+ newToast: "\u65B0\u5BF9\u8BDD",
19324
+ clearToast: "\u6EDA\u52A8\u56DE\u653E\u5DF2\u6E05\u9664",
19325
+ newFailed: "/new \u5931\u8D25\uFF1A{error}",
19326
+ clearFailed: "/clear \u5931\u8D25\uFF1A{error}",
19327
+ eventStreamError: "\u4E8B\u4EF6\u6D41\u4E2D\u65AD \u2014 \u6B63\u5728\u91CD\u8FDE\u2026",
19328
+ semanticBanner: "\u6B64\u9879\u76EE\u672A\u542F\u7528\u8BED\u4E49\u641C\u7D22\u3002",
19329
+ semanticBannerDesc: "\u6784\u5EFA\u4E00\u6B21\u7D22\u5F15\uFF0C\u6A21\u578B\u5373\u53EF\u6309\u542B\u4E49\u67E5\u627E\u4EE3\u7801\uFF08\u201C\u54EA\u91CC\u5904\u7406\u8BA4\u8BC1\u5931\u8D25\uFF1F\u201D\uFF09\uFF0C\u800C\u4E0D\u4EC5\u4F9D\u8D56\u7CBE\u786E\u5B57\u7B26\u4E32\u7684 grep\u3002",
19330
+ semanticBannerBtn: "\u6784\u5EFA \u2192",
19331
+ semanticBannerDismiss: "\u5173\u95ED\uFF08\u4E0D\u518D\u663E\u793A\uFF09",
19332
+ slashCommands: "\u659C\u6760\u547D\u4EE4",
19333
+ projectFiles: "\u9879\u76EE\u6587\u4EF6",
19334
+ effortTitle: "reasoning_effort \u2014 \u4E0B\u4E00\u8F6E\u751F\u6548",
19335
+ effortMaxTitle: "max\uFF08\u9ED8\u8BA4 \u2014 \u6700\u4F73\u8D28\u91CF\uFF09",
19336
+ effortHighTitle: "high\uFF08\u66F4\u4FBF\u5B9C / \u66F4\u5FEB\uFF09",
19337
+ presetTitle: "\u9884\u8BBE \u2014 \u6A21\u578B\u627F\u8BFA",
19338
+ presetAutoTitle: "auto \u2014 flash \u57FA\u7EBF\uFF1B\u56F0\u96BE\u8F6E\u6B21\u81EA\u52A8\u5347\u7EA7\u4E3A pro\uFF08NEEDS_PRO / \u5931\u8D25\u9608\u503C\uFF09",
19339
+ presetFlashTitle: "flash \u2014 \u59CB\u7EC8 flash\uFF1B\u4E0D\u81EA\u52A8\u5347\u7EA7\u3002/pro \u4ECD\u53EF\u7528\u4E8E\u4E00\u6B21\u6027\u624B\u52A8\u63D0\u5347",
19340
+ presetProTitle: "pro \u2014 \u59CB\u7EC8 pro\uFF1B\u7EA6 3 \u500D flash \u6210\u672C\uFF085/31 \u6298\u6263\uFF09\u3002\u9501\u5B9A\u56F0\u96BE\u7684\u67B6\u6784\u5DE5\u4F5C\u3002",
19341
+ editGateTitle: "\u7F16\u8F91\u95E8\u63A7 \u2014 Shift+Tab \u5728 TUI \u4E2D\u5FAA\u73AF",
19342
+ editReviewTitle: "review \u2014 \u7F16\u8F91\u548C\u975E\u5141\u8BB8\u5217\u8868\u7684 shell \u547D\u4EE4\u90FD\u4F1A\u5148\u8BE2\u95EE",
19343
+ editAutoTitle: "auto \u2014 \u7F16\u8F91\u81EA\u52A8\u5E94\u7528\uFF0Cshell \u4ECD\u4F1A\u8BE2\u95EE",
19344
+ editYoloTitle: "yolo \u2014 \u7F16\u8F91\u548C shell \u90FD\u81EA\u52A8\u8FD0\u884C\uFF0C\u7ED5\u8FC7\u5141\u8BB8\u5217\u8868",
19345
+ railSession: "\u4F1A\u8BDD",
19346
+ railTurns: "\u8F6E\u6B21",
19347
+ railPromptTok: "\u63D0\u793A tokens",
19348
+ railCost: "\u8D39\u7528",
19349
+ railCacheHit: "\u7F13\u5B58\u547D\u4E2D",
19350
+ railToolBudget: "\u5DE5\u5177\u9884\u7B97",
19351
+ railSpend: "\u5DF2\u7528",
19352
+ railActivePlan: "\u6D3B\u8DC3\u8BA1\u5212",
19353
+ railProgress: "\u8FDB\u5EA6",
19354
+ statusModel: "\u6A21\u578B",
19355
+ statusCtx: "\u4E0A\u4E0B\u6587",
19356
+ statusCache: "\u7F13\u5B58",
19357
+ statusTurn: "\u8F6E\u6B21",
19358
+ statusSession: "\u4F1A\u8BDD",
19359
+ statusBalance: "\u4F59\u989D",
19360
+ statusTurns: "{count} \u8F6E",
19361
+ waitingStats: "\xB7 \xB7 \xB7 \u7B49\u5F85\u5B9E\u65F6\u7EDF\u8BA1",
19362
+ inflightPhase: "{phase}",
19363
+ inflightRunning: "\u8FD0\u884C\u4E2D",
19364
+ inflightThinking: "\u601D\u8003\u4E2D",
19365
+ inflightStreaming: "\u8F93\u51FA\u4E2D",
19366
+ inflightWaiting: "\u7B49\u5F85\u4E2D",
19367
+ inflightReasoning: "\u63A8\u7406 {count} \u5B57\u7B26",
19368
+ inflightOut: "\u8F93\u51FA {count} \u5B57\u7B26",
19369
+ abortBtn: "\u4E2D\u6B62 (Esc)",
19370
+ confirmBtn: "\u5E94\u7528 (y)",
19371
+ rejectBtn: "\u62D2\u7EDD (n)",
19372
+ applyRestBtn: "\u5E94\u7528\u5269\u4F59 (a)",
19373
+ flipAutoBtn: "\u5207\u6362\u4E3A AUTO (A)"
19374
+ },
19375
+ overview: {
19376
+ loading: "\u52A0\u8F7D\u6982\u89C8\u2026",
19377
+ failed: "\u6982\u89C8\u5931\u8D25\uFF1A{error}",
19378
+ standaloneTitle: "\u72EC\u7ACB\u6A21\u5F0F",
19379
+ standaloneDesc: "\u53EA\u8BFB\u78C1\u76D8\u89C6\u56FE\u3002\u5728 reasonix code \u5185\u542F\u52A8 /dashboard \u4EE5\u83B7\u53D6\u5B9E\u65F6\u4F1A\u8BDD\u72B6\u6001\u3001MCP \u548C\u5DE5\u5177\u3002",
19380
+ cockpit: "\u9A7E\u9A76\u8231",
19381
+ balance: "\u4F59\u989D",
19382
+ tokens7d: "tokens \xB7 7 \u5929",
19383
+ cacheHit: "\u7F13\u5B58\u547D\u4E2D",
19384
+ toolCalls24h: "\u5DE5\u5177\u8C03\u7528 \xB7 24 \u5C0F\u65F6",
19385
+ currentSession: "\u5F53\u524D\u4F1A\u8BDD",
19386
+ noSession: "\u65E0\u6D3B\u8DC3\u4F1A\u8BDD \u2014 \u5728 reasonix code \u5185\u6267\u884C /dashboard \u8FDB\u884C\u8FDE\u63A5\u3002",
19387
+ promptTok: "\u63D0\u793A tokens",
19388
+ completionTok: "\u8865\u5168 tokens",
19389
+ cost: "\u8D39\u7528",
19390
+ costTrend: "\u8D39\u7528 \xB7 14 \u5929",
19391
+ noUsageYet: "\u6682\u65E0\u7528\u91CF",
19392
+ dayAvg: "/\u5929 \u5747\u503C",
19393
+ recentPlans: "\u8FD1\u671F\u8BA1\u5212",
19394
+ noPlans: "\u6682\u65E0\u8BA1\u5212 \u2014 \u4F7F\u7528 submit_plan \u63D0\u4EA4\u4E00\u4E2A\u3002",
19395
+ toolActivity: "\u5DE5\u5177\u6D3B\u52A8",
19396
+ noToolCalls: "\u6682\u65E0\u5DE5\u5177\u8C03\u7528\u3002",
19397
+ toolsLoaded: "\u5DF2\u52A0\u8F7D\u5DE5\u5177",
19398
+ mcpServers: "MCP \u670D\u52A1\u5668",
19399
+ editMode: "\u7F16\u8F91\u6A21\u5F0F",
19400
+ version: "Reasonix",
19401
+ workingDir: "\u5DE5\u4F5C\u76EE\u5F55",
19402
+ projectRoot: "\u9879\u76EE\u6839\u76EE\u5F55",
19403
+ noPriorData: "\u65E0\u5386\u53F2\u6570\u636E",
19404
+ stable: "\u2014 \u7A33\u5B9A",
19405
+ vsPrior: "{arrow} {pct}% \u8F83\u4E0A\u671F",
19406
+ active: "\u6D3B\u8DC3",
19407
+ allUp: "\u5168\u90E8\u5728\u7EBF",
19408
+ yoloWarning: "\u6240\u6709\u63D0\u793A\u88AB\u7ED5\u8FC7",
19409
+ checking: "\u68C0\u67E5\u4E2D",
19410
+ latest: "\u6700\u65B0"
19411
+ },
19412
+ usage: {
19413
+ loading: "\u52A0\u8F7D\u7528\u91CF\u2026",
19414
+ failed: "\u7528\u91CF\u5931\u8D25\uFF1A{error}",
19415
+ records: "{count} \u6761\u8BB0\u5F55",
19416
+ dailyUsage: "\u6BCF\u65E5\u7528\u91CF",
19417
+ dailyMeta: "\u8D39\u7528 \xB7 \u7F13\u5B58\u8282\u7701 \xB7 \u8F6E\u6B21",
19418
+ noData: "\u6682\u65E0\u7528\u91CF\u6570\u636E \u2014 \u5728 reasonix chat / code / run \u4E2D\u6267\u884C\u4E00\u8F6E\uFF0C\u7136\u540E\u5237\u65B0\u3002",
19419
+ windows: "\u6EDA\u52A8\u7A97\u53E3",
19420
+ colTurns: "\u8F6E\u6B21",
19421
+ colCacheHit: "\u7F13\u5B58\u547D\u4E2D",
19422
+ colCost: "\u8D39\u7528 (USD)",
19423
+ colCacheSaved: "\u7F13\u5B58\u8282\u7701",
19424
+ colVsClaude: "\u5BF9\u6BD4 Claude",
19425
+ colSaved: "\u8282\u7701",
19426
+ mostUsed: "\u6700\u5E38\u7528\u6A21\u578B",
19427
+ colModel: "\u6A21\u578B"
19428
+ },
19429
+ sessions: {
19430
+ loading: "\u52A0\u8F7D\u4F1A\u8BDD\u2026",
19431
+ failed: "\u4F1A\u8BDD\u5931\u8D25\uFF1A{error}",
19432
+ noSessions: "\u6682\u65E0\u5DF2\u4FDD\u5B58\u7684\u4F1A\u8BDD\u3002",
19433
+ filterPlaceholder: "\u7B5B\u9009\u4F1A\u8BDD",
19434
+ msgs: "\u6761\u6D88\u606F",
19435
+ pickHint: "\u9009\u62E9\u5DE6\u4FA7\u7684\u4F1A\u8BDD\u4EE5\u67E5\u770B\u5176\u8F6C\u5F55\u7A3F\u3002",
19436
+ resumeTitle: "\u5728 TUI \u4E2D\u6062\u590D",
19437
+ resumeDesc: "\u4F1A\u8BDD\u4E2D\u9014\u5207\u6362\u9700\u8981\u91CD\u542F\uFF0C\u4EE5\u4FBF\u6D88\u606F\u65E5\u5FD7\u53EF\u4EE5\u5E72\u51C0\u5730\u56DE\u9000\u3002\u8BF7\u9000\u51FA\u5F53\u524D\u4F1A\u8BDD\uFF0C\u7136\u540E\u8FD0\u884C\uFF1A",
19438
+ loadingTranscript: "\u52A0\u8F7D\u8F6C\u5F55\u7A3F\u2026",
19439
+ emptyTranscript: "\u7A7A\u7684\u8F6C\u5F55\u7A3F\u3002",
19440
+ messages: "{count} \u6761\u6D88\u606F"
19441
+ },
19442
+ tools: {
19443
+ loading: "\u52A0\u8F7D\u5DE5\u5177\u2026",
19444
+ failed: "\u5DE5\u5177\u5931\u8D25\uFF1A{error}",
19445
+ noTools: "\u672A\u6CE8\u518C\u4EFB\u4F55\u5DE5\u5177\u3002",
19446
+ planMode: "\u8BA1\u5212\u6A21\u5F0F \u2014 \u5199\u5165\u53D7\u9650",
19447
+ colTool: "\u5DE5\u5177",
19448
+ colFlags: "\u6807\u5FD7",
19449
+ colDesc: "\u63CF\u8FF0",
19450
+ readOnly: "\u53EA\u8BFB",
19451
+ write: "\u5199\u5165",
19452
+ flat: "\u6241\u5E73"
19453
+ },
19454
+ permissions: {
19455
+ loading: "\u52A0\u8F7D\u6743\u9650\u2026",
19456
+ failed: "\u6743\u9650\u5931\u8D25\uFF1A{error}",
19457
+ yoloTitle: "YOLO \u6A21\u5F0F",
19458
+ yoloDesc: "\u6240\u6709 shell \u547D\u4EE4\u81EA\u52A8\u8FD0\u884C\uFF0C\u5141\u8BB8\u5217\u8868\u88AB\u7ED5\u8FC7\u3002\u5728 TUI \u4E2D\u4F7F\u7528 /mode review \u5207\u6362\u56DE\u6765\u3002",
19459
+ project: "\u9879\u76EE",
19460
+ builtin: "\u5185\u7F6E",
19461
+ addPrefix: "\u6DFB\u52A0\u524D\u7F00",
19462
+ addPlaceholder: '\u4F8B\u5982 "npm run build" \u6216 "deploy.sh"',
19463
+ clearAll: "\u6E05\u9664\u5168\u90E8",
19464
+ alreadyIn: "{prefix} \u5DF2\u5728\u5217\u8868\u4E2D",
19465
+ added: "\u5DF2\u6DFB\u52A0\uFF1A{prefix}",
19466
+ removed: "\u5DF2\u79FB\u9664\uFF1A{prefix}",
19467
+ cleared: "\u5DF2\u6E05\u9664 {count} \u6761",
19468
+ removeConfirm: '\u4ECE\u6B64\u9879\u76EE\u7684\u5141\u8BB8\u5217\u8868\u4E2D\u79FB\u9664 "{prefix}"\uFF1F',
19469
+ clearConfirm: "\u6E05\u9664\u6240\u6709\u9879\u76EE\u5141\u8BB8\u5217\u8868\u6761\u76EE\uFF1F\u5185\u7F6E\u6761\u76EE\u4E0D\u53D7\u5F71\u54CD\u3002",
19470
+ projectAllowlist: "\u9879\u76EE\u5141\u8BB8\u5217\u8868 \xB7 {count}",
19471
+ nothingStored: "\u6B64\u9879\u76EE\u6682\u65E0\u5B58\u50A8\u7684\u6761\u76EE\u3002",
19472
+ colNum: "#",
19473
+ colPrefix: "\u524D\u7F00",
19474
+ builtinTitle: "\u5185\u7F6E \xB7 {count} \xB7 \u53EA\u8BFB",
19475
+ standaloneWarning: "\u4FEE\u6539\u64CD\u4F5C\u9700\u8981\u5728\u6D3B\u8DC3\u7684 reasonix code \u4F1A\u8BDD\u5185\u6267\u884C /dashboard \u2014 \u72EC\u7ACB\u6A21\u5F0F\u7684 reasonix dashboard \u65E0\u6CD5\u786E\u5B9A\u8981\u7F16\u8F91\u54EA\u4E2A\u9879\u76EE\u7684\u5141\u8BB8\u5217\u8868\u3002"
19476
+ },
19477
+ mcp: {
19478
+ loading: "\u52A0\u8F7D MCP\u2026",
19479
+ servers: "MCP \u670D\u52A1\u5668 \xB7 {count} \u4E2A\u5DF2\u6865\u63A5",
19480
+ all: "\u5168\u90E8",
19481
+ live: "\u5728\u7EBF",
19482
+ unbridged: "\u672A\u6865\u63A5",
19483
+ specPlaceholder: "\u89C4\u683C \u2014 \u4F8B\u5982 fs=npx -y @modelcontextprotocol/...",
19484
+ saved: "\u5DF2\u4FDD\u5B58",
19485
+ savedRestart: "\u5DF2\u4FDD\u5B58 \u2014 \u91CD\u542F reasonix code \u4EE5\u6865\u63A5\u6B64\u670D\u52A1\u5668",
19486
+ removed: "\u5DF2\u79FB\u9664 \u2014 \u91CD\u542F\u4EE5\u65AD\u5F00\u5B9E\u65F6\u6865\u63A5",
19487
+ removeConfirm: "\u4ECE\u914D\u7F6E\u4E2D\u79FB\u9664 MCP \u89C4\u683C\uFF1F\n\n{spec}",
19488
+ noServers: "\u6B64\u4F1A\u8BDD\u4E2D\u65E0 MCP \u670D\u52A1\u5668\u3002",
19489
+ tools: "\u4E2A\u5DE5\u5177",
19490
+ inConfig: "\u5728\u914D\u7F6E\u4E2D \xB7 \u672A\u52A0\u8F7D",
19491
+ unbridgedTitle: "\u672A\u6865\u63A5 \xB7 \u5728\u914D\u7F6E\u4E2D",
19492
+ removeBtn: "\u79FB\u9664",
19493
+ spec: "\u89C4\u683C",
19494
+ whyUnbridged: "\u4E3A\u4EC0\u4E48\u672A\u6865\u63A5\uFF1F",
19495
+ whyUnbridgedDesc: "\u6B64\u89C4\u683C\u5B58\u5728\u4E8E\u60A8\u7684 config.json \u4E2D\uFF0C\u4F46\u672A\u6865\u63A5\u5230\u5B9E\u65F6\u4F1A\u8BDD\u3002MCP \u670D\u52A1\u5668\u5728 reasonix code \u542F\u52A8\u65F6\u8FDE\u63A5\uFF1B\u4EEA\u8868\u76D8\u672C\u8EAB\u65E0\u6CD5\u751F\u6210\u5B50\u8FDB\u7A0B\u3002",
19496
+ whyUnbridgedHint: "\u6FC0\u6D3B\u65B9\u6CD5\uFF1A\u91CD\u542F reasonix code\uFF0C\u7136\u540E\u5237\u65B0\u6B64\u4EEA\u8868\u76D8\u3002",
19497
+ pickHint: "\u9009\u62E9\u5DE6\u4FA7\u7684 MCP \u670D\u52A1\u5668\u4EE5\u68C0\u67E5\u5DE5\u5177 / \u8D44\u6E90 / \u63D0\u793A\u3002",
19498
+ toolsTitle: "\u5DE5\u5177 \xB7 {count}",
19499
+ resourcesTitle: "\u8D44\u6E90 \xB7 {count}",
19500
+ promptsTitle: "\u63D0\u793A \xB7 {count}",
19501
+ colName: "\u540D\u79F0",
19502
+ colDesc: "\u63CF\u8FF0",
19503
+ colUri: "URI"
19504
+ },
19505
+ memory: {
19506
+ loading: "\u52A0\u8F7D\u8BB0\u5FC6\u2026",
19507
+ files: "\u8BB0\u5FC6 \xB7 {count} \u4E2A\u6587\u4EF6",
19508
+ exists: "\u5DF2\u5B58\u5728",
19509
+ create: "\u521B\u5EFA",
19510
+ noFiles: "\u6682\u65E0\u8BB0\u5FC6\u6587\u4EF6\u3002",
19511
+ pickHint: "\u9009\u62E9\u5DE6\u4FA7\u7684\u8BB0\u5FC6\u6587\u4EF6\u3002",
19512
+ pickDesc: "\u9879\u76EE REASONIX.md \u53EF\u63D0\u4EA4\uFF1B\u5168\u5C40\u7B14\u8BB0\u5B58\u50A8\u5728 ~/.reasonix/memory/\u3002",
19513
+ chars: "{count} \u4E2A\u5B57\u7B26",
19514
+ saved: "\u5DF2\u4FDD\u5B58 {scope}",
19515
+ reloadHint: "\u5728\u4E0B\u6B21 /new \u6216\u4F1A\u8BDD\u91CD\u542F\u65F6\u91CD\u65B0\u52A0\u8F7D"
19516
+ },
19517
+ hooks: {
19518
+ loading: "\u52A0\u8F7D\u94A9\u5B50\u2026",
19519
+ resolved: "\u5DF2\u89E3\u6790",
19520
+ eventMatrix: "\u4E8B\u4EF6\u77E9\u9635",
19521
+ matrixSub: "{scripts} \u4E2A\u811A\u672C \xD7 {events} \u4E2A\u4E8B\u4EF6",
19522
+ noHooks: "\u672A\u914D\u7F6E\u94A9\u5B50\u3002\u7F16\u8F91\u4E0B\u65B9\u7684 JSON \u4EE5\u6DFB\u52A0\u3002",
19523
+ colScript: "\u811A\u672C",
19524
+ noProject: "\u65E0\u6D3B\u8DC3\u9879\u76EE \u2014 \u5728 reasonix code \u4E2D\u6253\u5F00 /dashboard \u4EE5\u7F16\u8F91\u9879\u76EE\u94A9\u5B50\u3002",
19525
+ saveReload: "\u4FDD\u5B58\u5E76\u91CD\u8F7D",
19526
+ discard: "\u653E\u5F03\u66F4\u6539",
19527
+ savedReloaded: "\u5DF2\u4FDD\u5B58\u5E76\u91CD\u8F7D {scope}",
19528
+ recentRuns: "\u8FD1\u671F\u8FD0\u884C",
19529
+ noRuns: "\u8FD1\u671F\u4F1A\u8BDD\u65E5\u5FD7\u4E2D\u65E0\u94A9\u5B50\u8FD0\u884C\u8BB0\u5F55\u3002",
19530
+ colWhen: "\u65F6\u95F4",
19531
+ colPhase: "\u9636\u6BB5",
19532
+ colHook: "\u94A9\u5B50",
19533
+ colOutcome: "\u7ED3\u679C"
19534
+ },
19535
+ skills: {
19536
+ loading: "\u52A0\u8F7D\u6280\u80FD\u2026",
19537
+ filterPlaceholder: "\u7B5B\u9009\u6280\u80FD",
19538
+ project: "\u9879\u76EE",
19539
+ global: "\u5168\u5C40",
19540
+ builtin: "\u5185\u7F6E",
19541
+ newSkill: "\u65B0\u6280\u80FD",
19542
+ noDescription: "\uFF08\u65E0\u63CF\u8FF0\uFF09",
19543
+ runs7d: "\u6B21\u8FD0\u884C \xB7 7 \u5929",
19544
+ pickHint: "\u9009\u62E9\u5DE6\u4FA7\u7684\u6280\u80FD\uFF0C\u6216\u5728\u4E0A\u65B9\u521B\u5EFA\u65B0\u6280\u80FD\u3002",
19545
+ readOnlyBuiltin: "\u53EA\u8BFB \xB7 \u5185\u7F6E",
19546
+ builtinDesc: "\u5185\u7F6E\u6280\u80FD\u968F Reasonix \u4E00\u8D77\u53D1\u5E03\uFF1B\u6A21\u578B\u4F1A\u81EA\u52A8\u8BC6\u522B\u3002\u5982\u9700\u81EA\u5B9A\u4E49\uFF0C\u8BF7\u521B\u5EFA\u540C\u540D\u7684\u9879\u76EE\u6216\u5168\u5C40\u6280\u80FD\u3002",
19547
+ saved: "\u5DF2\u4FDD\u5B58 {scope}/{name}",
19548
+ deleteConfirm: "\u5220\u9664\u6280\u80FD {scope}/{name}\uFF1F",
19549
+ reloadHint: "\u5728\u4E0B\u6B21 /new \u6216\u4F1A\u8BDD\u91CD\u542F\u65F6\u91CD\u65B0\u52A0\u8F7D"
19550
+ },
19551
+ system: {
19552
+ loading: "\u52A0\u8F7D\u5065\u5EB7\u72B6\u6001\u2026",
19553
+ failed: "\u5065\u5EB7\u68C0\u67E5\u5931\u8D25\uFF1A{error}",
19554
+ healthChecks: "\u5065\u5EB7\u68C0\u67E5",
19555
+ version: "\u7248\u672C",
19556
+ checking: "\u68C0\u67E5\u4E2D",
19557
+ latest: "\u25CF \u6700\u65B0",
19558
+ outOfDate: "\u25CF \u9700\u8981\u66F4\u65B0",
19559
+ versionPending: "\u7248\u672C\u68C0\u67E5\u4E2D",
19560
+ upToDate: "\u5DF2\u662F\u6700\u65B0",
19561
+ latestVer: "\u6700\u65B0\uFF1A{version}",
19562
+ sessions: "\u4F1A\u8BDD",
19563
+ ok: "\u25CF \u6B63\u5E38",
19564
+ memory: "\u8BB0\u5FC6",
19565
+ semanticIndex: "\u8BED\u4E49\u7D22\u5F15",
19566
+ built: "\u25CF \u5DF2\u6784\u5EFA",
19567
+ none: "\u2014 \u65E0",
19568
+ runIndex: "\u8FD0\u884C reasonix index \u4EE5\u6784\u5EFA",
19569
+ usageLog: "\u7528\u91CF\u65E5\u5FD7",
19570
+ backgroundJobs: "\u540E\u53F0\u4EFB\u52A1",
19571
+ noSession: "\u2014 \u65E0\u4F1A\u8BDD",
19572
+ running: "{count} \u4E2A\u8FD0\u884C\u4E2D",
19573
+ attachHint: "\u8FDE\u63A5\u4F1A\u8BDD\u4EE5\u67E5\u770B\u4EFB\u52A1",
19574
+ shellSpawn: "Shell + \u751F\u6210",
19575
+ paths: "\u8DEF\u5F84",
19576
+ home: "\u4E3B\u76EE\u5F55",
19577
+ sessionsPath: "\u4F1A\u8BDD",
19578
+ memoryPath: "\u8BB0\u5FC6",
19579
+ semanticPath: "\u8BED\u4E49",
19580
+ usagePath: "\u7528\u91CF"
19581
+ },
19582
+ plans: {
19583
+ loading: "\u52A0\u8F7D\u8BA1\u5212\u2026",
19584
+ failed: "\u8BA1\u5212\u5931\u8D25\uFF1A{error}",
19585
+ noPlans: "\u6682\u65E0\u5F52\u6863\u8BA1\u5212 \u2014 \u8FD0\u884C\u8C03\u7528 submit_plan \u548C mark_step_complete \u7684\u8F6E\u6B21\u3002",
19586
+ filterPlaceholder: "\u7B5B\u9009\u8BA1\u5212",
19587
+ active: "\u8FDB\u884C\u4E2D",
19588
+ done: "\u5DF2\u5B8C\u6210",
19589
+ steps: "\u6B65\u9AA4",
19590
+ pickHint: "\u9009\u62E9\u5DE6\u4FA7\u7684\u8BA1\u5212\u3002",
19591
+ noTitle: "\uFF08\u65E0\u6807\u9898\uFF09",
19592
+ stepTimeline: "\u6B65\u9AA4\u65F6\u95F4\u7EBF \xB7 {done} / {total}",
19593
+ step: "\u6B65\u9AA4 {n}"
19594
+ },
19595
+ semantic: {
19596
+ codeRequired: "\u8BED\u4E49 \u2014 \u9700\u8981\u4EE3\u7801\u6A21\u5F0F",
19597
+ indexBuilt: "\u7D22\u5F15\u5DF2\u6784\u5EFA",
19598
+ noIndex: "\u5C1A\u65E0\u7D22\u5F15",
19599
+ ready: "\u5C31\u7EEA",
19600
+ setupNeeded: "\u9700\u8981\u8BBE\u7F6E",
19601
+ installOllama: "\u5B89\u88C5 Ollama",
19602
+ installOllamaDesc: "Reasonix \u4E0D\u4F1A\u4E3A\u60A8\u8FD0\u884C\u5305\u7BA1\u7406\u5668\u3002\u8BF7\u5148\u5B89\u88C5 Ollama\uFF0C\u7136\u540E\u8FD4\u56DE\uFF1A",
19603
+ macWindows: "macOS / Windows\uFF1A",
19604
+ download: "\u4ECE ollama.com/download \u4E0B\u8F7D",
19605
+ linux: "Linux\uFF1A",
19606
+ refreshHint: "\u5B89\u88C5\u540E\u5237\u65B0 \u2014 \u6B64\u9762\u677F\u5C06\u63D0\u4F9B\u542F\u52A8\u5B88\u62A4\u8FDB\u7A0B\u548C\u62C9\u53D6 {model} \u7684\u9009\u9879\u3002",
19607
+ daemon: "\u5B88\u62A4\u8FDB\u7A0B",
19608
+ daemonDesc: "ollama \u5728\u60A8\u7684 PATH \u4E2D\uFF0C\u4F46 HTTP \u5B88\u62A4\u8FDB\u7A0B\u4E0D\u53EF\u8FBE\u3002",
19609
+ startDaemon: "\u542F\u52A8\u5B88\u62A4\u8FDB\u7A0B",
19610
+ runsOllama: "\u4EE5\u5206\u79BB\u6A21\u5F0F\u8FD0\u884C ollama serve",
19611
+ model: "\u6A21\u578B",
19612
+ modelMissing: "{model} \u5C1A\u672A\u5B89\u88C5\u3002",
19613
+ modelSize: "\u9996\u6B21\u62C9\u53D6\u7EA6 270 MB\u3002",
19614
+ pulling: "\u62C9\u53D6\u4E2D\u2026",
19615
+ pullModel: "\u62C9\u53D6 {model}",
19616
+ indexStatus: "\u7D22\u5F15\u72B6\u6001",
19617
+ builtStatus: "\u25CF \u5DF2\u6784\u5EFA",
19618
+ chunks: "\u5206\u5757",
19619
+ files: "\u6587\u4EF6",
19620
+ dim: "\u7EF4\u5EA6",
19621
+ size: "\u5927\u5C0F",
19622
+ lastBuild: "\u4E0A\u6B21\u6784\u5EFA",
19623
+ runIndexHint: "\u8FD0\u884C\u7D22\u5F15\u4EE5\u542F\u7528 semantic_search\u3002",
19624
+ reIndex: "\u91CD\u5EFA\u7D22\u5F15",
19625
+ build: "\u6784\u5EFA",
19626
+ rebuild: "\u5B8C\u5168\u91CD\u5EFA",
19627
+ stop: "\u505C\u6B62",
19628
+ ollama: "Ollama",
19629
+ binary: "\u4E8C\u8FDB\u5236",
19630
+ found: "\u5DF2\u627E\u5230",
19631
+ missing: "\u7F3A\u5931",
19632
+ daemonStatus: "\u5B88\u62A4\u8FDB\u7A0B",
19633
+ up: "\u8FD0\u884C\u4E2D",
19634
+ down: "\u5DF2\u505C\u6B62",
19635
+ pulled: "\u5DF2\u62C9\u53D6",
19636
+ indexConfig: "\u7D22\u5F15\u914D\u7F6E",
19637
+ reset: "\u91CD\u7F6E",
19638
+ excludeDirs: "\u6392\u9664\u76EE\u5F55",
19639
+ excludeFiles: "\u6392\u9664\u6587\u4EF6",
19640
+ excludeExts: "\u6392\u9664\u6269\u5C55\u540D",
19641
+ excludePatterns: "\u6392\u9664\u6A21\u5F0F",
19642
+ glob: "glob",
19643
+ respectGitignore: "\u9075\u5FAA .gitignore",
19644
+ maxFileBytes: "\u6700\u5927\u6587\u4EF6\u5B57\u8282\u6570",
19645
+ skipLarger: "\u8DF3\u8FC7\u5927\u4E8E ~{size} MiB \u7684\u6587\u4EF6",
19646
+ preview: "\u9884\u89C8",
19647
+ searchPlaceholder: "\u63CF\u8FF0\u8981\u67E5\u627E\u7684\u5185\u5BB9 \u2014 '\u54EA\u91CC\u5904\u7406\u4E2D\u6B62\u4FE1\u53F7'",
19648
+ searching: "\u641C\u7D22\u4E2D\u2026",
19649
+ results: "{count} \u4E2A\u7ED3\u679C \xB7 {ms}ms \xB7 {model}",
19650
+ noMatches: "\u6CA1\u6709\u8D85\u8FC7\u5206\u6570\u9608\u503C\u7684\u5339\u914D\u3002",
19651
+ previewSummary: "\u9884\u89C8 \u2014 \u5C06\u7D22\u5F15 {included} \u4E2A\u6587\u4EF6\uFF0C\u8DF3\u8FC7 {skipped} \u4E2A",
19652
+ nothingSkipped: "\u65E0\u8DF3\u8FC7 \u2014 \u6240\u6709\u904D\u5386\u7684\u6587\u4EF6\u90FD\u5C06\u88AB\u7D22\u5F15\u3002",
19653
+ firstIncluded: "\u524D {count} \u4E2A\u5305\u542B\u7684\u6587\u4EF6",
19654
+ job: "\u4EFB\u52A1",
19655
+ phaseScan: "\u626B\u63CF\u6587\u4EF6",
19656
+ phaseEmbed: "\u5D4C\u5165\u5206\u5757",
19657
+ phaseWrite: "\u5199\u5165\u7D22\u5F15",
19658
+ phaseDone: "\u5B8C\u6210",
19659
+ phaseError: "\u9519\u8BEF",
19660
+ stopping: "\u505C\u6B62\u4E2D",
19661
+ scanned: "\u5DF2\u626B\u63CF {count}",
19662
+ changed: "\u5DF2\u53D8\u66F4 {count}",
19663
+ skipped: "\u5DF2\u8DF3\u8FC7 {count}",
19664
+ chunksProgress: "{done} / {total}\uFF08{pct}%\uFF09",
19665
+ result: "\u7ED3\u679C",
19666
+ added: "\u5DF2\u6DFB\u52A0 {count}",
19667
+ removed: "\u5DF2\u79FB\u9664 {count}",
19668
+ failed: "\u5931\u8D25 {count}",
19669
+ skippedFiles: "{total} \u4E2A\u6587\u4EF6\uFF08{details}\uFF09",
19670
+ rebuildStarted: "\u5DF2\u542F\u52A8\u5B8C\u5168\u91CD\u5EFA",
19671
+ incrementalStarted: "\u5DF2\u542F\u52A8\u589E\u91CF\u7D22\u5F15",
19672
+ stopRequested: "\u5DF2\u8BF7\u6C42\u505C\u6B62 \u2014 \u5F53\u524D\u5206\u5757\u6279\u6B21\u5C06\u9996\u5148\u5B8C\u6210",
19673
+ startingDaemon: "\u6B63\u5728\u542F\u52A8 ollama \u5B88\u62A4\u8FDB\u7A0B\uFF0815 \u79D2\u8D85\u65F6\uFF09\u2026",
19674
+ daemonUp: "\u5B88\u62A4\u8FDB\u7A0B\u5DF2\u542F\u52A8",
19675
+ daemonTimeout: "\u5B88\u62A4\u8FDB\u7A0B\u672A\u5728\u89C4\u5B9A\u65F6\u95F4\u5185\u542F\u52A8 \u2014 \u8BF7\u624B\u52A8\u68C0\u67E5 ollama serve",
19676
+ pullingModel: "\u6B63\u5728\u62C9\u53D6 {model} \u2014 \u9996\u6B21\u5B89\u88C5\u53EF\u80FD\u9700\u8981\u51E0\u5206\u949F",
19677
+ savedConfig: "\u5DF2\u4FDD\u5B58 \xB7 {count} \u4E2A\u5B57\u6BB5\u5DF2\u66F4\u65B0 \xB7 \u91CD\u65B0\u8FD0\u884C\u7D22\u5F15\u4EE5\u5E94\u7528",
19678
+ runningPreview: "\u6B63\u5728\u5BF9\u9879\u76EE\u6839\u76EE\u5F55\u6267\u884C\u5E72\u8FD0\u884C\u2026",
19679
+ exclude: "\u6392\u9664"
19680
+ },
19681
+ modal: {
19682
+ shellTitle: "Shell \u547D\u4EE4",
19683
+ shellBgTitle: "\u540E\u53F0\u8FDB\u7A0B",
19684
+ shellSubtitle: "\u6A21\u578B\u60F3\u8981\u8FD0\u884C\u4E00\u6761 Shell \u547D\u4EE4",
19685
+ shellBgSubtitle: "\u957F\u65F6\u95F4\u8FD0\u884C \u2014 \u6279\u51C6\u540E\u7EE7\u7EED\u8FD0\u884C",
19686
+ runOnce: "\u8FD0\u884C\u4E00\u6B21",
19687
+ alwaysAllow: '\u59CB\u7EC8\u5141\u8BB8 "{prefix}"',
19688
+ deny: "\u62D2\u7EDD",
19689
+ choiceTitle: "\u6A21\u578B\u9700\u8981\u60A8\u9009\u62E9",
19690
+ typeOwn: "\u8F93\u5165\u81EA\u5B9A\u4E49\u56DE\u7B54",
19691
+ typeOwnSummary: "\u4EE5\u4E0A\u9009\u9879\u90FD\u4E0D\u5408\u9002 \u2014 \u5199\u4E00\u4E2A\u81EA\u7531\u683C\u5F0F\u7684\u56DE\u590D\u3002",
19692
+ typePlaceholder: "\u8F93\u5165\u81EA\u7531\u683C\u5F0F\u7684\u56DE\u7B54\u2026",
19693
+ send: "\u53D1\u9001",
19694
+ cancel: "\u53D6\u6D88",
19695
+ cancelSummary: "\u653E\u5F03\u6B64\u95EE\u9898\u3002\u6A21\u578B\u5C06\u8BE2\u95EE\u60A8\u5B9E\u9645\u60F3\u8981\u4EC0\u4E48\u3002",
19696
+ planTitle: "\u8BA1\u5212\u5DF2\u63D0\u4EA4",
19697
+ planSubtitle: "\u6A21\u578B\u63D0\u51FA\u4E86\u4E00\u9879\u8BA1\u5212\uFF1B\u8BF7\u5BA1\u9605\u540E\u9009\u62E9",
19698
+ approveInstructions: "\u53EF\u9009\u7684\u6700\u540E\u6307\u793A / \u5BF9\u5F00\u653E\u95EE\u9898\u7684\u56DE\u7B54\uFF08Enter \u53D1\u9001\u7A7A\u767D\uFF09",
19699
+ refinePlaceholder: "\u9700\u8981\u66F4\u6539\u4EC0\u4E48\uFF1F\u8BF7\u5177\u4F53\u8BF4\u660E\u3002",
19700
+ approve: "\u6279\u51C6",
19701
+ refine: "\u4F18\u5316",
19702
+ sendRefinement: "\u53D1\u9001\u4F18\u5316",
19703
+ editTitle: "\u7F16\u8F91\u5F85\u5BA1\u9605",
19704
+ editSubtitle: "{path} \xB7 {remaining} / {total} \u4E2A\u5757\u5269\u4F59",
19705
+ before: "\u4FEE\u6539\u524D",
19706
+ after: "\u4FEE\u6539\u540E",
19707
+ workspaceTitle: "\u6A21\u578B\u60F3\u8981\u5207\u6362\u5DE5\u4F5C\u533A",
19708
+ workspaceSubtitle: "\u540E\u7EED\u6240\u6709\u6587\u4EF6 / Shell / \u8BB0\u5FC6\u5DE5\u5177\u5C06\u9488\u5BF9\u65B0\u6839\u76EE\u5F55\u89E3\u6790",
19709
+ switchBtn: "\u5207\u6362 (Enter)",
19710
+ denyBtn: "\u62D2\u7EDD (Esc)",
19711
+ stepComplete: "\u6B65\u9AA4\u5B8C\u6210{counter}",
19712
+ continueBtn: "\u7EE7\u7EED",
19713
+ reviseBtn: "\u4FEE\u8BA2\u2026",
19714
+ stopBtn: "\u505C\u6B62",
19715
+ revisionTitle: "\u6A21\u578B\u63D0\u51FA\u4E86\u8BA1\u5212\u4FEE\u8BA2",
19716
+ sendRevision: "\u53D1\u9001\u4FEE\u8BA2",
19717
+ accept: "\u63A5\u53D7",
19718
+ reject: "\u62D2\u7EDD",
19719
+ arguments: "\u53C2\u6570",
19720
+ revisePlaceholder: "\u4E0B\u4E00\u6B65\u4E4B\u524D\u9700\u8981\u66F4\u6539\u4EC0\u4E48\uFF1F\u7559\u7A7A\u5219\u76F4\u63A5\u7EE7\u7EED\u3002"
19721
+ }
19722
+ };
19723
+
19724
+ // dashboard/src/i18n/index.ts
19725
+ var t4 = createT({ en, "zh-CN": zhCN });
19726
+
18639
19727
  // node_modules/marked/lib/marked.esm.js
18640
19728
  function _getDefaults() {
18641
19729
  return {
@@ -18950,7 +20038,7 @@ var _Tokenizer = class {
18950
20038
  }
18951
20039
  raw = cap[0];
18952
20040
  src = src.substring(raw.length);
18953
- let line = cap[2].split("\n", 1)[0].replace(/^\t+/, (t4) => " ".repeat(3 * t4.length));
20041
+ let line = cap[2].split("\n", 1)[0].replace(/^\t+/, (t5) => " ".repeat(3 * t5.length));
18954
20042
  let nextLine = src.split("\n", 1)[0];
18955
20043
  let indent = 0;
18956
20044
  if (this.options.pedantic) {
@@ -19053,8 +20141,8 @@ var _Tokenizer = class {
19053
20141
  this.lexer.state.top = false;
19054
20142
  list2.items[i3].tokens = this.lexer.blockTokens(list2.items[i3].text, []);
19055
20143
  if (!list2.loose) {
19056
- const spacers = list2.items[i3].tokens.filter((t4) => t4.type === "space");
19057
- const hasMultipleLineBreaks = spacers.length > 0 && spacers.some((t4) => /\n.*\n/.test(t4.raw));
20144
+ const spacers = list2.items[i3].tokens.filter((t5) => t5.type === "space");
20145
+ const hasMultipleLineBreaks = spacers.length > 0 && spacers.some((t5) => /\n.*\n/.test(t5.raw));
19058
20146
  list2.loose = hasMultipleLineBreaks;
19059
20147
  }
19060
20148
  }
@@ -20817,6 +21905,7 @@ function parseToolArgs(raw) {
20817
21905
  }
20818
21906
  }
20819
21907
  function ToolCard({ msg }) {
21908
+ useLang();
20820
21909
  const args = parseToolArgs(msg.toolArgs);
20821
21910
  const name = msg.toolName ?? "tool";
20822
21911
  const path = args?.path ?? args?.file_path ?? args?.filename;
@@ -20898,7 +21987,7 @@ function ToolCard({ msg }) {
20898
21987
  <span class="tool-card-icon">▣</span>
20899
21988
  <span class="tool-card-name">${name}</span>
20900
21989
  </div>
20901
- ${args ? html4`<details class="tool-card-args"><summary>arguments</summary><pre>${escapeHtml(JSON.stringify(args, null, 2))}</pre></details>` : null}
21990
+ ${args ? html4`<details class="tool-card-args"><summary>${t4("modal.arguments")}</summary><pre>${escapeHtml(JSON.stringify(args, null, 2))}</pre></details>` : null}
20902
21991
  <pre class="tool-card-output">${msg.text}</pre>
20903
21992
  </div>
20904
21993
  `;
@@ -20940,28 +22029,30 @@ function ModalCard({ accent, icon, title, subtitle, children }) {
20940
22029
  `;
20941
22030
  }
20942
22031
  function ShellModal({ modal, onResolve }) {
22032
+ useLang();
20943
22033
  const isBg = modal.shellKind === "run_background";
20944
22034
  return html4`
20945
22035
  <${ModalCard}
20946
22036
  accent="#f87171"
20947
22037
  icon=${isBg ? "\u23F1" : "\u26A1"}
20948
- title=${isBg ? "background process" : "shell command"}
20949
- subtitle=${isBg ? "long-running \u2014 keeps running after approval" : "model wants to run a shell command"}
22038
+ title=${isBg ? t4("modal.shellBgTitle") : t4("modal.shellTitle")}
22039
+ subtitle=${isBg ? t4("modal.shellBgSubtitle") : t4("modal.shellSubtitle")}
20950
22040
  >
20951
22041
  <div class="modal-cmd"><span class="modal-cmd-prompt">$</span> <code>${modal.command}</code></div>
20952
22042
  <div class="modal-actions">
20953
- <button class="primary" onClick=${() => onResolve("shell", "run_once")}>Run once</button>
20954
- <button onClick=${() => onResolve("shell", "always_allow")}>Always allow "${modal.allowPrefix}"</button>
20955
- <button class="danger" onClick=${() => onResolve("shell", "deny")}>Deny</button>
22043
+ <button class="primary" onClick=${() => onResolve("shell", "run_once")}>${t4("modal.runOnce")}</button>
22044
+ <button onClick=${() => onResolve("shell", "always_allow")}>${t4("modal.alwaysAllow", { prefix: modal.allowPrefix ?? "" })}</button>
22045
+ <button class="danger" onClick=${() => onResolve("shell", "deny")}>${t4("modal.deny")}</button>
20956
22046
  </div>
20957
22047
  <//>
20958
22048
  `;
20959
22049
  }
20960
22050
  function ChoiceModal({ modal, onResolve }) {
22051
+ useLang();
20961
22052
  const [custom, setCustom] = p2("");
20962
22053
  const [showCustom, setShowCustom] = p2(false);
20963
22054
  return html4`
20964
- <${ModalCard} accent="#f0abfc" icon="🔀" title="model wants you to pick" subtitle=${modal.question}>
22055
+ <${ModalCard} accent="#f0abfc" icon="🔀" title=${t4("modal.choiceTitle")} subtitle=${modal.question}>
20965
22056
  ${modal.options.map(
20966
22057
  (opt) => html4`
20967
22058
  <button
@@ -20978,60 +22069,61 @@ function ChoiceModal({ modal, onResolve }) {
20978
22069
  ${modal.allowCustom ? showCustom ? html4`
20979
22070
  <div class="modal-custom">
20980
22071
  <textarea
20981
- placeholder="Type a free-form answer…"
22072
+ placeholder=${t4("modal.typePlaceholder")}
20982
22073
  rows="2"
20983
22074
  value=${custom}
20984
22075
  onInput=${(e3) => setCustom(e3.target.value)}
20985
22076
  ></textarea>
20986
22077
  <div class="modal-actions">
20987
- <button class="primary" onClick=${() => onResolve("choice", { kind: "custom", text: custom })} disabled=${!custom.trim()}>Send</button>
22078
+ <button class="primary" onClick=${() => onResolve("choice", { kind: "custom", text: custom })} disabled=${!custom.trim()}>${t4("modal.send")}</button>
20988
22079
  <button onClick=${() => {
20989
22080
  setShowCustom(false);
20990
22081
  setCustom("");
20991
- }}>Back</button>
22082
+ }}>${t4("common.back")}</button>
20992
22083
  </div>
20993
22084
  </div>
20994
22085
  ` : html4`
20995
22086
  <button class="modal-choice-row" onClick=${() => setShowCustom(true)}>
20996
22087
  <span class="modal-choice-id">·</span>
20997
- <span class="modal-choice-title">Type my own answer</span>
20998
- <span class="modal-choice-summary">None of the above fits — write a free-form reply.</span>
22088
+ <span class="modal-choice-title">${t4("modal.typeOwn")}</span>
22089
+ <span class="modal-choice-summary">${t4("modal.typeOwnSummary")}</span>
20999
22090
  </button>
21000
22091
  ` : null}
21001
22092
  <button class="modal-choice-row modal-choice-cancel" onClick=${() => onResolve("choice", { kind: "cancel" })}>
21002
22093
  <span class="modal-choice-id">×</span>
21003
- <span class="modal-choice-title">Cancel</span>
21004
- <span class="modal-choice-summary">Drop the question. Model will ask what you actually want.</span>
22094
+ <span class="modal-choice-title">${t4("modal.cancel")}</span>
22095
+ <span class="modal-choice-summary">${t4("modal.cancelSummary")}</span>
21005
22096
  </button>
21006
22097
  <//>
21007
22098
  `;
21008
22099
  }
21009
22100
  function PlanModal({ modal, onResolve }) {
22101
+ useLang();
21010
22102
  const [feedback, setFeedback] = p2("");
21011
22103
  const [stage, setStage] = p2(null);
21012
22104
  const send = () => onResolve("plan", stage, feedback);
21013
22105
  return html4`
21014
- <${ModalCard} accent="#67e8f9" icon="◆" title="plan submitted" subtitle="model proposed a plan; review then pick">
22106
+ <${ModalCard} accent="#67e8f9" icon="◆" title=${t4("modal.planTitle")} subtitle=${t4("modal.planSubtitle")}>
21015
22107
  <div class="md modal-plan-body" dangerouslySetInnerHTML=${{ __html: marked.parse(modal.body || "") }}></div>
21016
22108
  ${stage ? html4`
21017
22109
  <textarea
21018
- placeholder=${stage === "approve" ? "Optional last instructions / answers to open questions (Enter to send blank)" : "What needs to change? Be specific."}
22110
+ placeholder=${stage === "approve" ? t4("modal.approveInstructions") : t4("modal.refinePlaceholder")}
21019
22111
  rows="3"
21020
22112
  value=${feedback}
21021
22113
  onInput=${(e3) => setFeedback(e3.target.value)}
21022
22114
  ></textarea>
21023
22115
  <div class="modal-actions">
21024
- <button class="primary" onClick=${send}>${stage === "approve" ? "Approve" : "Send refinement"}</button>
22116
+ <button class="primary" onClick=${send}>${stage === "approve" ? t4("modal.approve") : t4("modal.sendRefinement")}</button>
21025
22117
  <button onClick=${() => {
21026
22118
  setStage(null);
21027
22119
  setFeedback("");
21028
- }}>Back</button>
22120
+ }}>${t4("common.back")}</button>
21029
22121
  </div>
21030
22122
  ` : html4`
21031
22123
  <div class="modal-actions">
21032
- <button class="primary" onClick=${() => setStage("approve")}>Approve</button>
21033
- <button onClick=${() => setStage("refine")}>Refine</button>
21034
- <button class="danger" onClick=${() => onResolve("plan", "cancel")}>Cancel</button>
22124
+ <button class="primary" onClick=${() => setStage("approve")}>${t4("modal.approve")}</button>
22125
+ <button onClick=${() => setStage("refine")}>${t4("modal.refine")}</button>
22126
+ <button class="danger" onClick=${() => onResolve("plan", "cancel")}>${t4("modal.cancel")}</button>
21035
22127
  </div>
21036
22128
  `}
21037
22129
  <//>
@@ -21099,6 +22191,7 @@ function pairDiffRows(diff) {
21099
22191
  return rows;
21100
22192
  }
21101
22193
  function EditReviewModal({ modal, onResolve }) {
22194
+ useLang();
21102
22195
  const search = modal.search ?? "";
21103
22196
  const replace = modal.replace ?? "";
21104
22197
  const lang = langFromPath(modal.path);
@@ -21109,16 +22202,16 @@ function EditReviewModal({ modal, onResolve }) {
21109
22202
  <${ModalCard}
21110
22203
  accent="#86efac"
21111
22204
  icon="◆"
21112
- title="edit pending review"
21113
- subtitle=${`${modal.path} \xB7 ${modal.remaining} of ${modal.total} blocks remaining`}
22205
+ title=${t4("modal.editTitle")}
22206
+ subtitle=${t4("modal.editSubtitle", { path: modal.path ?? "", remaining: modal.remaining, total: modal.total })}
21114
22207
  >
21115
22208
  <div class="edit-diff-wrap">
21116
22209
  <div class="edit-diff-head">
21117
22210
  <div class="edit-diff-side edit-diff-side-old">
21118
- <span class="edit-diff-marker">−</span> before
22211
+ <span class="edit-diff-marker">−</span> ${t4("modal.before")}
21119
22212
  </div>
21120
22213
  <div class="edit-diff-side edit-diff-side-new">
21121
- <span class="edit-diff-marker">+</span> after
22214
+ <span class="edit-diff-marker">+</span> ${t4("modal.after")}
21122
22215
  </div>
21123
22216
  </div>
21124
22217
  <div class="edit-diff-body">
@@ -21143,31 +22236,33 @@ function EditReviewModal({ modal, onResolve }) {
21143
22236
  </div>
21144
22237
  </div>
21145
22238
  <div class="modal-actions">
21146
- <button class="primary" onClick=${() => onResolve("edit-review", "apply")}>Apply (y)</button>
21147
- <button onClick=${() => onResolve("edit-review", "reject")}>Reject (n)</button>
21148
- <button onClick=${() => onResolve("edit-review", "apply-rest-of-turn")}>Apply rest (a)</button>
21149
- <button onClick=${() => onResolve("edit-review", "flip-to-auto")}>Flip to AUTO (A)</button>
22239
+ <button class="primary" onClick=${() => onResolve("edit-review", "apply")}>${t4("chat.confirmBtn")}</button>
22240
+ <button onClick=${() => onResolve("edit-review", "reject")}>${t4("chat.rejectBtn")}</button>
22241
+ <button onClick=${() => onResolve("edit-review", "apply-rest-of-turn")}>${t4("chat.applyRestBtn")}</button>
22242
+ <button onClick=${() => onResolve("edit-review", "flip-to-auto")}>${t4("chat.flipAutoBtn")}</button>
21150
22243
  </div>
21151
22244
  <//>
21152
22245
  `;
21153
22246
  }
21154
22247
  function WorkspaceModal({ modal, onResolve }) {
22248
+ useLang();
21155
22249
  return html4`
21156
22250
  <${ModalCard}
21157
22251
  accent="#fbbf24"
21158
22252
  icon="◇"
21159
- title="model wants to switch workspace"
21160
- subtitle="every subsequent file / shell / memory tool resolves against the new root"
22253
+ title=${t4("modal.workspaceTitle")}
22254
+ subtitle=${t4("modal.workspaceSubtitle")}
21161
22255
  >
21162
22256
  <div class="modal-cmd"><span class="modal-cmd-prompt">→</span> <code>${modal.path}</code></div>
21163
22257
  <div class="modal-actions">
21164
- <button class="primary" onClick=${() => onResolve("workspace", "switch")}>Switch (Enter)</button>
21165
- <button class="danger" onClick=${() => onResolve("workspace", "deny")}>Deny (Esc)</button>
22258
+ <button class="primary" onClick=${() => onResolve("workspace", "switch")}>${t4("modal.switchBtn")}</button>
22259
+ <button class="danger" onClick=${() => onResolve("workspace", "deny")}>${t4("modal.denyBtn")}</button>
21166
22260
  </div>
21167
22261
  <//>
21168
22262
  `;
21169
22263
  }
21170
22264
  function CheckpointModal({ modal, onResolve }) {
22265
+ useLang();
21171
22266
  const [reviseText, setReviseText] = p2("");
21172
22267
  const [staged, setStaged] = p2(false);
21173
22268
  const label = modal.title ? `${modal.stepId} \xB7 ${modal.title}` : modal.stepId;
@@ -21176,40 +22271,41 @@ function CheckpointModal({ modal, onResolve }) {
21176
22271
  <${ModalCard}
21177
22272
  accent="#a5f3fc"
21178
22273
  icon="✓"
21179
- title=${`step complete${counter}`}
22274
+ title=${t4("modal.stepComplete", { counter })}
21180
22275
  subtitle=${label}
21181
22276
  >
21182
22277
  ${staged ? html4`
21183
22278
  <textarea
21184
- placeholder="What needs to change before the next step? Leave blank to just continue."
22279
+ placeholder=${t4("modal.revisePlaceholder")}
21185
22280
  rows="3"
21186
22281
  value=${reviseText}
21187
22282
  onInput=${(e3) => setReviseText(e3.target.value)}
21188
22283
  ></textarea>
21189
22284
  <div class="modal-actions">
21190
- <button class="primary" onClick=${() => onResolve("checkpoint", "revise", reviseText)}>Send revision</button>
22285
+ <button class="primary" onClick=${() => onResolve("checkpoint", "revise", reviseText)}>${t4("modal.sendRevision")}</button>
21191
22286
  <button onClick=${() => {
21192
22287
  setStaged(false);
21193
22288
  setReviseText("");
21194
- }}>Back</button>
22289
+ }}>${t4("common.back")}</button>
21195
22290
  </div>
21196
22291
  ` : html4`
21197
22292
  <div class="modal-actions">
21198
- <button class="primary" onClick=${() => onResolve("checkpoint", "continue")}>Continue</button>
21199
- <button onClick=${() => setStaged(true)}>Revise…</button>
21200
- <button class="danger" onClick=${() => onResolve("checkpoint", "stop")}>Stop</button>
22293
+ <button class="primary" onClick=${() => onResolve("checkpoint", "continue")}>${t4("modal.continueBtn")}</button>
22294
+ <button onClick=${() => setStaged(true)}>${t4("modal.reviseBtn")}</button>
22295
+ <button class="danger" onClick=${() => onResolve("checkpoint", "stop")}>${t4("modal.stopBtn")}</button>
21201
22296
  </div>
21202
22297
  `}
21203
22298
  <//>
21204
22299
  `;
21205
22300
  }
21206
22301
  function RevisionModal({ modal, onResolve }) {
22302
+ useLang();
21207
22303
  const riskColor = (r3) => r3 === "high" ? "#f87171" : r3 === "med" ? "#fbbf24" : r3 === "low" ? "#86efac" : "#9ca3af";
21208
22304
  return html4`
21209
22305
  <${ModalCard}
21210
22306
  accent="#c4b5fd"
21211
22307
  icon="✎"
21212
- title="model proposed a plan revision"
22308
+ title=${t4("modal.revisionTitle")}
21213
22309
  subtitle=${modal.summary || modal.reason}
21214
22310
  >
21215
22311
  <div class="modal-revise-reason">${modal.reason}</div>
@@ -21226,8 +22322,8 @@ function RevisionModal({ modal, onResolve }) {
21226
22322
  )}
21227
22323
  </ol>
21228
22324
  <div class="modal-actions">
21229
- <button class="primary" onClick=${() => onResolve("revision", "accept")}>Accept</button>
21230
- <button class="danger" onClick=${() => onResolve("revision", "reject")}>Reject</button>
22325
+ <button class="primary" onClick=${() => onResolve("revision", "accept")}>${t4("modal.accept")}</button>
22326
+ <button class="danger" onClick=${() => onResolve("revision", "reject")}>${t4("modal.reject")}</button>
21231
22327
  </div>
21232
22328
  <//>
21233
22329
  `;
@@ -21281,6 +22377,7 @@ function fmtRelativeTime(iso) {
21281
22377
 
21282
22378
  // dashboard/src/panels/chat.ts
21283
22379
  function ChatPanel() {
22380
+ useLang();
21284
22381
  const [messages, setMessages] = p2([]);
21285
22382
  const [streaming, setStreaming] = p2(null);
21286
22383
  const [activeTool, setActiveTool] = p2(null);
@@ -21436,7 +22533,7 @@ function ChatPanel() {
21436
22533
  }
21437
22534
  };
21438
22535
  es.onerror = () => {
21439
- setError("event stream interrupted \u2014 reconnecting\u2026");
22536
+ setError(t4("chat.eventStreamError"));
21440
22537
  setTimeout(() => setError(null), 3e3);
21441
22538
  };
21442
22539
  return () => es.close();
@@ -21468,8 +22565,8 @@ function ChatPanel() {
21468
22565
  }, []);
21469
22566
  const newConversation = x2(async () => {
21470
22567
  if (busy) {
21471
- if (!confirm("A turn is in flight. Abort and start a new conversation?")) return;
21472
- } else if (messages.length > 0 && !confirm("Clear current conversation and start fresh?")) {
22568
+ if (!confirm(t4("chat.newConfirmBusy"))) return;
22569
+ } else if (messages.length > 0 && !confirm(t4("chat.newConfirm"))) {
21473
22570
  return;
21474
22571
  }
21475
22572
  try {
@@ -21477,7 +22574,7 @@ function ChatPanel() {
21477
22574
  setMessages([]);
21478
22575
  setStreaming(null);
21479
22576
  setActiveTool(null);
21480
- showToast("new conversation", "info");
22577
+ showToast(t4("chat.newToast"), "info");
21481
22578
  setTimeout(async () => {
21482
22579
  try {
21483
22580
  const r3 = await api("/messages");
@@ -21486,7 +22583,7 @@ function ChatPanel() {
21486
22583
  }
21487
22584
  }, 200);
21488
22585
  } catch (err) {
21489
- setError(`/new failed: ${err.message}`);
22586
+ setError(t4("chat.newFailed", { error: err.message }));
21490
22587
  }
21491
22588
  }, [busy, messages.length]);
21492
22589
  const clearScrollback = x2(async () => {
@@ -21495,7 +22592,7 @@ function ChatPanel() {
21495
22592
  setMessages([]);
21496
22593
  setStreaming(null);
21497
22594
  setActiveTool(null);
21498
- showToast("scrollback cleared", "info");
22595
+ showToast(t4("chat.clearToast"), "info");
21499
22596
  setTimeout(async () => {
21500
22597
  try {
21501
22598
  const r3 = await api("/messages");
@@ -21504,7 +22601,7 @@ function ChatPanel() {
21504
22601
  }
21505
22602
  }, 200);
21506
22603
  } catch (err) {
21507
- setError(`/clear failed: ${err.message}`);
22604
+ setError(t4("chat.clearFailed", { error: err.message }));
21508
22605
  }
21509
22606
  }, []);
21510
22607
  const updatePopover = x2(
@@ -21602,7 +22699,7 @@ function ChatPanel() {
21602
22699
  [send, popoverKind, popoverItems, applyPopover]
21603
22700
  );
21604
22701
  if (bootError) {
21605
- return html4`<div class="notice err">chat unavailable: ${bootError}</div>`;
22702
+ return html4`<div class="notice err">${t4("common.loadingFailed", { name: "chat", error: bootError })}</div>`;
21606
22703
  }
21607
22704
  const autoScrollInFlight = F2(false);
21608
22705
  _2(() => {
@@ -21664,10 +22761,10 @@ function ChatPanel() {
21664
22761
  }
21665
22762
  };
21666
22763
  tick();
21667
- const t4 = setInterval(tick, 2500);
22764
+ const t5 = setInterval(tick, 2500);
21668
22765
  return () => {
21669
22766
  cancelled = true;
21670
- clearInterval(t4);
22767
+ clearInterval(t5);
21671
22768
  };
21672
22769
  }, []);
21673
22770
  const setEditMode = x2(async (next) => {
@@ -21702,25 +22799,25 @@ function ChatPanel() {
21702
22799
  <div class="chat-shell">
21703
22800
  <div style="display:flex;align-items:center;gap:12px;margin-bottom:10px">
21704
22801
  <div class="chips" style="padding:0">
21705
- <span class="chip-f active">${MODE === "attached" ? "TUI mirror" : "session view"}</span>
22802
+ <span class="chip-f active">${MODE === "attached" ? t4("chat.modeMirror") : t4("chat.modeView")}</span>
21706
22803
  </div>
21707
22804
  <div class="header-pickers" style="margin-left:auto">
21708
22805
  ${effort ? html4`
21709
- <div class="mode-picker" title="reasoning_effort — applies next turn">
22806
+ <div class="mode-picker" title=${t4("chat.effortTitle")}>
21710
22807
  ${["high", "max"].map(
21711
22808
  (e3) => html4`
21712
22809
  <button
21713
22810
  key=${e3}
21714
22811
  class="mode-btn ${effort === e3 ? "active accent" : ""}"
21715
22812
  onClick=${() => setSetting("reasoningEffort", e3)}
21716
- title=${e3 === "max" ? "max (default \u2014 best quality)" : "high (cheaper / faster)"}
22813
+ title=${e3 === "max" ? t4("chat.effortMaxTitle") : t4("chat.effortHighTitle")}
21717
22814
  >${e3}</button>
21718
22815
  `
21719
22816
  )}
21720
22817
  </div>
21721
22818
  ` : null}
21722
22819
  ${preset ? html4`
21723
- <div class="mode-picker" title="preset — model commitment">
22820
+ <div class="mode-picker" title=${t4("chat.presetTitle")}>
21724
22821
  ${(() => {
21725
22822
  const KNOWN = ["auto", "flash", "pro"];
21726
22823
  const canonical = KNOWN.includes(preset) ? preset : "auto";
@@ -21730,7 +22827,7 @@ function ChatPanel() {
21730
22827
  key=${p3}
21731
22828
  class="mode-btn ${canonical === p3 ? "active accent" : ""}"
21732
22829
  onClick=${() => setSetting("preset", p3)}
21733
- title=${p3 === "auto" ? "auto \u2014 flash baseline; auto-escalates to pro on hard turns (NEEDS_PRO / failure threshold)" : p3 === "flash" ? "flash \u2014 always flash; no auto-escalate. /pro still works for one-shot manual" : "pro \u2014 always pro; ~3\xD7 flash cost (5/31 discount). Locks in on hard architecture work."}
22830
+ title=${p3 === "auto" ? t4("chat.presetAutoTitle") : p3 === "flash" ? t4("chat.presetFlashTitle") : t4("chat.presetProTitle")}
21734
22831
  >${p3}</button>
21735
22832
  `
21736
22833
  );
@@ -21738,14 +22835,14 @@ function ChatPanel() {
21738
22835
  </div>
21739
22836
  ` : null}
21740
22837
  ${editMode ? html4`
21741
- <div class="mode-picker" title="edit gate — Shift+Tab cycles in TUI">
22838
+ <div class="mode-picker" title=${t4("chat.editGateTitle")}>
21742
22839
  ${["review", "auto", "yolo"].map(
21743
22840
  (m2) => html4`
21744
22841
  <button
21745
22842
  key=${m2}
21746
22843
  class="mode-btn ${editMode === m2 ? "active" : ""} ${m2 === "yolo" ? "yolo" : ""}"
21747
22844
  onClick=${() => setEditMode(m2)}
21748
- title=${m2 === "review" ? "review \u2014 both edits and non-allowlisted shell ask first" : m2 === "auto" ? "auto \u2014 edits auto-apply, shell still asks" : "yolo \u2014 edits AND shell auto-run, allowlist bypassed"}
22845
+ title=${m2 === "review" ? t4("chat.editReviewTitle") : m2 === "auto" ? t4("chat.editAutoTitle") : t4("chat.editYoloTitle")}
21749
22846
  >${m2}</button>
21750
22847
  `
21751
22848
  )}
@@ -21758,19 +22855,19 @@ function ChatPanel() {
21758
22855
  ${semanticIndex === false && !semanticBannerDismissed ? html4`<div class="chat-banner">
21759
22856
  <span class="chat-banner-icon">≈</span>
21760
22857
  <span class="chat-banner-text">
21761
- <strong>Semantic search isn't enabled for this project.</strong>
22858
+ <strong>${t4("chat.semanticBanner")}</strong>
21762
22859
  <span class="muted">
21763
- Build the index once and the model can find code by meaning ("where do we handle auth failures?") instead of grep on exact strings.
22860
+ ${t4("chat.semanticBannerDesc")}
21764
22861
  </span>
21765
22862
  </span>
21766
22863
  <button
21767
22864
  class="primary"
21768
22865
  onClick=${() => appBus.dispatchEvent(new CustomEvent("navigate-tab", { detail: { tabId: "semantic" } }))}
21769
- >Build it →</button>
22866
+ >${t4("chat.semanticBannerBtn")}</button>
21770
22867
  <button
21771
22868
  class="chat-banner-close"
21772
22869
  onClick=${() => setSemanticBannerDismissed(true)}
21773
- title="dismiss (don't show again)"
22870
+ title=${t4("chat.semanticBannerDismiss")}
21774
22871
  >×</button>
21775
22872
  </div>` : null}
21776
22873
  ${error ? html4`<div class="notice err">${error}</div>` : null}
@@ -21781,7 +22878,7 @@ function ChatPanel() {
21781
22878
  <div class="chat-main">
21782
22879
  <div class="chat-feed" ref=${feedRef}>
21783
22880
  ${allMessages.length === 0 ? html4`<div class="chat-empty">
21784
- No conversation yet. Send a prompt below to begin.
22881
+ ${t4("chat.noConversation")}
21785
22882
  </div>` : allMessages.map(
21786
22883
  (m2) => html4`
21787
22884
  <${ChatMessage}
@@ -21796,7 +22893,7 @@ function ChatPanel() {
21796
22893
  <div class="chat-input-area" style="position:relative">
21797
22894
  ${popoverKind && popoverItems.length > 0 ? html4`
21798
22895
  <div class="popover" style="position:absolute;bottom:calc(100% + 6px);left:0;width:380px;max-height:280px;overflow-y:auto;z-index:10">
21799
- <div class="popover-h">${popoverKind === "slash" ? "slash commands" : "project files"}</div>
22896
+ <div class="popover-h">${popoverKind === "slash" ? t4("chat.slashCommands") : t4("chat.projectFiles")}</div>
21800
22897
  ${popoverItems.map(
21801
22898
  (it, i3) => html4`
21802
22899
  <div
@@ -21816,7 +22913,7 @@ function ChatPanel() {
21816
22913
  </div>
21817
22914
  ` : null}
21818
22915
  <textarea
21819
- placeholder=${busy ? "wait for the current turn to finish\u2026" : "Type a prompt \u2014 Enter sends, Shift+Enter for a newline \xB7 / @ for pickers"}
22916
+ placeholder=${busy ? t4("chat.placeholderBusy") : t4("chat.placeholder")}
21820
22917
  value=${input}
21821
22918
  onInput=${onInput}
21822
22919
  onKeyDown=${onKeyDown}
@@ -21829,10 +22926,10 @@ function ChatPanel() {
21829
22926
  class="primary"
21830
22927
  onClick=${send}
21831
22928
  disabled=${busy || !input.trim()}
21832
- >Send</button>
22929
+ >${t4("chat.send")}</button>
21833
22930
  <div style="display: flex; gap: 6px;">
21834
- <button onClick=${newConversation} title="/new — wipe conversation context (loop log + scrollback)">New</button>
21835
- <button onClick=${clearScrollback} title="/clear — wipe just visible scrollback (context kept)">Clear</button>
22931
+ <button onClick=${newConversation} title=${t4("chat.newTitle")}>${t4("chat.new")}</button>
22932
+ <button onClick=${clearScrollback} title=${t4("chat.clearTitle")}>${t4("chat.clear")}</button>
21836
22933
  </div>
21837
22934
  </div>
21838
22935
  </div>
@@ -21854,6 +22951,7 @@ function ChatPanel() {
21854
22951
  `;
21855
22952
  }
21856
22953
  function SideRail({ stats, budgetUsd, activePlan }) {
22954
+ useLang();
21857
22955
  if (!stats && !activePlan) return html4`<aside class="chat-rail"></aside>`;
21858
22956
  const cachePct = stats ? Math.round(stats.cacheHitRatio * 100) : 0;
21859
22957
  const cacheTone = cachePct >= 80 ? "ok" : cachePct >= 50 ? "" : "warn";
@@ -21865,12 +22963,12 @@ function SideRail({ stats, budgetUsd, activePlan }) {
21865
22963
  ${activePlan ? html4`<${ActivePlanCard} plan=${activePlan} />` : null}
21866
22964
  ${stats ? html4`
21867
22965
  <div class="rail-card">
21868
- <div class="rh">Session</div>
21869
- <div class="rail-kv"><span class="k">turns</span><span class="v">${stats.turns.toLocaleString()}</span></div>
21870
- <div class="rail-kv"><span class="k">prompt tok</span><span class="v">${stats.lastPromptTokens.toLocaleString()}</span></div>
21871
- <div class="rail-kv"><span class="k">cost</span><span class="v">${fmtUsd(stats.totalCostUsd)}</span></div>
22966
+ <div class="rh">${t4("chat.railSession")}</div>
22967
+ <div class="rail-kv"><span class="k">${t4("chat.railTurns")}</span><span class="v">${stats.turns.toLocaleString()}</span></div>
22968
+ <div class="rail-kv"><span class="k">${t4("chat.railPromptTok")}</span><span class="v">${stats.lastPromptTokens.toLocaleString()}</span></div>
22969
+ <div class="rail-kv"><span class="k">${t4("chat.railCost")}</span><span class="v">${fmtUsd(stats.totalCostUsd)}</span></div>
21872
22970
  <div class="progress-row" style="margin-top:8px">
21873
- <span class="lbl">cache hit</span>
22971
+ <span class="lbl">${t4("chat.railCacheHit")}</span>
21874
22972
  <div class=${`progress ${cacheTone}`}><div class="progress-fill" style=${`width:${cachePct}%`}></div></div>
21875
22973
  <span class="v">${cachePct}%</span>
21876
22974
  </div>
@@ -21878,9 +22976,9 @@ function SideRail({ stats, budgetUsd, activePlan }) {
21878
22976
  ` : null}
21879
22977
  ${showBudget ? html4`
21880
22978
  <div class="rail-card">
21881
- <div class="rh">Tool budget</div>
22979
+ <div class="rh">${t4("chat.railToolBudget")}</div>
21882
22980
  <div class="progress-row">
21883
- <span class="lbl">spend</span>
22981
+ <span class="lbl">${t4("chat.railSpend")}</span>
21884
22982
  <div class=${`progress ${budgetTone}`}><div class="progress-fill" style=${`width:${Math.min(100, budgetPct)}%`}></div></div>
21885
22983
  <span class="v" style=${budgetTone === "err" ? "color:var(--c-err)" : budgetTone === "warn" ? "color:var(--c-warn)" : ""}>${fmtUsd(stats.totalCostUsd)} / ${fmtUsd(budgetUsd)}</span>
21886
22984
  </div>
@@ -21890,6 +22988,7 @@ function SideRail({ stats, budgetUsd, activePlan }) {
21890
22988
  `;
21891
22989
  }
21892
22990
  function ActivePlanCard({ plan }) {
22991
+ useLang();
21893
22992
  const dots = [];
21894
22993
  for (let i3 = 0; i3 < plan.totalSteps; i3++) {
21895
22994
  const done = i3 < plan.completedSteps;
@@ -21903,10 +23002,10 @@ function ActivePlanCard({ plan }) {
21903
23002
  }
21904
23003
  return html4`
21905
23004
  <div class="rail-card">
21906
- <div class="rh">Active plan</div>
23005
+ <div class="rh">${t4("chat.railActivePlan")}</div>
21907
23006
  <div class="steps" style="margin-bottom:8px">${dots}</div>
21908
23007
  <div class="rail-kv"><span class="k" style="font-family:var(--font-sans);color:var(--fg-1);font-size:12.5px">${plan.title}</span></div>
21909
- <div class="rail-kv"><span class="k">progress</span><span class="v">${plan.completedSteps} / ${plan.totalSteps}</span></div>
23008
+ <div class="rail-kv"><span class="k">${t4("chat.railProgress")}</span><span class="v">${plan.completedSteps} / ${plan.totalSteps}</span></div>
21910
23009
  </div>
21911
23010
  `;
21912
23011
  }
@@ -21940,12 +23039,13 @@ function InFlightRow({
21940
23039
  onAbort,
21941
23040
  tick: _tick
21942
23041
  }) {
23042
+ useLang();
21943
23043
  const elapsedMs = startedAt ? Date.now() - startedAt : 0;
21944
23044
  const elapsed = (elapsedMs / 1e3).toFixed(1);
21945
23045
  const reasoningLen = streaming?.reasoning?.length ?? 0;
21946
23046
  const textLen = streaming?.text?.length ?? 0;
21947
23047
  const toolSummary = summarizeActiveTool(activeTool);
21948
- const phase = toolSummary ? "running" : reasoningLen > 0 && textLen === 0 ? "thinking" : textLen > 0 ? "streaming" : "waiting";
23048
+ const phase = toolSummary ? t4("chat.inflightRunning") : reasoningLen > 0 && textLen === 0 ? t4("chat.inflightThinking") : textLen > 0 ? t4("chat.inflightStreaming") : t4("chat.inflightWaiting");
21949
23049
  return html4`
21950
23050
  <div class="chat-inflight">
21951
23051
  <span class="spinner"></span>
@@ -21959,24 +23059,25 @@ function InFlightRow({
21959
23059
  ${!toolSummary && (textLen > 0 || reasoningLen > 0) ? html4`
21960
23060
  <span class="chat-inflight-sep">·</span>
21961
23061
  <span class="muted">
21962
- ${reasoningLen > 0 ? html4`reasoning ${reasoningLen.toLocaleString()} ch` : null}
23062
+ ${reasoningLen > 0 ? html4`${t4("chat.inflightReasoning", { count: reasoningLen.toLocaleString() })}` : null}
21963
23063
  ${reasoningLen > 0 && textLen > 0 ? html4`<span> · </span>` : null}
21964
- ${textLen > 0 ? html4`out ${textLen.toLocaleString()} ch` : null}
23064
+ ${textLen > 0 ? html4`${t4("chat.inflightOut", { count: textLen.toLocaleString() })}` : null}
21965
23065
  </span>
21966
23066
  ` : null}
21967
23067
  ${statusLine ? html4`
21968
23068
  <span class="chat-inflight-sep">·</span>
21969
23069
  <span class="muted">${statusLine}</span>
21970
23070
  ` : null}
21971
- <button class="chat-inflight-abort" onClick=${onAbort}>Abort (Esc)</button>
23071
+ <button class="chat-inflight-abort" onClick=${onAbort}>${t4("chat.abortBtn")}</button>
21972
23072
  </div>
21973
23073
  `;
21974
23074
  }
21975
23075
  function ChatStatusBar({ stats, model }) {
23076
+ useLang();
21976
23077
  if (!stats) {
21977
23078
  return html4`
21978
23079
  <div class="chat-statusbar">
21979
- <span class="muted">· · · waiting for live stats</span>
23080
+ <span class="muted">${t4("chat.waitingStats")}</span>
21980
23081
  </div>
21981
23082
  `;
21982
23083
  }
@@ -21985,36 +23086,36 @@ function ChatStatusBar({ stats, model }) {
21985
23086
  return html4`
21986
23087
  <div class="chat-statusbar">
21987
23088
  <span class="status-item">
21988
- <span class="status-label">model</span>
23089
+ <span class="status-label">${t4("chat.statusModel")}</span>
21989
23090
  <code>${model ?? "\u2014"}</code>
21990
23091
  </span>
21991
23092
  <span class="status-item">
21992
- <span class="status-label">ctx</span>
23093
+ <span class="status-label">${t4("chat.statusCtx")}</span>
21993
23094
  <span class="status-bar-mini">
21994
23095
  <span class="status-bar-mini-fill" style=${`width: ${Math.min(100, ctxPct).toFixed(1)}%;`}></span>
21995
23096
  </span>
21996
23097
  <span class="muted">${stats.lastPromptTokens.toLocaleString()} / ${(stats.contextCapTokens / 1e3).toFixed(0)}K</span>
21997
23098
  </span>
21998
23099
  <span class="status-item">
21999
- <span class="status-label">cache</span>
23100
+ <span class="status-label">${t4("chat.statusCache")}</span>
22000
23101
  <span class=${stats.cacheHitRatio >= 0.9 ? "status-ok" : stats.cacheHitRatio >= 0.6 ? "status-warn" : "status-err"}>
22001
23102
  ${(stats.cacheHitRatio * 100).toFixed(1)}%
22002
23103
  </span>
22003
23104
  </span>
22004
23105
  <span class="status-item">
22005
- <span class="status-label">turn</span>
23106
+ <span class="status-label">${t4("chat.statusTurn")}</span>
22006
23107
  <code>${fmtUsd(stats.lastTurnCostUsd)}</code>
22007
23108
  </span>
22008
23109
  <span class="status-item">
22009
- <span class="status-label">session</span>
23110
+ <span class="status-label">${t4("chat.statusSession")}</span>
22010
23111
  <code>${fmtUsd(stats.totalCostUsd)}</code>
22011
23112
  <span class="muted" style="font-size: 10px;">
22012
- (${stats.turns} turn${stats.turns === 1 ? "" : "s"})
23113
+ ${t4("chat.statusTurns", { count: stats.turns, s: stats.turns === 1 ? "" : "s" })}
22013
23114
  </span>
22014
23115
  </span>
22015
23116
  ${balance ? html4`
22016
23117
  <span class="status-item">
22017
- <span class="status-label">balance</span>
23118
+ <span class="status-label">${t4("chat.statusBalance")}</span>
22018
23119
  <code>${balance.total_balance} ${balance.currency}</code>
22019
23120
  </span>
22020
23121
  ` : null}
@@ -22043,6 +23144,7 @@ function buildMatrix(data) {
22043
23144
  return [...rows.values()];
22044
23145
  }
22045
23146
  function HooksPanel() {
23147
+ useLang();
22046
23148
  const [data, setData] = p2(null);
22047
23149
  const [error, setError] = p2(null);
22048
23150
  const [drafts, setDrafts] = p2({});
@@ -22078,7 +23180,7 @@ function HooksPanel() {
22078
23180
  try {
22079
23181
  await api("/hooks/save", { method: "POST", body: { scope, hooks: parsed } });
22080
23182
  await api("/hooks/reload", { method: "POST", body: {} });
22081
- setInfo(`saved + reloaded ${scope}`);
23183
+ setInfo(t4("hooks.savedReloaded", { scope }));
22082
23184
  setTimeout(() => setInfo(null), 3e3);
22083
23185
  await load();
22084
23186
  } catch (err) {
@@ -22090,7 +23192,7 @@ function HooksPanel() {
22090
23192
  [drafts, load]
22091
23193
  );
22092
23194
  if (!data && !error)
22093
- return html4`<div class="card" style="color:var(--fg-3)">loading hooks…</div>`;
23195
+ return html4`<div class="card" style="color:var(--fg-3)">${t4("hooks.loading")}</div>`;
22094
23196
  if (error && !data) return html4`<div class="card accent-err">${error}</div>`;
22095
23197
  if (!data) return null;
22096
23198
  const sectionH3 = (text, sub) => html4`
@@ -22104,20 +23206,19 @@ function HooksPanel() {
22104
23206
  return html4`
22105
23207
  <div style="display:flex;flex-direction:column;gap:6px">
22106
23208
  <div class="chips">
22107
- <span class="chip-f active">resolved <span class="ct">${data.resolved.length}</span></span>
23209
+ <span class="chip-f active">${t4("hooks.resolved")} <span class="ct">${data.resolved.length}</span></span>
22108
23210
  ${data.events.map((ev) => html4`<span class="chip-f">${ev}</span>`)}
22109
23211
  </div>
22110
23212
  ${info ? html4`<div><span class="pill ok">${info}</span></div>` : null}
22111
23213
  ${error ? html4`<div class="card accent-err">${error}</div>` : null}
22112
23214
 
22113
- ${sectionH3("Event matrix", `${matrixRows.length} script${matrixRows.length === 1 ? "" : "s"} \xD7 ${events.length} event${events.length === 1 ? "" : "s"}`)}
22114
- ${matrixRows.length === 0 ? html4`<div class="card" style="color:var(--fg-3)">
22115
- No hooks configured. Edit the JSON below to add some.
23215
+ ${sectionH3(t4("hooks.eventMatrix"), t4("hooks.matrixSub", { scripts: matrixRows.length, s: matrixRows.length === 1 ? "" : "s", events: events.length }))}${matrixRows.length === 0 ? html4`<div class="card" style="color:var(--fg-3)">
23216
+ ${t4("hooks.noHooks")}
22116
23217
  </div>` : html4`
22117
23218
  <div class="card" style="padding:10px 14px;overflow-x:auto">
22118
23219
  <div class="matrix" style=${`min-width:fit-content`}>
22119
23220
  <div class="row h" style=${`grid-template-columns:${gridCols}`}>
22120
- <div>script</div>
23221
+ <div>${t4("hooks.colScript")}</div>
22121
23222
  ${events.map((ev) => html4`<div>${ev}</div>`)}
22122
23223
  </div>
22123
23224
  ${matrixRows.map(
@@ -22148,8 +23249,7 @@ function HooksPanel() {
22148
23249
  return html4`
22149
23250
  ${sectionH3(scope, meta.path ?? "(no path)")}
22150
23251
  ${scope === "project" && !meta.path ? html4`<div class="card" style="color:var(--fg-3)">
22151
- No active project — open <code class="mono">/dashboard</code> from
22152
- <code class="mono">reasonix code</code> to edit project hooks.
23252
+ ${t4("hooks.noProject")}
22153
23253
  </div>` : html4`
22154
23254
  <div class="card">
22155
23255
  <textarea
@@ -22160,27 +23260,27 @@ function HooksPanel() {
22160
23260
  ></textarea>
22161
23261
  <div style="display:flex;gap:6px;margin-top:8px">
22162
23262
  <button class="btn primary" disabled=${busy} onClick=${() => saveScope(scope)}>
22163
- Save + Reload
23263
+ ${t4("hooks.saveReload")}
22164
23264
  </button>
22165
- <button class="btn ghost" disabled=${busy} onClick=${load}>Discard changes</button>
23265
+ <button class="btn ghost" disabled=${busy} onClick=${load}>${t4("hooks.discard")}</button>
22166
23266
  </div>
22167
23267
  </div>
22168
23268
  `}
22169
23269
  `;
22170
23270
  })}
22171
23271
 
22172
- ${sectionH3("Recent runs", `${data.recentRuns?.length ?? 0}`)}
23272
+ ${sectionH3(t4("hooks.recentRuns"), `${data.recentRuns?.length ?? 0}`)}
22173
23273
  ${!data.recentRuns || data.recentRuns.length === 0 ? html4`<div class="card" style="color:var(--fg-3)">
22174
- No hook runs in the recent session log.
23274
+ ${t4("hooks.noRuns")}
22175
23275
  </div>` : html4`
22176
23276
  <div class="card" style="padding:0;overflow-x:auto">
22177
23277
  <table class="tbl" style="width:100%;font-family:var(--font-mono);font-size:11.5px">
22178
23278
  <thead>
22179
23279
  <tr>
22180
- <th style="text-align:left;padding:8px 12px">when</th>
22181
- <th style="text-align:left;padding:8px 12px">phase</th>
22182
- <th style="text-align:left;padding:8px 12px">hook</th>
22183
- <th style="text-align:left;padding:8px 12px">outcome</th>
23280
+ <th style="text-align:left;padding:8px 12px">${t4("hooks.colWhen")}</th>
23281
+ <th style="text-align:left;padding:8px 12px">${t4("hooks.colPhase")}</th>
23282
+ <th style="text-align:left;padding:8px 12px">${t4("hooks.colHook")}</th>
23283
+ <th style="text-align:left;padding:8px 12px">${t4("hooks.colOutcome")}</th>
22184
23284
  </tr>
22185
23285
  </thead>
22186
23286
  <tbody>
@@ -22214,6 +23314,7 @@ function specCommand(spec) {
22214
23314
  return eq > 0 ? spec.slice(eq + 1) : spec;
22215
23315
  }
22216
23316
  function McpPanel() {
23317
+ useLang();
22217
23318
  const [data, setData] = p2(null);
22218
23319
  const [specs, setSpecs] = p2(null);
22219
23320
  const [error, setError] = p2(null);
@@ -22243,7 +23344,7 @@ function McpPanel() {
22243
23344
  body: { spec: newSpec.trim() }
22244
23345
  });
22245
23346
  setInfo(
22246
- r3.requiresRestart ? "saved \u2014 restart `reasonix code` to bridge this server" : "saved"
23347
+ r3.requiresRestart ? t4("mcp.savedRestart") : t4("mcp.saved")
22247
23348
  );
22248
23349
  setTimeout(() => setInfo(null), 4e3);
22249
23350
  setNewSpec("");
@@ -22256,13 +23357,11 @@ function McpPanel() {
22256
23357
  }, [newSpec, load]);
22257
23358
  const removeSpec = x2(
22258
23359
  async (spec) => {
22259
- if (!confirm(`Remove MCP spec from config?
22260
-
22261
- ${spec}`)) return;
23360
+ if (!confirm(t4("mcp.removeConfirm", { spec }))) return;
22262
23361
  setBusy(true);
22263
23362
  try {
22264
23363
  await api("/mcp/specs", { method: "DELETE", body: { spec } });
22265
- setInfo("removed \u2014 restart to drop the live bridge");
23364
+ setInfo(t4("mcp.removed"));
22266
23365
  setTimeout(() => setInfo(null), 4e3);
22267
23366
  await load();
22268
23367
  } catch (err) {
@@ -22273,7 +23372,7 @@ ${spec}`)) return;
22273
23372
  },
22274
23373
  [load]
22275
23374
  );
22276
- if (!data && !error) return html4`<div class="card" style="color:var(--fg-3)">loading MCP…</div>`;
23375
+ if (!data && !error) return html4`<div class="card" style="color:var(--fg-3)">${t4("mcp.loading")}</div>`;
22277
23376
  if (error && !data) return html4`<div class="card accent-err">${error}</div>`;
22278
23377
  if (!data) return null;
22279
23378
  const liveCount = data.servers.length;
@@ -22285,19 +23384,19 @@ ${spec}`)) return;
22285
23384
  <div class="sessions-grid">
22286
23385
  <div class="sessions-list">
22287
23386
  <div class="ssl-h" style="font-family:var(--font-mono);font-size:11px;color:var(--fg-3);text-transform:uppercase;letter-spacing:.1em">
22288
- MCP servers · ${liveCount} bridged
23387
+ ${t4("mcp.servers", { count: liveCount })}
22289
23388
  </div>
22290
23389
  <div style="padding:8px 12px 4px">
22291
23390
  <div class="chips">
22292
- <span class=${`chip-f ${filter === "all" ? "active" : ""}`} onClick=${() => setFilter("all")}>all <span class="ct">${liveCount + unbridgedCount}</span></span>
22293
- <span class=${`chip-f ${filter === "live" ? "active" : ""}`} onClick=${() => setFilter("live")}>live <span class="ct">${liveCount}</span></span>
22294
- <span class=${`chip-f ${filter === "unbridged" ? "active" : ""}`} onClick=${() => setFilter("unbridged")}>unbridged <span class="ct">${unbridgedCount}</span></span>
23391
+ <span class=${`chip-f ${filter === "all" ? "active" : ""}`} onClick=${() => setFilter("all")}>${t4("mcp.all")} <span class="ct">${liveCount + unbridgedCount}</span></span>
23392
+ <span class=${`chip-f ${filter === "live" ? "active" : ""}`} onClick=${() => setFilter("live")}>${t4("mcp.live")} <span class="ct">${liveCount}</span></span>
23393
+ <span class=${`chip-f ${filter === "unbridged" ? "active" : ""}`} onClick=${() => setFilter("unbridged")}>${t4("mcp.unbridged")} <span class="ct">${unbridgedCount}</span></span>
22295
23394
  </div>
22296
23395
  </div>
22297
23396
  <div style="padding:8px 12px;display:flex;gap:6px">
22298
23397
  <input
22299
23398
  type="text"
22300
- placeholder='spec — e.g. fs=npx -y @modelcontextprotocol/...'
23399
+ placeholder=${t4("mcp.specPlaceholder")}
22301
23400
  value=${newSpec}
22302
23401
  onInput=${(e3) => setNewSpec(e3.target.value)}
22303
23402
  style="flex:1;font-size:11px"
@@ -22309,7 +23408,7 @@ ${spec}`)) return;
22309
23408
 
22310
23409
  <div class="ssl-rows">
22311
23410
  ${liveCount === 0 && unbridgedCount === 0 ? html4`<div style="color:var(--fg-3);padding:14px;font-size:12px">
22312
- No MCP servers in this session.
23411
+ ${t4("mcp.noServers")}
22313
23412
  </div>` : null}
22314
23413
  ${showLive ? data.servers.map(
22315
23414
  (s3) => html4`
@@ -22320,9 +23419,9 @@ ${spec}`)) return;
22320
23419
  setOpenUnbridged(null);
22321
23420
  }}
22322
23421
  >
22323
- <span class="name">${s3.label} <span class="pill ok">live</span></span>
23422
+ <span class="name">${s3.label} <span class="pill ok">${t4("mcp.live")}</span></span>
22324
23423
  <span class="preview">${specCommand(s3.spec)}</span>
22325
- <span class="meta"><span><span class="v">${fmtNum(s3.toolCount)}</span> tools</span></span>
23424
+ <span class="meta"><span><span class="v">${fmtNum(s3.toolCount)}</span> ${t4("mcp.tools")}</span></span>
22326
23425
  </div>
22327
23426
  `
22328
23427
  ) : null}
@@ -22335,9 +23434,9 @@ ${spec}`)) return;
22335
23434
  setOpen(null);
22336
23435
  }}
22337
23436
  >
22338
- <span class="name">${specLabel(spec)} <span class="pill">unbridged</span></span>
23437
+ <span class="name">${specLabel(spec)} <span class="pill">${t4("mcp.unbridged")}</span></span>
22339
23438
  <span class="preview">${specCommand(spec)}</span>
22340
- <span class="meta"><span class="dim">in config · not loaded</span></span>
23439
+ <span class="meta"><span class="dim">${t4("mcp.inConfig")}</span></span>
22341
23440
  </div>
22342
23441
  `
22343
23442
  ) : null}
@@ -22348,41 +23447,39 @@ ${spec}`)) return;
22348
23447
  ${openUnbridged != null ? html4`
22349
23448
  <div class="sessions-detail-h">
22350
23449
  <span class="name">${specLabel(openUnbridged)}</span>
22351
- <span class="ws"><span class="pill">unbridged · in config</span></span>
23450
+ <span class="ws"><span class="pill">${t4("mcp.unbridgedTitle")}</span></span>
22352
23451
  <span class="actions">
22353
23452
  <button class="btn" disabled=${busy} onClick=${() => removeSpec(openUnbridged)}
22354
- style="border-color:var(--c-err);color:var(--c-err)">Remove</button>
22355
- <button class="btn ghost" onClick=${() => setOpenUnbridged(null)}>← back</button>
23453
+ style="border-color:var(--c-err);color:var(--c-err)">${t4("mcp.removeBtn")}</button>
23454
+ <button class="btn ghost" onClick=${() => setOpenUnbridged(null)}>${t4("common.back")}</button>
22356
23455
  </span>
22357
23456
  </div>
22358
23457
  <div class="card" style="margin-bottom:12px">
22359
- <div class="card-h"><span class="title">spec</span></div>
23458
+ <div class="card-h"><span class="title">${t4("mcp.spec")}</span></div>
22360
23459
  <code class="mono" style="font-size:11.5px;color:var(--fg-2);word-break:break-all">${openUnbridged}</code>
22361
23460
  </div>
22362
23461
  <div class="card accent-warn">
22363
- <div class="card-h"><span class="title" style="color:var(--c-warn)">Why unbridged?</span></div>
23462
+ <div class="card-h"><span class="title" style="color:var(--c-warn)">${t4("mcp.whyUnbridged")}</span></div>
22364
23463
  <div class="card-b" style="font-size:13px;line-height:1.6">
22365
- This spec lives in your <code class="mono">config.json</code> but isn't bridged into the live session.
22366
- MCP servers attach when <code class="mono">reasonix code</code> starts; the dashboard alone can't
22367
- spawn the child process.
23464
+ ${t4("mcp.whyUnbridgedDesc")}
22368
23465
  <div style="margin-top:10px;color:var(--fg-3);font-size:12px">
22369
- To activate: restart <code class="mono">reasonix code</code>, then refresh this dashboard.
23466
+ ${t4("mcp.whyUnbridgedHint")}
22370
23467
  </div>
22371
23468
  </div>
22372
23469
  </div>
22373
23470
  ` : open == null ? html4`<div style="color:var(--fg-3);font-size:13px;text-align:center;padding:60px 20px">
22374
- Pick an MCP server on the left to inspect tools / resources / prompts.
23471
+ ${t4("mcp.pickHint")}
22375
23472
  </div>` : html4`
22376
23473
  <div class="sessions-detail-h">
22377
23474
  <span class="name">${open.label}</span>
22378
23475
  <span class="ws">${open.serverInfo?.name ?? "\u2014"} ${open.serverInfo?.version ? `v${open.serverInfo.version}` : ""} · ${open.protocolVersion ?? "\u2014"}</span>
22379
23476
  <span class="actions">
22380
- <button class="btn ghost" onClick=${() => setOpen(null)}>← back</button>
23477
+ <button class="btn ghost" onClick=${() => setOpen(null)}>${t4("common.back")}</button>
22381
23478
  </span>
22382
23479
  </div>
22383
23480
 
22384
23481
  <div class="card" style="margin-bottom:12px">
22385
- <div class="card-h"><span class="title">spec</span></div>
23482
+ <div class="card-h"><span class="title">${t4("mcp.spec")}</span></div>
22386
23483
  <code class="mono" style="font-size:11.5px;color:var(--fg-2)">${open.spec}</code>
22387
23484
  </div>
22388
23485
 
@@ -22391,14 +23488,14 @@ ${spec}`)) return;
22391
23488
  </div>` : null}
22392
23489
 
22393
23490
  <h3 style="margin:18px 0 6px;font-family:var(--font-mono);font-size:11px;color:var(--fg-3);text-transform:uppercase;letter-spacing:.1em">
22394
- Tools · ${open.tools.length}
23491
+ ${t4("mcp.toolsTitle", { count: open.tools.length })}
22395
23492
  </h3>
22396
23493
  <div class="card" style="padding:0;overflow:hidden">
22397
23494
  <table class="tbl">
22398
- <thead><tr><th>name</th><th>description</th></tr></thead>
23495
+ <thead><tr><th>${t4("mcp.colName")}</th><th>${t4("mcp.colDesc")}</th></tr></thead>
22399
23496
  <tbody>
22400
23497
  ${open.tools.map(
22401
- (t4) => html4`<tr><td><code class="mono">${t4.name}</code></td><td class="dim">${t4.description ?? ""}</td></tr>`
23498
+ (tool) => html4`<tr><td><code class="mono">${tool.name}</code></td><td class="dim">${tool.description ?? ""}</td></tr>`
22402
23499
  )}
22403
23500
  </tbody>
22404
23501
  </table>
@@ -22406,11 +23503,11 @@ ${spec}`)) return;
22406
23503
 
22407
23504
  ${open.resources.length > 0 ? html4`
22408
23505
  <h3 style="margin:18px 0 6px;font-family:var(--font-mono);font-size:11px;color:var(--fg-3);text-transform:uppercase;letter-spacing:.1em">
22409
- Resources · ${open.resources.length}
23506
+ ${t4("mcp.resourcesTitle", { count: open.resources.length })}
22410
23507
  </h3>
22411
23508
  <div class="card" style="padding:0;overflow:hidden">
22412
23509
  <table class="tbl">
22413
- <thead><tr><th>name</th><th>uri</th></tr></thead>
23510
+ <thead><tr><th>${t4("mcp.colName")}</th><th>${t4("mcp.colUri")}</th></tr></thead>
22414
23511
  <tbody>
22415
23512
  ${open.resources.map(
22416
23513
  (r3) => html4`<tr><td>${r3.name}</td><td class="path">${r3.uri}</td></tr>`
@@ -22422,11 +23519,11 @@ ${spec}`)) return;
22422
23519
 
22423
23520
  ${open.prompts.length > 0 ? html4`
22424
23521
  <h3 style="margin:18px 0 6px;font-family:var(--font-mono);font-size:11px;color:var(--fg-3);text-transform:uppercase;letter-spacing:.1em">
22425
- Prompts · ${open.prompts.length}
23522
+ ${t4("mcp.promptsTitle", { count: open.prompts.length })}
22426
23523
  </h3>
22427
23524
  <div class="card" style="padding:0;overflow:hidden">
22428
23525
  <table class="tbl">
22429
- <thead><tr><th>name</th><th>description</th></tr></thead>
23526
+ <thead><tr><th>${t4("mcp.colName")}</th><th>${t4("mcp.colDesc")}</th></tr></thead>
22430
23527
  <tbody>
22431
23528
  ${open.prompts.map(
22432
23529
  (p3) => html4`<tr><td><code class="mono">${p3.name}</code></td><td class="dim">${p3.description ?? ""}</td></tr>`
@@ -22443,6 +23540,7 @@ ${spec}`)) return;
22443
23540
 
22444
23541
  // dashboard/src/panels/memory.ts
22445
23542
  function MemoryPanel() {
23543
+ useLang();
22446
23544
  const [tree, setTree] = p2(null);
22447
23545
  const [error, setError] = p2(null);
22448
23546
  const [open, setOpen] = p2(null);
@@ -22479,7 +23577,7 @@ function MemoryPanel() {
22479
23577
  try {
22480
23578
  const path = open.scope === "project" ? "/memory/project" : `/memory/${open.scope}/${encodeURIComponent(open.name ?? "")}`;
22481
23579
  await api(path, { method: "POST", body: { body } });
22482
- setInfo(`saved ${open.scope}${open.name ? `/${open.name}` : ""}`);
23580
+ setInfo(t4("memory.saved", { scope: open.scope + (open.name ? `/${open.name}` : "") }));
22483
23581
  setTimeout(() => setInfo(null), 3e3);
22484
23582
  await load();
22485
23583
  } catch (err) {
@@ -22489,7 +23587,7 @@ function MemoryPanel() {
22489
23587
  }
22490
23588
  }, [open, body, load]);
22491
23589
  if (!tree && !error)
22492
- return html4`<div class="card" style="color:var(--fg-3)">loading memory…</div>`;
23590
+ return html4`<div class="card" style="color:var(--fg-3)">${t4("memory.loading")}</div>`;
22493
23591
  if (error && !tree) return html4`<div class="card accent-err">${error}</div>`;
22494
23592
  if (!tree) return null;
22495
23593
  const fileRow = (scope, f3) => {
@@ -22508,11 +23606,12 @@ function MemoryPanel() {
22508
23606
  </div>
22509
23607
  `;
22510
23608
  };
23609
+ const totalFiles = (tree.project.path ? 1 : 0) + tree.global.files.length + tree.projectMem.files.length;
22511
23610
  return html4`
22512
23611
  <div class="sessions-grid">
22513
23612
  <div class="sessions-list">
22514
23613
  <div class="ssl-h" style="font-family:var(--font-mono);font-size:11px;color:var(--fg-3);text-transform:uppercase;letter-spacing:.1em">
22515
- memory · ${(tree.project.path ? 1 : 0) + tree.global.files.length + tree.projectMem.files.length} files
23614
+ ${t4("memory.files", { count: totalFiles })}
22516
23615
  </div>
22517
23616
  <div class="ssl-rows">
22518
23617
  ${tree.project.path ? html4`
@@ -22522,7 +23621,7 @@ function MemoryPanel() {
22522
23621
  >
22523
23622
  <span class="name">
22524
23623
  REASONIX.md
22525
- ${tree.project.exists ? html4`<span class="pill ok">exists</span>` : html4`<span class="pill">create</span>`}
23624
+ ${tree.project.exists ? html4`<span class="pill ok">${t4("memory.exists")}</span>` : html4`<span class="pill">${t4("memory.create")}</span>`}
22526
23625
  </span>
22527
23626
  <span class="preview">${tree.project.path}</span>
22528
23627
  <span class="meta"><span class="dim">project</span></span>
@@ -22531,27 +23630,26 @@ function MemoryPanel() {
22531
23630
  ${tree.global.files.map((f3) => fileRow("global", f3))}
22532
23631
  ${tree.projectMem.files.map((f3) => fileRow("project-mem", f3))}
22533
23632
  ${tree.global.files.length === 0 && tree.projectMem.files.length === 0 && !tree.project.path ? html4`<div style="color:var(--fg-3);padding:14px;font-size:12px">
22534
- No memory files yet.
23633
+ ${t4("memory.noFiles")}
22535
23634
  </div>` : null}
22536
23635
  </div>
22537
23636
  </div>
22538
23637
 
22539
23638
  <div class="sessions-detail">
22540
23639
  ${open == null ? html4`<div style="color:var(--fg-3);font-size:13px;text-align:center;padding:60px 20px">
22541
- Pick a memory file on the left.
23640
+ ${t4("memory.pickHint")}
22542
23641
  <div style="margin-top:12px;font-size:11.5px">
22543
- Project REASONIX.md is committable; global notes live in
22544
- <code class="mono">~/.reasonix/memory/</code>.
23642
+ ${t4("memory.pickDesc")}
22545
23643
  </div>
22546
23644
  </div>` : html4`
22547
23645
  <div class="sessions-detail-h">
22548
23646
  <span class="name">
22549
23647
  ${open.scope}${open.name ? `/${open.name}` : ""}
22550
23648
  </span>
22551
- <span class="ws">${body.length.toLocaleString()} chars</span>
23649
+ <span class="ws">${t4("memory.chars", { count: body.length.toLocaleString() })}</span>
22552
23650
  <span class="actions">
22553
- <button class="btn primary" disabled=${busy} onClick=${save}>Save</button>
22554
- <button class="btn ghost" onClick=${() => setOpen(null)}>← back</button>
23651
+ <button class="btn primary" disabled=${busy} onClick=${save}>${t4("common.save")}</button>
23652
+ <button class="btn ghost" onClick=${() => setOpen(null)}>${t4("common.back")}</button>
22555
23653
  </span>
22556
23654
  </div>
22557
23655
  ${info ? html4`<div style="margin-bottom:8px"><span class="pill ok">${info}</span></div>` : null}
@@ -22563,7 +23661,7 @@ function MemoryPanel() {
22563
23661
  disabled=${busy}
22564
23662
  ></textarea>
22565
23663
  <div style="margin-top:8px;color:var(--fg-3);font-size:11.5px">
22566
- re-applied on next <code class="mono">/new</code> or session restart
23664
+ ${t4("memory.reloadHint")}
22567
23665
  </div>
22568
23666
  `}
22569
23667
  </div>
@@ -22617,59 +23715,59 @@ function kpi(label, value, delta, deltaTone) {
22617
23715
  `;
22618
23716
  }
22619
23717
  function deltaPctText(deltaPct) {
22620
- if (deltaPct === null) return { text: "no prior data", tone: "flat" };
22621
- if (Math.abs(deltaPct) < 1) return { text: "\u2014 stable", tone: "flat" };
23718
+ if (deltaPct === null) return { text: t4("overview.noPriorData"), tone: "flat" };
23719
+ if (Math.abs(deltaPct) < 1) return { text: t4("overview.stable"), tone: "flat" };
22622
23720
  const arrow = deltaPct > 0 ? "\u25B2" : "\u25BC";
22623
23721
  return {
22624
- text: `${arrow} ${Math.abs(deltaPct).toFixed(0)}% vs prior`,
23722
+ text: t4("overview.vsPrior", { arrow, pct: Math.abs(deltaPct).toFixed(0) }),
22625
23723
  tone: deltaPct > 0 ? "up" : "down"
22626
23724
  };
22627
23725
  }
22628
23726
  function deltaPpText(deltaPp) {
22629
- if (deltaPp === null) return { text: "no prior data", tone: "flat" };
22630
- if (Math.abs(deltaPp) < 0.5) return { text: "\u2014 stable", tone: "flat" };
23727
+ if (deltaPp === null) return { text: t4("overview.noPriorData"), tone: "flat" };
23728
+ if (Math.abs(deltaPp) < 0.5) return { text: t4("overview.stable"), tone: "flat" };
22631
23729
  const arrow = deltaPp > 0 ? "\u25B2" : "\u25BC";
22632
23730
  return { text: `${arrow} ${Math.abs(deltaPp).toFixed(1)}pp`, tone: deltaPp > 0 ? "up" : "down" };
22633
23731
  }
22634
23732
  function deltaCountText(delta) {
22635
- if (delta === null || delta === 0) return { text: "\u2014 stable", tone: "flat" };
23733
+ if (delta === null || delta === 0) return { text: t4("overview.stable"), tone: "flat" };
22636
23734
  const arrow = delta > 0 ? "\u25B2" : "\u25BC";
22637
23735
  return { text: `${arrow} ${Math.abs(delta)}`, tone: delta > 0 ? "up" : "down" };
22638
23736
  }
22639
23737
  function balanceKpi(c3) {
22640
- if (!c3.balance) return kpi("balance", "\u2014", "open in TUI", "flat");
23738
+ if (!c3.balance) return kpi(t4("overview.balance"), "\u2014", "open in TUI", "flat");
22641
23739
  const symbol = c3.balance.currency === "CNY" ? "\xA5" : c3.balance.currency === "USD" ? "$" : "";
22642
- return kpi("balance", `${symbol}${c3.balance.total}`, c3.balance.currency, "flat");
23740
+ return kpi(t4("overview.balance"), `${symbol}${c3.balance.total}`, c3.balance.currency, "flat");
22643
23741
  }
22644
23742
  function tokens7dKpi(c3) {
22645
- if (!c3.tokens7d) return kpi("tokens \xB7 7d", "\u2014", "no usage yet", "flat");
23743
+ if (!c3.tokens7d) return kpi(t4("overview.tokens7d"), "\u2014", t4("overview.noUsageYet"), "flat");
22646
23744
  const d3 = deltaPctText(c3.tokens7d.deltaPct);
22647
- return kpi("tokens \xB7 7d", fmtCompactNum(c3.tokens7d.total), d3.text, d3.tone);
23745
+ return kpi(t4("overview.tokens7d"), fmtCompactNum(c3.tokens7d.total), d3.text, d3.tone);
22648
23746
  }
22649
23747
  function cacheHitKpi(c3) {
22650
- if (!c3.cacheHit7d) return kpi("cache hit", "\u2014", "no usage yet", "flat");
23748
+ if (!c3.cacheHit7d) return kpi(t4("overview.cacheHit"), "\u2014", t4("overview.noUsageYet"), "flat");
22651
23749
  const pct = (c3.cacheHit7d.ratio * 100).toFixed(0);
22652
23750
  const d3 = deltaPpText(c3.cacheHit7d.deltaPp);
22653
23751
  return html4`
22654
23752
  <div class="kpi cock-w-1">
22655
- <div class="label">cache hit</div>
23753
+ <div class="label">${t4("overview.cacheHit")}</div>
22656
23754
  <div class="value">${pct}<span class="unit">%</span></div>
22657
23755
  <div class=${`delta ${d3.tone}`}>${d3.text}</div>
22658
23756
  </div>
22659
23757
  `;
22660
23758
  }
22661
23759
  function toolCallsKpi(c3) {
22662
- if (!c3.toolCalls24h) return kpi("tool calls \xB7 24h", "\u2014", "no events", "flat");
23760
+ if (!c3.toolCalls24h) return kpi(t4("overview.toolCalls24h"), "\u2014", t4("overview.noToolCalls"), "flat");
22663
23761
  const d3 = deltaCountText(c3.toolCalls24h.delta);
22664
- return kpi("tool calls \xB7 24h", fmtNum(c3.toolCalls24h.total), d3.text, d3.tone);
23762
+ return kpi(t4("overview.toolCalls24h"), fmtNum(c3.toolCalls24h.total), d3.text, d3.tone);
22665
23763
  }
22666
23764
  function currentSessionBlock(c3) {
22667
23765
  if (!c3.currentSession) {
22668
23766
  return html4`
22669
23767
  <div class="cock-list cock-w-2">
22670
- <div class="ch"><span class="ttl">current session</span></div>
23768
+ <div class="ch"><span class="ttl">${t4("overview.currentSession")}</span></div>
22671
23769
  <div style="color:var(--fg-3);font-size:12.5px;padding:8px 0">
22672
- No live session — <code class="mono">/dashboard</code> from inside <code class="mono">reasonix code</code> to attach.
23770
+ ${t4("overview.noSession")}
22673
23771
  </div>
22674
23772
  </div>
22675
23773
  `;
@@ -22677,14 +23775,14 @@ function currentSessionBlock(c3) {
22677
23775
  const s3 = c3.currentSession;
22678
23776
  return html4`
22679
23777
  <div class="cock-list cock-w-2">
22680
- <div class="ch"><span class="ttl">current session</span></div>
23778
+ <div class="ch"><span class="ttl">${t4("overview.currentSession")}</span></div>
22681
23779
  <div class="card accent-brand" style="margin:0 0 8px;background:transparent;border:none;padding:0">
22682
23780
  <div class="card-h"><span class="glyph">◆</span><span class="title">${s3.id}</span><span class="meta">${s3.turns} turn${s3.turns === 1 ? "" : "s"}</span></div>
22683
23781
  </div>
22684
23782
  <div style="display:grid;grid-template-columns:repeat(3, 1fr);gap:8px;font-family:var(--font-mono);font-size:11px">
22685
- <div><span style="color:var(--fg-3)">prompt tok</span><div style="color:var(--fg-0);font-size:13px;font-weight:600">${fmtNum(s3.lastPromptTokens)}</div></div>
22686
- <div><span style="color:var(--fg-3)">completion tok</span><div style="color:var(--fg-0);font-size:13px;font-weight:600">${fmtNum(s3.completionTokens)}</div></div>
22687
- <div><span style="color:var(--fg-3)">cost</span><div style="color:var(--fg-0);font-size:13px;font-weight:600">${fmtUsd(s3.totalCostUsd)}</div></div>
23783
+ <div><span style="color:var(--fg-3)">${t4("overview.promptTok")}</span><div style="color:var(--fg-0);font-size:13px;font-weight:600">${fmtNum(s3.lastPromptTokens)}</div></div>
23784
+ <div><span style="color:var(--fg-3)">${t4("overview.completionTok")}</span><div style="color:var(--fg-0);font-size:13px;font-weight:600">${fmtNum(s3.completionTokens)}</div></div>
23785
+ <div><span style="color:var(--fg-3)">${t4("overview.cost")}</span><div style="color:var(--fg-0);font-size:13px;font-weight:600">${fmtUsd(s3.totalCostUsd)}</div></div>
22688
23786
  </div>
22689
23787
  </div>
22690
23788
  `;
@@ -22693,8 +23791,8 @@ function costTrendSpark(c3) {
22693
23791
  if (!c3.costTrend14d || c3.costTrend14d.length === 0) {
22694
23792
  return html4`
22695
23793
  <div class="chart cock-w-2">
22696
- <div class="chart-h"><span class="title">cost · 14 day</span></div>
22697
- <div class="chart-v" style="color:var(--fg-4)">—<span class="unit">no usage yet</span></div>
23794
+ <div class="chart-h"><span class="title">${t4("overview.costTrend")}</span></div>
23795
+ <div class="chart-v" style="color:var(--fg-4)">—<span class="unit">${t4("overview.noUsageYet")}</span></div>
22698
23796
  </div>
22699
23797
  `;
22700
23798
  }
@@ -22712,8 +23810,8 @@ function costTrendSpark(c3) {
22712
23810
  const avg = total / days2.length;
22713
23811
  return html4`
22714
23812
  <div class="chart cock-w-2">
22715
- <div class="chart-h"><span class="title">cost · 14 day</span></div>
22716
- <div class="chart-v">${fmtUsd(avg)}<span class="unit">/day avg</span></div>
23813
+ <div class="chart-h"><span class="title">${t4("overview.costTrend")}</span></div>
23814
+ <div class="chart-v">${fmtUsd(avg)}<span class="unit">${t4("overview.dayAvg")}</span></div>
22717
23815
  <div class="chart-spark">
22718
23816
  <svg viewBox=${`0 0 ${w3} ${h3}`} preserveAspectRatio="none">
22719
23817
  <polyline fill="none" stroke="var(--c-brand)" stroke-width="1.5" points=${points2} />
@@ -22726,8 +23824,8 @@ function costTrendSpark(c3) {
22726
23824
  function recentPlansRail(c3) {
22727
23825
  return html4`
22728
23826
  <div class="cock-list cock-w-2">
22729
- <div class="ch"><span class="ttl">recent plans</span></div>
22730
- ${!c3.recentPlans || c3.recentPlans.length === 0 ? html4`<div style="color:var(--fg-3);font-size:12.5px;padding:8px 0">No plans yet — submit one with <code class="mono">submit_plan</code>.</div>` : c3.recentPlans.map(
23827
+ <div class="ch"><span class="ttl">${t4("overview.recentPlans")}</span></div>
23828
+ ${!c3.recentPlans || c3.recentPlans.length === 0 ? html4`<div style="color:var(--fg-3);font-size:12.5px;padding:8px 0">${t4("overview.noPlans")}</div>` : c3.recentPlans.map(
22731
23829
  (p3) => html4`
22732
23830
  <div class=${`rail-step ${p3.status === "done" ? "done" : "active"}`}>
22733
23831
  <span class="g">${p3.status === "done" ? "\u2713" : "\u23F5"}</span>
@@ -22742,8 +23840,8 @@ function recentPlansRail(c3) {
22742
23840
  function toolActivityFeed(c3) {
22743
23841
  return html4`
22744
23842
  <div class="cock-list cock-w-2">
22745
- <div class="ch"><span class="ttl">tool activity</span></div>
22746
- ${!c3.toolActivity || c3.toolActivity.length === 0 ? html4`<div style="color:var(--fg-3);font-size:12.5px;padding:8px 0">No tool calls yet.</div>` : c3.toolActivity.map(
23843
+ <div class="ch"><span class="ttl">${t4("overview.toolActivity")}</span></div>
23844
+ ${!c3.toolActivity || c3.toolActivity.length === 0 ? html4`<div style="color:var(--fg-3);font-size:12.5px;padding:8px 0">${t4("overview.noToolCalls")}</div>` : c3.toolActivity.map(
22747
23845
  (r3) => html4`
22748
23846
  <div class=${`feed-row ${r3.level}`}>
22749
23847
  <span class="g">${r3.level === "ok" ? "\u25CF" : r3.level === "warn" ? "\u25B2" : "\u2715"}</span>
@@ -22756,10 +23854,11 @@ function toolActivityFeed(c3) {
22756
23854
  `;
22757
23855
  }
22758
23856
  function OverviewPanel() {
23857
+ useLang();
22759
23858
  const { data, error, loading } = usePoll("/overview", 2500);
22760
23859
  if (loading && !data)
22761
- return html4`<div class="card" style="color:var(--fg-3)">loading overview…</div>`;
22762
- if (error) return html4`<div class="card accent-err">overview failed: ${error.message}</div>`;
23860
+ return html4`<div class="card" style="color:var(--fg-3)">${t4("overview.loading")}</div>`;
23861
+ if (error) return html4`<div class="card accent-err">${t4("overview.failed", { error: error.message })}</div>`;
22763
23862
  if (!data) return null;
22764
23863
  const o3 = data;
22765
23864
  const c3 = o3.cockpit ?? {
@@ -22773,22 +23872,21 @@ function OverviewPanel() {
22773
23872
  toolActivity: null
22774
23873
  };
22775
23874
  const upToDate = o3.latestVersion ? o3.latestVersion === o3.version : null;
22776
- const versionDelta = upToDate === null ? "checking" : upToDate ? "latest" : `latest: ${o3.latestVersion}`;
23875
+ const versionDelta = upToDate === null ? t4("overview.checking") : upToDate ? t4("overview.latest") : `latest: ${o3.latestVersion}`;
22777
23876
  const versionTone = upToDate === false ? "down" : "flat";
22778
23877
  return html4`
22779
23878
  <div style="display:flex;flex-direction:column;gap:14px">
22780
23879
  ${o3.mode === "standalone" ? html4`<div class="card accent-warn">
22781
23880
  <div class="card-h">
22782
- <span class="title" style="color:var(--c-warn)">Standalone mode</span>
23881
+ <span class="title" style="color:var(--c-warn)">${t4("overview.standaloneTitle")}</span>
22783
23882
  </div>
22784
23883
  <div class="card-b">
22785
- Read-only disk view. Start <code class="mono">/dashboard</code> from inside
22786
- <code class="mono">reasonix code</code> for live session state, MCP, and tools.
23884
+ ${t4("overview.standaloneDesc")}
22787
23885
  </div>
22788
23886
  </div>` : null}
22789
23887
 
22790
23888
  <h3 style="margin:0;font-family:var(--font-mono);font-size:11px;color:var(--fg-3);text-transform:uppercase;letter-spacing:.1em">
22791
- Cockpit
23889
+ ${t4("overview.cockpit")}
22792
23890
  </h3>
22793
23891
  <div class="cockpit">
22794
23892
  ${balanceKpi(c3)}
@@ -22802,17 +23900,17 @@ function OverviewPanel() {
22802
23900
  ${recentPlansRail(c3)}
22803
23901
  ${toolActivityFeed(c3)}
22804
23902
 
22805
- ${kpi("tools loaded", fmtNum(o3.toolCount), o3.toolCount ? "active" : "\u2014", "flat")}
22806
- ${kpi("mcp servers", fmtNum(o3.mcpServerCount), o3.mcpServerCount ? "all up" : "\u2014", o3.mcpServerCount ? "up" : "flat")}
22807
- ${kpi("edit mode", o3.editMode ?? "\u2014", o3.editMode === "yolo" ? "all prompts bypassed" : null, o3.editMode === "yolo" ? "down" : "flat")}
22808
- ${kpi("Reasonix", o3.version ?? "\u2014", versionDelta, versionTone)}
23903
+ ${kpi(t4("overview.toolsLoaded"), fmtNum(o3.toolCount), o3.toolCount ? t4("overview.active") : "\u2014", "flat")}
23904
+ ${kpi(t4("overview.mcpServers"), fmtNum(o3.mcpServerCount), o3.mcpServerCount ? t4("overview.allUp") : "\u2014", o3.mcpServerCount ? "up" : "flat")}
23905
+ ${kpi(t4("overview.editMode"), o3.editMode ?? "\u2014", o3.editMode === "yolo" ? t4("overview.yoloWarning") : null, o3.editMode === "yolo" ? "down" : "flat")}
23906
+ ${kpi(t4("overview.version"), o3.version ?? "\u2014", versionDelta, versionTone)}
22809
23907
  </div>
22810
23908
 
22811
23909
  <h3 style="margin:0;font-family:var(--font-mono);font-size:11px;color:var(--fg-3);text-transform:uppercase;letter-spacing:.1em">
22812
- Working directory
23910
+ ${t4("overview.workingDir")}
22813
23911
  </h3>
22814
23912
  <div class="card">
22815
- <div class="card-h"><span class="title">project root</span></div>
23913
+ <div class="card-h"><span class="title">${t4("overview.projectRoot")}</span></div>
22816
23914
  <code class="mono" style="color:var(--fg-2);font-size:12px">${o3.cwd ?? "\u2014"}</code>
22817
23915
  </div>
22818
23916
  </div>
@@ -22833,6 +23931,7 @@ function groupByVerb(list2) {
22833
23931
  return [...groups.entries()];
22834
23932
  }
22835
23933
  function PermissionsPanel() {
23934
+ useLang();
22836
23935
  const { data, error, loading, refresh } = usePoll("/permissions", 5e3);
22837
23936
  const [draft, setDraft] = p2("");
22838
23937
  const [busy, setBusy] = p2(false);
@@ -22847,8 +23946,8 @@ function PermissionsPanel() {
22847
23946
  method: "POST",
22848
23947
  body: { prefix }
22849
23948
  });
22850
- if (res.alreadyPresent) setFeedback({ kind: "info", text: `${prefix} already in list` });
22851
- else setFeedback({ kind: "ok", text: `added: ${prefix}` });
23949
+ if (res.alreadyPresent) setFeedback({ kind: "info", text: t4("permissions.alreadyIn", { prefix }) });
23950
+ else setFeedback({ kind: "ok", text: t4("permissions.added", { prefix }) });
22852
23951
  setDraft("");
22853
23952
  await refresh();
22854
23953
  } catch (err) {
@@ -22859,12 +23958,12 @@ function PermissionsPanel() {
22859
23958
  }, [draft, refresh]);
22860
23959
  const remove = x2(
22861
23960
  async (prefix) => {
22862
- if (!confirm(`Remove "${prefix}" from this project's allowlist?`)) return;
23961
+ if (!confirm(t4("permissions.removeConfirm", { prefix }))) return;
22863
23962
  setBusy(true);
22864
23963
  setFeedback(null);
22865
23964
  try {
22866
23965
  await api("/permissions", { method: "DELETE", body: { prefix } });
22867
- setFeedback({ kind: "ok", text: `removed: ${prefix}` });
23966
+ setFeedback({ kind: "ok", text: t4("permissions.removed", { prefix }) });
22868
23967
  await refresh();
22869
23968
  } catch (err) {
22870
23969
  setFeedback({ kind: "err", text: err.message });
@@ -22875,7 +23974,7 @@ function PermissionsPanel() {
22875
23974
  [refresh]
22876
23975
  );
22877
23976
  const clearAll = x2(async () => {
22878
- if (!confirm("Wipe every project allowlist entry? Builtin entries are unaffected.")) return;
23977
+ if (!confirm(t4("permissions.clearConfirm"))) return;
22879
23978
  setBusy(true);
22880
23979
  setFeedback(null);
22881
23980
  try {
@@ -22885,7 +23984,7 @@ function PermissionsPanel() {
22885
23984
  });
22886
23985
  setFeedback({
22887
23986
  kind: "ok",
22888
- text: `cleared ${res.dropped} entr${res.dropped === 1 ? "y" : "ies"}`
23987
+ text: t4("permissions.cleared", { count: res.dropped, y: res.dropped === 1 ? "y" : "ies" })
22889
23988
  });
22890
23989
  await refresh();
22891
23990
  } catch (err) {
@@ -22895,8 +23994,8 @@ function PermissionsPanel() {
22895
23994
  }
22896
23995
  }, [refresh]);
22897
23996
  if (loading && !data)
22898
- return html4`<div class="card" style="color:var(--fg-3)">loading permissions…</div>`;
22899
- if (error) return html4`<div class="card accent-err">permissions failed: ${error.message}</div>`;
23997
+ return html4`<div class="card" style="color:var(--fg-3)">${t4("permissions.loading")}</div>`;
23998
+ if (error) return html4`<div class="card accent-err">${t4("common.loadingFailed", { name: "permissions", error: error.message })}</div>`;
22900
23999
  if (!data) return null;
22901
24000
  const p3 = data;
22902
24001
  const feedbackPill = feedback ? html4`<span
@@ -22905,28 +24004,27 @@ function PermissionsPanel() {
22905
24004
  return html4`
22906
24005
  <div style="display:flex;flex-direction:column;gap:14px">
22907
24006
  ${p3.editMode === "yolo" ? html4`<div class="card accent-warn">
22908
- <div class="card-h"><span class="title" style="color:var(--c-warn)">YOLO mode</span></div>
24007
+ <div class="card-h"><span class="title" style="color:var(--c-warn)">${t4("permissions.yoloTitle")}</span></div>
22909
24008
  <div class="card-b">
22910
- Every shell command auto-runs, allowlist bypassed.
22911
- Switch back with <code class="mono">/mode review</code> in the TUI.
24009
+ ${t4("permissions.yoloDesc")}
22912
24010
  </div>
22913
24011
  </div>` : null}
22914
24012
 
22915
24013
  <div class="chips">
22916
- <span class="chip-f active">project <span class="ct">${p3.project.length}</span></span>
22917
- <span class="chip-f">builtin <span class="ct">${p3.builtin.length}</span></span>
24014
+ <span class="chip-f active">${t4("permissions.project")} <span class="ct">${p3.project.length}</span></span>
24015
+ <span class="chip-f">${t4("permissions.builtin")} <span class="ct">${p3.builtin.length}</span></span>
22918
24016
  </div>
22919
24017
 
22920
24018
  ${p3.currentCwd ? html4`
22921
24019
  <div class="card">
22922
24020
  <div class="card-h">
22923
- <span class="title">add a prefix</span>
24021
+ <span class="title">${t4("permissions.addPrefix")}</span>
22924
24022
  <span class="meta">${p3.currentCwd}</span>
22925
24023
  </div>
22926
24024
  <div style="display:flex;gap:8px;align-items:center">
22927
24025
  <input
22928
24026
  type="text"
22929
- placeholder='e.g. "npm run build" or "deploy.sh"'
24027
+ placeholder=${t4("permissions.addPlaceholder")}
22930
24028
  value=${draft}
22931
24029
  onInput=${(e3) => setDraft(e3.target.value)}
22932
24030
  onKeyDown=${(e3) => {
@@ -22935,35 +24033,33 @@ function PermissionsPanel() {
22935
24033
  disabled=${busy}
22936
24034
  style="flex:1"
22937
24035
  />
22938
- <button class="primary" onClick=${add} disabled=${busy || !draft.trim()}>Add</button>
24036
+ <button class="primary" onClick=${add} disabled=${busy || !draft.trim()}>${t4("common.add")}</button>
22939
24037
  <button
22940
24038
  class="danger"
22941
24039
  onClick=${clearAll}
22942
24040
  disabled=${busy || p3.project.length === 0}
22943
- >Clear all</button>
24041
+ >${t4("permissions.clearAll")}</button>
22944
24042
  </div>
22945
24043
  ${feedbackPill ? html4`<div style="margin-top:8px">${feedbackPill}</div>` : null}
22946
24044
  </div>
22947
24045
  ` : html4`
22948
24046
  <div class="card accent-warn">
22949
24047
  <div class="card-b">
22950
- Mutations require <code class="mono">/dashboard</code> from inside an active
22951
- <code class="mono">reasonix code</code> session — standalone
22952
- <code class="mono">reasonix dashboard</code> can't tell which project's allowlist to edit.
24048
+ ${t4("permissions.standaloneWarning")}
22953
24049
  </div>
22954
24050
  </div>
22955
24051
  `}
22956
24052
 
22957
24053
  <h3 style="margin:6px 0 0;font-family:var(--font-mono);font-size:11px;color:var(--fg-3);text-transform:uppercase;letter-spacing:.1em">
22958
- Project allowlist · ${p3.project.length}
24054
+ ${t4("permissions.projectAllowlist", { count: p3.project.length })}
22959
24055
  </h3>
22960
- ${p3.project.length === 0 ? html4`<div class="card" style="color:var(--fg-3)">Nothing stored yet for this project.</div>` : html4`
24056
+ ${p3.project.length === 0 ? html4`<div class="card" style="color:var(--fg-3)">${t4("permissions.nothingStored")}</div>` : html4`
22961
24057
  <div class="card" style="padding:0;overflow:hidden">
22962
24058
  <table class="tbl">
22963
24059
  <thead>
22964
24060
  <tr>
22965
- <th style="width:48px">#</th>
22966
- <th>prefix</th>
24061
+ <th style="width:48px">${t4("permissions.colNum")}</th>
24062
+ <th>${t4("permissions.colPrefix")}</th>
22967
24063
  <th style="width:120px"></th>
22968
24064
  </tr>
22969
24065
  </thead>
@@ -22978,7 +24074,7 @@ function PermissionsPanel() {
22978
24074
  class="danger"
22979
24075
  onClick=${() => remove(prefix)}
22980
24076
  disabled=${busy}
22981
- >remove</button>` : null}
24077
+ >${t4("common.remove")}</button>` : null}
22982
24078
  </td>
22983
24079
  </tr>
22984
24080
  `
@@ -22989,7 +24085,7 @@ function PermissionsPanel() {
22989
24085
  `}
22990
24086
 
22991
24087
  <h3 style="margin:6px 0 0;font-family:var(--font-mono);font-size:11px;color:var(--fg-3);text-transform:uppercase;letter-spacing:.1em">
22992
- Builtin · ${p3.builtin.length} · read-only
24088
+ ${t4("permissions.builtinTitle", { count: p3.builtin.length })}
22993
24089
  </h3>
22994
24090
  <div class="card" style="font-family:var(--font-mono);font-size:11.5px;line-height:1.8">
22995
24091
  ${groupByVerb(p3.builtin).map(
@@ -23007,22 +24103,22 @@ function PermissionsPanel() {
23007
24103
 
23008
24104
  // dashboard/src/panels/plans.ts
23009
24105
  function statusPill(p3) {
23010
- if (p3.completionRatio >= 1) return html4`<span class="pill ok">done</span>`;
23011
- if (p3.completionRatio > 0) return html4`<span class="pill info">active</span>`;
24106
+ if (p3.completionRatio >= 1) return html4`<span class="pill ok">${t4("plans.done")}</span>`;
24107
+ if (p3.completionRatio > 0) return html4`<span class="pill info">${t4("plans.active")}</span>`;
23012
24108
  return html4`<span class="pill">idle</span>`;
23013
24109
  }
23014
24110
  function PlansPanel() {
24111
+ useLang();
23015
24112
  const { data, error, loading } = usePoll("/plans", 8e3);
23016
24113
  const [openIdx, setOpenIdx] = p2(null);
23017
24114
  const [filter, setFilter] = p2("");
23018
24115
  if (loading && !data)
23019
- return html4`<div class="card" style="color:var(--fg-3)">loading plans…</div>`;
23020
- if (error) return html4`<div class="card accent-err">plans failed: ${error.message}</div>`;
24116
+ return html4`<div class="card" style="color:var(--fg-3)">${t4("plans.loading")}</div>`;
24117
+ if (error) return html4`<div class="card accent-err">${t4("common.loadingFailed", { name: "plans", error: error.message })}</div>`;
23021
24118
  const plans = data?.plans ?? [];
23022
24119
  if (plans.length === 0)
23023
24120
  return html4`<div class="card" style="color:var(--fg-3)">
23024
- No archived plans yet — run a turn that calls <code class="mono">submit_plan</code>
23025
- and <code class="mono">mark_step_complete</code>.
24121
+ ${t4("plans.noPlans")}
23026
24122
  </div>`;
23027
24123
  const filtered = filter.trim() ? plans.filter(
23028
24124
  (p3) => p3.session.toLowerCase().includes(filter.toLowerCase()) || (p3.summary ?? "").toLowerCase().includes(filter.toLowerCase())
@@ -23034,20 +24130,20 @@ function PlansPanel() {
23034
24130
  <div class="ssl-h">
23035
24131
  <input
23036
24132
  type="text"
23037
- placeholder="filter plans"
24133
+ placeholder=${t4("plans.filterPlaceholder")}
23038
24134
  value=${filter}
23039
24135
  onInput=${(e3) => setFilter(e3.target.value)}
23040
24136
  style="flex:1"
23041
24137
  />
23042
24138
  </div>
23043
24139
  <div class="chips" style="padding:0 12px 8px">
23044
- <span class="chip-f active">all <span class="ct">${plans.length}</span></span>
24140
+ <span class="chip-f active">${t4("common.all")} <span class="ct">${plans.length}</span></span>
23045
24141
  <span class="chip-f">
23046
- active
24142
+ ${t4("plans.active")}
23047
24143
  <span class="ct">${plans.filter((p3) => p3.completionRatio > 0 && p3.completionRatio < 1).length}</span>
23048
24144
  </span>
23049
24145
  <span class="chip-f">
23050
- done <span class="ct">${plans.filter((p3) => p3.completionRatio >= 1).length}</span>
24146
+ ${t4("plans.done")} <span class="ct">${plans.filter((p3) => p3.completionRatio >= 1).length}</span>
23051
24147
  </span>
23052
24148
  </div>
23053
24149
  <div class="ssl-rows">
@@ -23059,7 +24155,7 @@ function PlansPanel() {
23059
24155
  <span class="name">${p3.summary ?? p3.session} ${statusPill(p3)}</span>
23060
24156
  ${p3.summary && p3.session !== p3.summary ? html4`<span class="preview">${p3.session}</span>` : null}
23061
24157
  <span class="meta">
23062
- <span><span class="v">${p3.totalSteps}</span> steps</span>
24158
+ <span><span class="v">${p3.totalSteps}</span> ${t4("plans.steps")}</span>
23063
24159
  <span><span class="v">${p3.completedSteps} / ${p3.totalSteps}</span> · ${fmtPct(p3.completionRatio)}</span>
23064
24160
  <span>${fmtRelativeTime(p3.completedAt)}</span>
23065
24161
  </span>
@@ -23071,18 +24167,18 @@ function PlansPanel() {
23071
24167
 
23072
24168
  <div class="sessions-detail">
23073
24169
  ${open == null ? html4`<div style="color:var(--fg-3);font-size:13px;text-align:center;padding:60px 20px">
23074
- Pick a plan on the left.
24170
+ ${t4("plans.pickHint")}
23075
24171
  </div>` : html4`
23076
24172
  <div class="sessions-detail-h">
23077
- <span class="name">${open.summary ?? "(no title)"}</span>
24173
+ <span class="name">${open.summary ?? t4("plans.noTitle")}</span>
23078
24174
  <span class="ws">${open.session} · ${fmtRelativeTime(open.completedAt)}</span>
23079
24175
  <span class="actions">
23080
- <button class="btn ghost" onClick=${() => setOpenIdx(null)}>← back</button>
24176
+ <button class="btn ghost" onClick=${() => setOpenIdx(null)}>${t4("common.back")}</button>
23081
24177
  </span>
23082
24178
  </div>
23083
24179
 
23084
24180
  <h3 style="margin:0 0 6px;font-family:var(--font-mono);font-size:11px;color:var(--fg-3);text-transform:uppercase;letter-spacing:.1em">
23085
- Step timeline · ${open.completedSteps} / ${open.totalSteps}
24181
+ ${t4("plans.stepTimeline", { done: open.completedSteps, total: open.totalSteps })}
23086
24182
  </h3>
23087
24183
  <div class="plan-timeline" style="margin-bottom:14px">
23088
24184
  ${open.steps.map((step, i3) => {
@@ -23090,7 +24186,7 @@ function PlansPanel() {
23090
24186
  const cls = done ? "done" : i3 === open.completedSteps ? "active" : "";
23091
24187
  return html4`
23092
24188
  <div class=${`plan-step ${cls}`}>
23093
- <span class="lbl">step ${i3 + 1}</span>
24189
+ <span class="lbl">${t4("plans.step", { n: i3 + 1 })}</span>
23094
24190
  <span class="name">${step.title}</span>
23095
24191
  ${step.action ? html4`<span class="meta">${step.action}</span>` : null}
23096
24192
  ${step.risk ? html4`<span
@@ -23109,6 +24205,7 @@ function PlansPanel() {
23109
24205
 
23110
24206
  // dashboard/src/panels/semantic.ts
23111
24207
  function SemanticPanel() {
24208
+ useLang();
23112
24209
  const [data, setData] = p2(null);
23113
24210
  const [error, setError] = p2(null);
23114
24211
  const [busy, setBusy] = p2(false);
@@ -23137,7 +24234,7 @@ function SemanticPanel() {
23137
24234
  setInfo(null);
23138
24235
  try {
23139
24236
  await api("/semantic/start", { method: "POST", body: { rebuild: !!rebuild } });
23140
- setInfo(rebuild ? "rebuild started" : "incremental index started");
24237
+ setInfo(rebuild ? t4("semantic.rebuildStarted") : t4("semantic.incrementalStarted"));
23141
24238
  await load();
23142
24239
  } catch (err) {
23143
24240
  setError(err.message);
@@ -23152,7 +24249,7 @@ function SemanticPanel() {
23152
24249
  setError(null);
23153
24250
  try {
23154
24251
  await api("/semantic/stop", { method: "POST", body: {} });
23155
- setInfo("stopping requested \u2014 current chunk batch will finish first");
24252
+ setInfo(t4("semantic.stopRequested"));
23156
24253
  await load();
23157
24254
  } catch (err) {
23158
24255
  setError(err.message);
@@ -23163,11 +24260,11 @@ function SemanticPanel() {
23163
24260
  const startDaemon = x2(async () => {
23164
24261
  setBusy(true);
23165
24262
  setError(null);
23166
- setInfo("starting ollama daemon (15s timeout)\u2026");
24263
+ setInfo(t4("semantic.startingDaemon"));
23167
24264
  try {
23168
24265
  const r3 = await api("/semantic/ollama/start", { method: "POST", body: {} });
23169
24266
  setInfo(
23170
- r3.ready ? "daemon is up" : "daemon didn't come up in time \u2014 check `ollama serve` manually"
24267
+ r3.ready ? t4("semantic.daemonUp") : t4("semantic.daemonTimeout")
23171
24268
  );
23172
24269
  await load();
23173
24270
  } catch (err) {
@@ -23180,7 +24277,7 @@ function SemanticPanel() {
23180
24277
  async (model) => {
23181
24278
  setBusy(true);
23182
24279
  setError(null);
23183
- setInfo(`pulling ${model} \u2014 this may take a few minutes on first install`);
24280
+ setInfo(t4("semantic.pullingModel", { model }));
23184
24281
  try {
23185
24282
  await api("/semantic/ollama/pull", { method: "POST", body: { model } });
23186
24283
  await load();
@@ -23193,13 +24290,13 @@ function SemanticPanel() {
23193
24290
  [load]
23194
24291
  );
23195
24292
  if (!data && !error)
23196
- return html4`<div class="card" style="color:var(--fg-3)">loading semantic status…</div>`;
24293
+ return html4`<div class="card" style="color:var(--fg-3)">${t4("common.loading")}</div>`;
23197
24294
  if (error && !data) return html4`<div class="card accent-err">${error}</div>`;
23198
24295
  if (!data) return null;
23199
24296
  if (!data.attached) {
23200
24297
  return html4`
23201
24298
  <div class="card" style="color:var(--fg-3)">
23202
- <div class="card-h"><span class="title">Semantic — code-mode required</span></div>
24299
+ <div class="card-h"><span class="title">${t4("semantic.codeRequired")}</span></div>
23203
24300
  <div class="card-b">${data.reason}</div>
23204
24301
  </div>
23205
24302
  `;
@@ -23225,9 +24322,9 @@ function SemanticPanel() {
23225
24322
  <div style="display:flex;flex-direction:column;gap:10px;min-width:0">
23226
24323
  <div class="chips">
23227
24324
  <span class=${`chip-f ${idx?.exists ? "active" : ""}`}>
23228
- ${idx?.exists ? "index built" : "no index yet"}
24325
+ ${idx?.exists ? t4("semantic.indexBuilt") : t4("semantic.noIndex")}
23229
24326
  </span>
23230
- ${ready ? html4`<span class="chip-f" style="border-color:var(--c-ok);color:var(--c-ok)">ready</span>` : html4`<span class="chip-f" style="border-color:var(--c-warn);color:var(--c-warn)">setup needed</span>`}
24327
+ ${ready ? html4`<span class="chip-f" style="border-color:var(--c-ok);color:var(--c-ok)">${t4("semantic.ready")}</span>` : html4`<span class="chip-f" style="border-color:var(--c-warn);color:var(--c-warn)">${t4("semantic.setupNeeded")}</span>`}
23231
24328
  </div>
23232
24329
  ${info ? html4`<div><span class="pill info">${info}</span></div>` : null}
23233
24330
  ${error ? html4`<div class="card accent-err">${error}</div>` : null}
@@ -23236,37 +24333,37 @@ function SemanticPanel() {
23236
24333
 
23237
24334
  ${!binaryFound ? html4`
23238
24335
  <div class="card">
23239
- <div class="card-h"><span class="title">Install Ollama</span></div>
24336
+ <div class="card-h"><span class="title">${t4("semantic.installOllama")}</span></div>
23240
24337
  <div class="card-b" style="font-size:13px">
23241
- Reasonix doesn't run package managers for you. Install Ollama first, then come back:
24338
+ ${t4("semantic.installOllamaDesc")}
23242
24339
  <ul style="margin:10px 0 4px 18px;padding:0">
23243
- <li><strong>macOS / Windows:</strong> download from <a href="https://ollama.com/download" target="_blank" rel="noreferrer">ollama.com/download</a></li>
23244
- <li><strong>Linux:</strong> <code class="mono">curl -fsSL https://ollama.com/install.sh | sh</code></li>
24340
+ <li><strong>${t4("semantic.macWindows")}</strong> ${t4("semantic.download")} <a href="https://ollama.com/download" target="_blank" rel="noreferrer">ollama.com/download</a></li>
24341
+ <li><strong>${t4("semantic.linux")}</strong> <code class="mono">curl -fsSL https://ollama.com/install.sh | sh</code></li>
23245
24342
  </ul>
23246
- <div style="color:var(--fg-3);margin-top:8px">Refresh after install this panel will offer to start the daemon and pull <code class="mono">${modelName}</code>.</div>
24343
+ <div style="color:var(--fg-3);margin-top:8px">${t4("semantic.refreshHint", { model: modelName })}</div>
23247
24344
  </div>
23248
24345
  </div>
23249
24346
  ` : null}
23250
24347
  ${binaryFound && !daemonRunning ? html4`
23251
24348
  <div class="card">
23252
- <div class="card-h"><span class="title">Daemon</span></div>
24349
+ <div class="card-h"><span class="title">${t4("semantic.daemon")}</span></div>
23253
24350
  <div class="card-b" style="font-size:13px">
23254
- <code class="mono">ollama</code> is on your PATH but the HTTP daemon isn't reachable.
24351
+ ${t4("semantic.daemonDesc")}
23255
24352
  <div style="display:flex;gap:8px;margin-top:10px;align-items:center">
23256
- <button class="primary" disabled=${busy} onClick=${startDaemon}>Start daemon</button>
23257
- <span style="color:var(--fg-3);font-size:12px">runs <code class="mono">ollama serve</code> detached</span>
24353
+ <button class="primary" disabled=${busy} onClick=${startDaemon}>${t4("semantic.startDaemon")}</button>
24354
+ <span style="color:var(--fg-3);font-size:12px">${t4("semantic.runsOllama")}</span>
23258
24355
  </div>
23259
24356
  </div>
23260
24357
  </div>
23261
24358
  ` : null}
23262
24359
  ${daemonRunning && !modelPulled ? html4`
23263
24360
  <div class="card">
23264
- <div class="card-h"><span class="title">Model</span></div>
24361
+ <div class="card-h"><span class="title">${t4("semantic.model")}</span></div>
23265
24362
  <div class="card-b" style="font-size:13px">
23266
- <code class="mono">${modelName}</code> isn't installed yet.${pulling ? "" : " ~270 MB on first pull."}
24363
+ ${t4("semantic.modelMissing", { model: modelName })}${pulling ? "" : ` ${t4("semantic.modelSize")}`}
23267
24364
  <div style="display:flex;gap:8px;margin-top:10px">
23268
24365
  <button class="primary" disabled=${busy || pulling} onClick=${() => pullModel(modelName)}>
23269
- ${pulling ? "pulling\u2026" : `Pull ${modelName}`}
24366
+ ${pulling ? t4("semantic.pulling") : t4("semantic.pullModel", { model: modelName })}
23270
24367
  </button>
23271
24368
  </div>
23272
24369
  ${pull ? html4`
@@ -23281,7 +24378,7 @@ function SemanticPanel() {
23281
24378
  ` : null}
23282
24379
 
23283
24380
  ${job ? html4`
23284
- ${sectionH3("Job")}
24381
+ ${sectionH3(t4("semantic.job"))}
23285
24382
  <${SemanticJobView} job=${job} running=${running} />
23286
24383
  ` : null}
23287
24384
  </div>
@@ -23289,43 +24386,43 @@ function SemanticPanel() {
23289
24386
  <aside style="display:flex;flex-direction:column;gap:10px">
23290
24387
  <div class="card">
23291
24388
  <div class="card-h">
23292
- <span class="title">index status</span>
24389
+ <span class="title">${t4("semantic.indexStatus")}</span>
23293
24390
  <span class="meta">
23294
- ${idx?.exists ? html4`<span class="pill ok">● built</span>` : html4`<span class="pill">none</span>`}
24391
+ ${idx?.exists ? html4`<span class="pill ok">${t4("semantic.builtStatus")}</span>` : html4`<span class="pill">${t4("system.none")}</span>`}
23295
24392
  </span>
23296
24393
  </div>
23297
24394
  ${idx?.exists ? html4`
23298
- <div class="rail-kv"><span class="k">chunks</span><span class="v">${fmtNum(idx.chunks)}</span></div>
23299
- <div class="rail-kv"><span class="k">files</span><span class="v">${fmtNum(idx.files)}</span></div>
23300
- <div class="rail-kv"><span class="k">model</span><span class="v" style="font-size:11px">${idx.model ?? modelName}</span></div>
23301
- <div class="rail-kv"><span class="k">dim</span><span class="v">${fmtNum(idx.dim)}</span></div>
23302
- <div class="rail-kv"><span class="k">size</span><span class="v">${fmtBytes(idx.sizeBytes)}</span></div>
23303
- <div class="rail-kv"><span class="k">last build</span><span class="v">${fmtRelativeTime(idx.lastBuiltMs ?? null)}</span></div>
24395
+ <div class="rail-kv"><span class="k">${t4("semantic.chunks")}</span><span class="v">${fmtNum(idx.chunks)}</span></div>
24396
+ <div class="rail-kv"><span class="k">${t4("semantic.files")}</span><span class="v">${fmtNum(idx.files)}</span></div>
24397
+ <div class="rail-kv"><span class="k">${t4("semantic.model")}</span><span class="v" style="font-size:11px">${idx.model ?? modelName}</span></div>
24398
+ <div class="rail-kv"><span class="k">${t4("semantic.dim")}</span><span class="v">${fmtNum(idx.dim)}</span></div>
24399
+ <div class="rail-kv"><span class="k">${t4("semantic.size")}</span><span class="v">${fmtBytes(idx.sizeBytes)}</span></div>
24400
+ <div class="rail-kv"><span class="k">${t4("semantic.lastBuild")}</span><span class="v">${fmtRelativeTime(idx.lastBuiltMs ?? null)}</span></div>
23304
24401
  ` : html4`
23305
24402
  <div style="color:var(--fg-3);font-size:12.5px;padding:6px 0">
23306
- Run an index to enable <code class="mono">semantic_search</code>.
24403
+ ${t4("semantic.runIndexHint")}
23307
24404
  </div>
23308
24405
  `}
23309
24406
  <div style="display:flex;gap:6px;margin-top:10px;flex-wrap:wrap">
23310
- <button class="primary" disabled=${busy || running || !ready} onClick=${() => start(false)}>${idx?.exists ? "Re-index" : "Build"}</button>
23311
- ${idx?.exists ? html4`<button disabled=${busy || running || !ready} onClick=${() => start(true)}>Rebuild</button>` : null}
23312
- ${running ? html4`<button onClick=${stop} style="border-color:var(--c-err);color:var(--c-err)">Stop</button>` : null}
24407
+ <button class="primary" disabled=${busy || running || !ready} onClick=${() => start(false)}>${idx?.exists ? t4("semantic.reIndex") : t4("semantic.build")}</button>
24408
+ ${idx?.exists ? html4`<button disabled=${busy || running || !ready} onClick=${() => start(true)}>${t4("semantic.rebuild")}</button>` : null}
24409
+ ${running ? html4`<button onClick=${stop} style="border-color:var(--c-err);color:var(--c-err)">${t4("semantic.stop")}</button>` : null}
23313
24410
  </div>
23314
24411
  </div>
23315
24412
 
23316
24413
  <div class="card">
23317
- <div class="card-h"><span class="title">ollama</span></div>
24414
+ <div class="card-h"><span class="title">${t4("semantic.ollama")}</span></div>
23318
24415
  <div class="rail-kv">
23319
- <span class="k">binary</span>
23320
- <span class="v">${binaryFound ? html4`<span class="pill ok">found</span>` : html4`<span class="pill err">missing</span>`}</span>
24416
+ <span class="k">${t4("semantic.binary")}</span>
24417
+ <span class="v">${binaryFound ? html4`<span class="pill ok">${t4("semantic.found")}</span>` : html4`<span class="pill err">${t4("semantic.missing")}</span>`}</span>
23321
24418
  </div>
23322
24419
  <div class="rail-kv">
23323
- <span class="k">daemon</span>
23324
- <span class="v">${daemonRunning ? html4`<span class="pill ok">up</span>` : html4`<span class="pill warn">down</span>`}</span>
24420
+ <span class="k">${t4("semantic.daemonStatus")}</span>
24421
+ <span class="v">${daemonRunning ? html4`<span class="pill ok">${t4("semantic.up")}</span>` : html4`<span class="pill warn">${t4("semantic.down")}</span>`}</span>
23325
24422
  </div>
23326
24423
  <div class="rail-kv">
23327
- <span class="k">model</span>
23328
- <span class="v">${modelPulled ? html4`<span class="pill ok">pulled</span>` : html4`<span class="pill warn">missing</span>`}</span>
24424
+ <span class="k">${t4("semantic.model")}</span>
24425
+ <span class="v">${modelPulled ? html4`<span class="pill ok">${t4("semantic.pulled")}</span>` : html4`<span class="pill warn">${t4("semantic.missing")}</span>`}</span>
23329
24426
  </div>
23330
24427
  </div>
23331
24428
 
@@ -23335,6 +24432,7 @@ function SemanticPanel() {
23335
24432
  `;
23336
24433
  }
23337
24434
  function SemanticSearchSection() {
24435
+ useLang();
23338
24436
  const [query2, setQuery] = p2("");
23339
24437
  const [hits, setHits] = p2(null);
23340
24438
  const [meta, setMeta] = p2(null);
@@ -23367,7 +24465,7 @@ function SemanticSearchSection() {
23367
24465
  type="text"
23368
24466
  class="mono"
23369
24467
  style="width:100%;padding:10px 14px 10px 38px;font-size:13.5px;background:var(--bg-input);border:1px solid var(--bd);border-radius:var(--r);color:var(--fg-0);outline:none"
23370
- placeholder="describe what to find — 'where do we handle abort signals'"
24468
+ placeholder=${t4("semantic.searchPlaceholder")}
23371
24469
  value=${query2}
23372
24470
  disabled=${busy}
23373
24471
  onInput=${(e3) => setQuery(e3.target.value)}
@@ -23381,7 +24479,7 @@ function SemanticSearchSection() {
23381
24479
  </div>
23382
24480
  ${hits || busy || error ? html4`
23383
24481
  <div style="font-family:var(--font-mono);font-size:11px;color:var(--fg-3);margin:8px 0 6px;display:flex;align-items:center;gap:8px">
23384
- ${busy ? html4`<span>searching…</span>` : error ? html4`<span style="color:var(--c-err)">${error}</span>` : hits ? html4`<span>${hits.length} result${hits.length === 1 ? "" : "s"} · ${meta?.elapsedMs ?? 0}ms · ${meta?.model ?? ""}</span>` : null}
24482
+ ${busy ? html4`<span>${t4("semantic.searching")}</span>` : error ? html4`<span style="color:var(--c-err)">${error}</span>` : hits ? html4`<span>${t4("semantic.results", { count: hits.length, s: hits.length === 1 ? "" : "s", ms: meta?.elapsedMs ?? 0, model: meta?.model ?? "" })}</span>` : null}
23385
24483
  </div>
23386
24484
  ${hits && hits.length > 0 ? html4`
23387
24485
  <div class="card" style="padding:0;max-height:420px;overflow-y:auto">
@@ -23398,7 +24496,7 @@ function SemanticSearchSection() {
23398
24496
  `
23399
24497
  )}
23400
24498
  </div>
23401
- ` : hits && hits.length === 0 && !busy ? html4`<div class="card" style="color:var(--fg-3);font-size:12px">No matches above the score threshold.</div>` : null}
24499
+ ` : hits && hits.length === 0 && !busy ? html4`<div class="card" style="color:var(--fg-3);font-size:12px">${t4("semantic.noMatches")}</div>` : null}
23402
24500
  ` : null}
23403
24501
  </div>
23404
24502
  `;
@@ -23430,6 +24528,7 @@ function fromDraft(d3) {
23430
24528
  };
23431
24529
  }
23432
24530
  function SemanticExcludesCard() {
24531
+ useLang();
23433
24532
  const [data, setData] = p2(null);
23434
24533
  const [draft, setDraft] = p2(null);
23435
24534
  const [preview, setPreview] = p2(null);
@@ -23463,7 +24562,7 @@ function SemanticExcludesCard() {
23463
24562
  method: "POST",
23464
24563
  body: payload
23465
24564
  });
23466
- setInfo(`saved \xB7 ${r3.changed.length || 0} fields updated \xB7 re-run index to apply`);
24565
+ setInfo(t4("semantic.savedConfig", { count: r3.changed.length || 0 }));
23467
24566
  await load();
23468
24567
  } catch (err) {
23469
24568
  setError(err.message);
@@ -23475,7 +24574,7 @@ function SemanticExcludesCard() {
23475
24574
  if (!draft) return;
23476
24575
  setBusy(true);
23477
24576
  setError(null);
23478
- setInfo("running dry walk against project root\u2026");
24577
+ setInfo(t4("semantic.runningPreview"));
23479
24578
  try {
23480
24579
  const payload = fromDraft(draft);
23481
24580
  const r3 = await api("/index-config/preview", {
@@ -23494,47 +24593,47 @@ function SemanticExcludesCard() {
23494
24593
  if (!draft) {
23495
24594
  return html4`
23496
24595
  <div class="card">
23497
- <div class="card-h"><span class="title">index config</span></div>
23498
- <div style="color:var(--fg-3);font-size:12.5px">loading…</div>
24596
+ <div class="card-h"><span class="title">${t4("semantic.indexConfig")}</span></div>
24597
+ <div style="color:var(--fg-3);font-size:12.5px">${t4("common.loading")}</div>
23499
24598
  </div>
23500
24599
  `;
23501
24600
  }
23502
24601
  return html4`
23503
24602
  <div class="card">
23504
24603
  <div class="card-h">
23505
- <span class="title">index config</span>
24604
+ <span class="title">${t4("semantic.indexConfig")}</span>
23506
24605
  <span class="meta">
23507
24606
  <a
23508
24607
  class="mono"
23509
24608
  style="color:var(--c-brand);text-decoration:none;font-size:11px;cursor:pointer"
23510
24609
  onClick=${reset}
23511
- >reset</a>
24610
+ >${t4("semantic.reset")}</a>
23512
24611
  </span>
23513
24612
  </div>
23514
24613
  ${info ? html4`<div style="margin-bottom:8px"><span class="pill ok">${info}</span></div>` : null}
23515
24614
  ${error ? html4`<div class="card accent-err" style="margin-bottom:8px">${error}</div>` : null}
23516
24615
 
23517
24616
  <${ChipFormRow}
23518
- label="exclude dirs"
24617
+ label=${t4("semantic.excludeDirs")}
23519
24618
  value=${draft.excludeDirs}
23520
24619
  onChange=${(v3) => setDraft({ ...draft, excludeDirs: v3 })}
23521
24620
  placeholder="dist"
23522
24621
  />
23523
24622
  <${ChipFormRow}
23524
- label="exclude files"
24623
+ label=${t4("semantic.excludeFiles")}
23525
24624
  value=${draft.excludeFiles}
23526
24625
  onChange=${(v3) => setDraft({ ...draft, excludeFiles: v3 })}
23527
24626
  placeholder="package-lock.json"
23528
24627
  />
23529
24628
  <${ChipFormRow}
23530
- label="exclude exts"
24629
+ label=${t4("semantic.excludeExts")}
23531
24630
  value=${draft.excludeExts}
23532
24631
  onChange=${(v3) => setDraft({ ...draft, excludeExts: v3 })}
23533
24632
  placeholder=".lock"
23534
24633
  />
23535
24634
  <${ChipFormRow}
23536
- label="exclude patterns"
23537
- sub="glob"
24635
+ label=${t4("semantic.excludePatterns")}
24636
+ sub=${t4("semantic.glob")}
23538
24637
  value=${draft.excludePatterns}
23539
24638
  onChange=${(v3) => setDraft({ ...draft, excludePatterns: v3 })}
23540
24639
  placeholder="**/*.test.ts"
@@ -23546,11 +24645,11 @@ function SemanticExcludesCard() {
23546
24645
  onClick=${() => setDraft({ ...draft, respectGitignore: !draft.respectGitignore })}
23547
24646
  >
23548
24647
  <span class=${`box ${draft.respectGitignore ? "on" : ""}`}>${draft.respectGitignore ? "\u2713" : ""}</span>
23549
- <span>respect <code class="mono">.gitignore</code></span>
24648
+ <span>${t4("semantic.respectGitignore")}</span>
23550
24649
  </div>
23551
24650
 
23552
24651
  <div class="form-row" style="margin-top:10px">
23553
- <span class="lbl">max file bytes</span>
24652
+ <span class="lbl">${t4("semantic.maxFileBytes")}</span>
23554
24653
  <input
23555
24654
  class="input mono"
23556
24655
  type="number"
@@ -23560,14 +24659,14 @@ function SemanticExcludesCard() {
23560
24659
  onInput=${(e3) => setDraft({ ...draft, maxFileBytes: Number(e3.target.value) || 0 })}
23561
24660
  style="font-size:12px"
23562
24661
  />
23563
- <span class="help">skip files larger than ~${(draft.maxFileBytes / 1024 / 1024).toFixed(1)} MiB</span>
24662
+ <span class="help">${t4("semantic.skipLarger", { size: (draft.maxFileBytes / 1024 / 1024).toFixed(1) })}</span>
23564
24663
  </div>
23565
24664
 
23566
24665
  <div style="display:flex;gap:6px;margin-top:10px">
23567
24666
  <button class="btn ghost" style="flex:1" disabled=${busy} onClick=${runPreview}>
23568
- <span class="g">⊕</span><span>Preview</span>
24667
+ <span class="g">⊕</span><span>${t4("semantic.preview")}</span>
23569
24668
  </button>
23570
- <button class="btn primary" style="flex:1" disabled=${busy} onClick=${save}>Save</button>
24669
+ <button class="btn primary" style="flex:1" disabled=${busy} onClick=${save}>${t4("common.save")}</button>
23571
24670
  </div>
23572
24671
 
23573
24672
  ${preview ? html4`<div style="margin-top:10px"><${ExcludesPreview} preview=${preview} /></div>` : null}
@@ -23575,6 +24674,7 @@ function SemanticExcludesCard() {
23575
24674
  `;
23576
24675
  }
23577
24676
  function ExcludesPreview({ preview }) {
24677
+ useLang();
23578
24678
  const buckets = preview.skipBuckets || {};
23579
24679
  const samples = preview.skipSamples || {};
23580
24680
  const totalSkipped = Object.values(buckets).reduce((a3, b2) => a3 + (b2 || 0), 0);
@@ -23591,9 +24691,9 @@ function ExcludesPreview({ preview }) {
23591
24691
  return html4`
23592
24692
  <div class="excludes-preview">
23593
24693
  <div class="summary">
23594
- Preview would index <strong>${preview.filesIncluded}</strong> file(s), skip <strong>${totalSkipped}</strong>
24694
+ ${t4("semantic.previewSummary", { included: preview.filesIncluded, skipped: totalSkipped })}
23595
24695
  </div>
23596
- ${reasons.length === 0 ? html4`<div style="color:var(--fg-3)">nothing skipped — all walked files would be indexed.</div>` : reasons.map(
24696
+ ${reasons.length === 0 ? html4`<div style="color:var(--fg-3)">${t4("semantic.nothingSkipped")}</div>` : reasons.map(
23597
24697
  (r3) => html4`
23598
24698
  <details>
23599
24699
  <summary><strong>${r3}: ${buckets[r3]}</strong></summary>
@@ -23606,7 +24706,7 @@ function ExcludesPreview({ preview }) {
23606
24706
  )}
23607
24707
  ${preview.sampleIncluded?.length ? html4`
23608
24708
  <details>
23609
- <summary>first ${preview.sampleIncluded.length} included file(s)</summary>
24709
+ <summary>${t4("semantic.firstIncluded", { count: preview.sampleIncluded.length })}</summary>
23610
24710
  <ul>
23611
24711
  ${preview.sampleIncluded.map((p3) => html4`<li><code>${p3}</code></li>`)}
23612
24712
  </ul>
@@ -23667,12 +24767,13 @@ function ChipFormRow({
23667
24767
  `;
23668
24768
  }
23669
24769
  function SemanticJobView({ job, running }) {
24770
+ useLang();
23670
24771
  const phaseLabel = {
23671
- scan: "scanning files",
23672
- embed: "embedding chunks",
23673
- write: "writing index",
23674
- done: "done",
23675
- error: "error"
24772
+ scan: t4("semantic.phaseScan"),
24773
+ embed: t4("semantic.phaseEmbed"),
24774
+ write: t4("semantic.phaseWrite"),
24775
+ done: t4("semantic.phaseDone"),
24776
+ error: t4("semantic.phaseError")
23676
24777
  }[job.phase] ?? job.phase;
23677
24778
  const total = job.chunksTotal ?? 0;
23678
24779
  const doneN = job.chunksDone ?? 0;
@@ -23682,25 +24783,26 @@ function SemanticJobView({ job, running }) {
23682
24783
  <div class="kv">
23683
24784
  <div><span class="kv-key">phase</span>
23684
24785
  <span class=${`pill ${job.phase === "error" ? "pill-err" : running ? "pill-active" : "pill-dim"}`}>${phaseLabel}</span>
23685
- ${job.aborted ? html4`<span class="pill warn" style="margin-left: 6px;">stopping</span>` : null}
24786
+ ${job.aborted ? html4`<span class="pill warn" style="margin-left: 6px;">${t4("semantic.stopping")}</span>` : null}
23686
24787
  <span style="color:var(--fg-3);margin-left:8px">${elapsed}s</span>
23687
24788
  </div>
23688
- ${job.filesScanned !== null && job.filesScanned !== void 0 ? html4`<div><span class="kv-key">files</span>scanned ${job.filesScanned}${job.filesChanged != null ? ` \xB7 changed ${job.filesChanged}` : ""}${job.filesSkipped ? ` \xB7 skipped ${job.filesSkipped}` : ""}</div>` : null}
24789
+ ${job.filesScanned !== null && job.filesScanned !== void 0 ? html4`<div><span class="kv-key">${t4("semantic.files")}</span>${t4("semantic.scanned", { count: job.filesScanned })}${job.filesChanged != null ? ` \xB7 ${t4("semantic.changed", { count: job.filesChanged })}` : ""}${job.filesSkipped ? ` \xB7 ${t4("semantic.skipped", { count: job.filesSkipped })}` : ""}</div>` : null}
23689
24790
  ${total > 0 ? html4`
23690
24791
  <div>
23691
- <span class="kv-key">chunks</span>${doneN} / ${total} (${(ratio * 100).toFixed(0)}%)
24792
+ <span class="kv-key">${t4("semantic.chunks")}</span>${t4("semantic.chunksProgress", { done: doneN, total, pct: (ratio * 100).toFixed(0) })}
23692
24793
  </div>
23693
24794
  <div class="bar" style="margin-top: 4px;">
23694
24795
  <div class="fill" style=${`width: ${(ratio * 100).toFixed(1)}%; background: var(--primary);`}></div>
23695
24796
  </div>
23696
24797
  ` : null}
23697
- ${job.error ? html4`<div><span class="kv-key">error</span><span class="err">${job.error}</span></div>` : null}
23698
- ${job.result ? html4`<div><span class="kv-key">result</span>added ${job.result.chunksAdded} · removed ${job.result.chunksRemoved}${job.result.chunksSkipped ? ` \xB7 failed ${job.result.chunksSkipped}` : ""} · ${(job.result.durationMs / 1e3).toFixed(1)}s</div>` : null}
24798
+ ${job.error ? html4`<div><span class="kv-key">${t4("semantic.phaseError")}</span><span class="err">${job.error}</span></div>` : null}
24799
+ ${job.result ? html4`<div><span class="kv-key">${t4("semantic.result")}</span>${t4("semantic.added", { count: job.result.chunksAdded })} · ${t4("semantic.removed", { count: job.result.chunksRemoved })}${job.result.chunksSkipped ? ` \xB7 ${t4("semantic.failed", { count: job.result.chunksSkipped })}` : ""} · ${(job.result.durationMs / 1e3).toFixed(1)}s</div>` : null}
23699
24800
  ${job.result?.skipBuckets ? html4`<${SkipBucketsView} buckets=${job.result.skipBuckets} />` : null}
23700
24801
  </div>
23701
24802
  `;
23702
24803
  }
23703
24804
  function SkipBucketsView({ buckets }) {
24805
+ useLang();
23704
24806
  const order = [
23705
24807
  ["gitignore", "gitignore"],
23706
24808
  ["pattern", "pattern"],
@@ -23714,11 +24816,12 @@ function SkipBucketsView({ buckets }) {
23714
24816
  const total = order.reduce((a3, [k3]) => a3 + (buckets[k3] || 0), 0);
23715
24817
  if (total === 0) return null;
23716
24818
  const parts = order.filter(([k3]) => (buckets[k3] || 0) > 0).map(([k3, label]) => `${label}: ${buckets[k3]}`);
23717
- return html4`<div><span class="kv-key">skipped</span>${total} files <span style="color:var(--fg-3)">(${parts.join(", ")})</span></div>`;
24819
+ return html4`<div><span class="kv-key">${t4("semantic.skipped")}</span>${t4("semantic.skippedFiles", { total, details: parts.join(", ") })}</div>`;
23718
24820
  }
23719
24821
 
23720
24822
  // dashboard/src/panels/sessions.ts
23721
24823
  function SessionsPanel() {
24824
+ useLang();
23722
24825
  const { data, error, loading } = usePoll("/sessions", 5e3);
23723
24826
  const [open, setOpen] = p2(null);
23724
24827
  const [openLoading, setOpenLoading] = p2(false);
@@ -23736,11 +24839,11 @@ function SessionsPanel() {
23736
24839
  }
23737
24840
  }, []);
23738
24841
  if (loading && !data)
23739
- return html4`<div class="card" style="color:var(--fg-3)">loading sessions…</div>`;
23740
- if (error) return html4`<div class="card accent-err">sessions failed: ${error.message}</div>`;
24842
+ return html4`<div class="card" style="color:var(--fg-3)">${t4("sessions.loading")}</div>`;
24843
+ if (error) return html4`<div class="card accent-err">${t4("common.loadingFailed", { name: "sessions", error: error.message })}</div>`;
23741
24844
  const sessions = data?.sessions ?? [];
23742
24845
  if (sessions.length === 0)
23743
- return html4`<div class="card" style="color:var(--fg-3)">No saved sessions yet.</div>`;
24846
+ return html4`<div class="card" style="color:var(--fg-3)">${t4("sessions.noSessions")}</div>`;
23744
24847
  const filtered = filter.trim() ? sessions.filter((s3) => s3.name.toLowerCase().includes(filter.toLowerCase())) : sessions;
23745
24848
  return html4`
23746
24849
  <div class="sessions-grid">
@@ -23748,14 +24851,14 @@ function SessionsPanel() {
23748
24851
  <div class="ssl-h">
23749
24852
  <input
23750
24853
  type="text"
23751
- placeholder="filter sessions"
24854
+ placeholder=${t4("sessions.filterPlaceholder")}
23752
24855
  value=${filter}
23753
24856
  onInput=${(e3) => setFilter(e3.target.value)}
23754
24857
  style="flex:1"
23755
24858
  />
23756
24859
  </div>
23757
24860
  <div class="chips" style="padding:0 12px 8px">
23758
- <span class="chip-f active">all <span class="ct">${sessions.length}</span></span>
24861
+ <span class="chip-f active">${t4("common.all")} <span class="ct">${sessions.length}</span></span>
23759
24862
  </div>
23760
24863
  <div class="ssl-rows">
23761
24864
  ${filtered.map(
@@ -23766,7 +24869,7 @@ function SessionsPanel() {
23766
24869
  >
23767
24870
  <span class="name">${s3.name}</span>
23768
24871
  <span class="meta">
23769
- <span><span class="v">${fmtNum(s3.messageCount)}</span> msgs</span>
24872
+ <span><span class="v">${fmtNum(s3.messageCount)}</span> ${t4("sessions.msgs")}</span>
23770
24873
  <span><span class="v">${fmtBytes(s3.size)}</span></span>
23771
24874
  <span>${fmtRelativeTime(s3.mtime)}</span>
23772
24875
  </span>
@@ -23778,25 +24881,25 @@ function SessionsPanel() {
23778
24881
 
23779
24882
  <div class="sessions-detail">
23780
24883
  ${open == null ? html4`<div style="color:var(--fg-3);font-size:13px;text-align:center;padding:60px 20px">
23781
- Pick a session on the left to read its transcript.
24884
+ ${t4("sessions.pickHint")}
23782
24885
  </div>` : html4`
23783
24886
  <div class="sessions-detail-h">
23784
24887
  <span class="name">${open.name}</span>
23785
24888
  <span class="ws">
23786
- ${open.messages ? `${open.messages.length} message${open.messages.length === 1 ? "" : "s"}` : "loading\u2026"}
24889
+ ${open.messages ? t4("sessions.messages", { count: open.messages.length, s: open.messages.length === 1 ? "" : "s" }) : t4("common.loading")}
23787
24890
  </span>
23788
24891
  <span class="actions">
23789
- <button class="btn ghost" onClick=${() => setOpen(null)}>← back</button>
24892
+ <button class="btn ghost" onClick=${() => setOpen(null)}>${t4("common.back")}</button>
23790
24893
  </span>
23791
24894
  </div>
23792
24895
  <div class="card accent-brand" style="margin-bottom:10px">
23793
- <div class="card-h"><span class="title">Resume in TUI</span></div>
24896
+ <div class="card-h"><span class="title">${t4("sessions.resumeTitle")}</span></div>
23794
24897
  <div class="card-b" style="font-size:12.5px;color:var(--fg-2)">
23795
- Mid-session swap requires a restart so the message log can rewind cleanly. Quit your current session, then run:
24898
+ ${t4("sessions.resumeDesc")}
23796
24899
  <code class="mono" style="display:block;margin-top:8px;padding:8px 10px;background:var(--bg-input);border-radius:var(--r);color:var(--fg-0);font-size:12px;user-select:all">reasonix chat --session ${open.name}</code>
23797
24900
  </div>
23798
24901
  </div>
23799
- ${openLoading ? html4`<div style="color:var(--fg-3)">loading transcript…</div>` : open.error ? html4`<div class="card accent-err">${open.error}</div>` : open.messages && open.messages.length > 0 ? html4`<div class="chat-feed" style="max-height:calc(100vh - 220px);overflow-y:auto">
24902
+ ${openLoading ? html4`<div style="color:var(--fg-3)">${t4("sessions.loadingTranscript")}</div>` : open.error ? html4`<div class="card accent-err">${open.error}</div>` : open.messages && open.messages.length > 0 ? html4`<div class="chat-feed" style="max-height:calc(100vh - 220px);overflow-y:auto">
23800
24903
  ${open.messages.map(
23801
24904
  (m2, i3) => html4`
23802
24905
  <${ChatMessage}
@@ -23811,7 +24914,7 @@ function SessionsPanel() {
23811
24914
  />
23812
24915
  `
23813
24916
  )}
23814
- </div>` : html4`<div style="color:var(--fg-3)">empty transcript.</div>`}
24917
+ </div>` : html4`<div style="color:var(--fg-3)">${t4("sessions.emptyTranscript")}</div>`}
23815
24918
  `}
23816
24919
  </div>
23817
24920
  </div>
@@ -23820,6 +24923,7 @@ function SessionsPanel() {
23820
24923
 
23821
24924
  // dashboard/src/panels/settings.ts
23822
24925
  function SettingsPanel() {
24926
+ useLang();
23823
24927
  const [data, setData] = p2(null);
23824
24928
  const [error, setError] = p2(null);
23825
24929
  const [saving, setSaving] = p2(false);
@@ -23844,7 +24948,7 @@ function SettingsPanel() {
23844
24948
  try {
23845
24949
  await api("/settings", { method: "POST", body: fields });
23846
24950
  await load();
23847
- setSaved(`saved: ${Object.keys(fields).join(", ")}`);
24951
+ setSaved(t4("settings.saved", { fields: Object.keys(fields).join(", ") }));
23848
24952
  setTimeout(() => setSaved(null), 3e3);
23849
24953
  } catch (err) {
23850
24954
  setError(err.message);
@@ -23855,7 +24959,7 @@ function SettingsPanel() {
23855
24959
  [load]
23856
24960
  );
23857
24961
  if (!data && !error)
23858
- return html4`<div class="card" style="color:var(--fg-3)">loading settings…</div>`;
24962
+ return html4`<div class="card" style="color:var(--fg-3)">${t4("settings.loading")}</div>`;
23859
24963
  if (error && !data) return html4`<div class="card accent-err">${error}</div>`;
23860
24964
  if (!data) return null;
23861
24965
  const v3 = data;
@@ -23869,23 +24973,43 @@ function SettingsPanel() {
23869
24973
  ${note ? html4`<span style="color:var(--fg-3);font-size:11px">${note}</span>` : null}
23870
24974
  </div>
23871
24975
  `;
24976
+ const currentLang2 = getLang();
23872
24977
  return html4`
23873
24978
  <div style="max-width:760px;display:flex;flex-direction:column;gap:6px">
23874
24979
  ${saved ? html4`<div><span class="pill ok">${saved}</span></div>` : null}
23875
24980
  ${error ? html4`<div class="card accent-err">${error}</div>` : null}
23876
24981
 
23877
- ${sectionH3("DeepSeek API")}
24982
+ ${sectionH3(t4("settings.sectionLanguage"))}
24983
+ <div class="card">
24984
+ ${fieldRow(
24985
+ t4("settings.language"),
24986
+ html4`
24987
+ <select
24988
+ value=${currentLang2}
24989
+ onChange=${(e3) => {
24990
+ const lang = e3.target.value;
24991
+ setLang(lang);
24992
+ }}
24993
+ >
24994
+ <option value="en">${t4("settings.langEn")}</option>
24995
+ <option value="zh-CN">${t4("settings.langZhCn")}</option>
24996
+ </select>
24997
+ `
24998
+ )}
24999
+ </div>
25000
+
25001
+ ${sectionH3(t4("settings.sectionApi"))}
23878
25002
  <div class="card">
23879
25003
  ${fieldRow(
23880
- "API key",
23881
- html4`<code class="mono" style="color:var(--fg-2);font-size:11.5px">${v3.apiKey ?? "(not set)"}</code>`
25004
+ t4("settings.apiKey"),
25005
+ html4`<code class="mono" style="color:var(--fg-2);font-size:11.5px">${v3.apiKey ?? t4("settings.notSet")}</code>`
23882
25006
  )}
23883
25007
  ${fieldRow(
23884
- "replace",
25008
+ t4("settings.replace"),
23885
25009
  html4`
23886
25010
  <input
23887
25011
  type="password"
23888
- placeholder="paste a fresh sk-… token"
25012
+ placeholder=${t4("settings.pasteKey")}
23889
25013
  value=${draft.apiKey ?? ""}
23890
25014
  onInput=${(e3) => setDraft({ ...draft, apiKey: e3.target.value })}
23891
25015
  style="flex:1"
@@ -23894,16 +25018,16 @@ function SettingsPanel() {
23894
25018
  class="btn primary"
23895
25019
  disabled=${saving || !(draft.apiKey ?? "").trim()}
23896
25020
  onClick=${() => save({ apiKey: draft.apiKey })}
23897
- >Save key</button>
25021
+ >${t4("settings.saveKey")}</button>
23898
25022
  `
23899
25023
  )}
23900
25024
  ${fieldRow(
23901
- "base url",
25025
+ t4("settings.baseUrl"),
23902
25026
  html4`
23903
25027
  <input
23904
25028
  type="text"
23905
25029
  value=${draft.baseUrl ?? v3.baseUrl ?? ""}
23906
- placeholder="https://api.deepseek.com (default)"
25030
+ placeholder=${t4("settings.baseUrlPlaceholder")}
23907
25031
  onInput=${(e3) => setDraft({ ...draft, baseUrl: e3.target.value })}
23908
25032
  style="flex:1"
23909
25033
  />
@@ -23911,65 +25035,65 @@ function SettingsPanel() {
23911
25035
  class="btn"
23912
25036
  disabled=${saving || (draft.baseUrl ?? v3.baseUrl ?? "") === (v3.baseUrl ?? "")}
23913
25037
  onClick=${() => save({ baseUrl: draft.baseUrl })}
23914
- >Save</button>
25038
+ >${t4("common.save")}</button>
23915
25039
  `
23916
25040
  )}
23917
25041
  </div>
23918
25042
 
23919
- ${sectionH3("Defaults")}
25043
+ ${sectionH3(t4("settings.sectionDefaults"))}
23920
25044
  <div class="card">
23921
25045
  ${fieldRow(
23922
- "preset",
25046
+ t4("settings.preset"),
23923
25047
  html4`
23924
25048
  <select
23925
25049
  value=${["auto", "flash", "pro"].includes(v3.preset ?? "") ? v3.preset : "auto"}
23926
25050
  onChange=${(e3) => save({ preset: e3.target.value })}
23927
25051
  disabled=${saving}
23928
25052
  >
23929
- <option value="auto">auto — flash → pro on hard turns</option>
23930
- <option value="flash">flash — always flash, no auto-escalate</option>
23931
- <option value="pro">pro — always pro</option>
25053
+ <option value="auto">${t4("settings.presetAuto")}</option>
25054
+ <option value="flash">${t4("settings.presetFlash")}</option>
25055
+ <option value="pro">${t4("settings.presetPro")}</option>
23932
25056
  </select>
23933
25057
  `,
23934
- "applies next turn"
25058
+ t4("settings.appliesNextTurn")
23935
25059
  )}
23936
25060
  ${fieldRow(
23937
- "effort",
25061
+ t4("settings.effort"),
23938
25062
  html4`
23939
25063
  <select
23940
25064
  value=${v3.reasoningEffort}
23941
25065
  onChange=${(e3) => save({ reasoningEffort: e3.target.value })}
23942
25066
  disabled=${saving}
23943
25067
  >
23944
- <option value="max">max (default — best)</option>
23945
- <option value="high">high (cheaper / faster)</option>
25068
+ <option value="max">${t4("settings.effortMax")}</option>
25069
+ <option value="high">${t4("settings.effortHigh")}</option>
23946
25070
  </select>
23947
25071
  `,
23948
- "applies next turn"
25072
+ t4("settings.appliesNextTurn")
23949
25073
  )}
23950
25074
  ${fieldRow(
23951
- "web search",
25075
+ t4("settings.webSearch"),
23952
25076
  html4`
23953
25077
  <button
23954
25078
  class=${`btn ${v3.search ? "primary" : ""}`}
23955
25079
  onClick=${() => save({ search: !v3.search })}
23956
25080
  disabled=${saving}
23957
- >${v3.search ? "ON" : "off"}</button>
25081
+ >${v3.search ? t4("common.on") : t4("common.off")}</button>
23958
25082
  `,
23959
- "web_fetch + web_search tools"
25083
+ t4("settings.webSearchNote")
23960
25084
  )}
23961
25085
  </div>
23962
25086
 
23963
- ${sectionH3("Runtime")}
25087
+ ${sectionH3(t4("settings.sectionRuntime"))}
23964
25088
  <div class="card">
23965
25089
  ${fieldRow(
23966
- "active model",
25090
+ t4("settings.activeModel"),
23967
25091
  html4`<code class="mono">${v3.model ?? "\u2014"}</code>`
23968
25092
  )}
23969
25093
  ${fieldRow(
23970
- "edit mode",
25094
+ t4("settings.editMode"),
23971
25095
  html4`<code class="mono">${v3.editMode}</code>`,
23972
- "switch from the Chat tab header"
25096
+ t4("settings.editModeNote")
23973
25097
  )}
23974
25098
  </div>
23975
25099
  </div>
@@ -23978,6 +25102,7 @@ function SettingsPanel() {
23978
25102
 
23979
25103
  // dashboard/src/panels/skills.ts
23980
25104
  function SkillsPanel() {
25105
+ useLang();
23981
25106
  const [data, setData] = p2(null);
23982
25107
  const [error, setError] = p2(null);
23983
25108
  const [open, setOpen] = p2(null);
@@ -24021,7 +25146,7 @@ function SkillsPanel() {
24021
25146
  method: "POST",
24022
25147
  body: { body }
24023
25148
  });
24024
- setInfo(`saved ${open.scope}/${open.name}`);
25149
+ setInfo(t4("skills.saved", { scope: open.scope, name: open.name }));
24025
25150
  setTimeout(() => setInfo(null), 3e3);
24026
25151
  await load();
24027
25152
  } catch (err) {
@@ -24032,7 +25157,7 @@ function SkillsPanel() {
24032
25157
  }, [open, body, load]);
24033
25158
  const remove = x2(async () => {
24034
25159
  if (!open) return;
24035
- if (!confirm(`Delete skill ${open.scope}/${open.name}?`)) return;
25160
+ if (!confirm(t4("skills.deleteConfirm", { scope: open.scope, name: open.name }))) return;
24036
25161
  setBusy(true);
24037
25162
  try {
24038
25163
  await api(`/skills/${open.scope}/${encodeURIComponent(open.name)}`, { method: "DELETE" });
@@ -24070,7 +25195,7 @@ description: TODO \u2014 one-line description that helps the model match this sk
24070
25195
  }
24071
25196
  }, [newName, newScope, load, openSkill]);
24072
25197
  if (!data && !error)
24073
- return html4`<div class="card" style="color:var(--fg-3)">loading skills…</div>`;
25198
+ return html4`<div class="card" style="color:var(--fg-3)">${t4("skills.loading")}</div>`;
24074
25199
  if (error && !data) return html4`<div class="card accent-err">${error}</div>`;
24075
25200
  if (!data) return null;
24076
25201
  const allWith = [
@@ -24087,17 +25212,17 @@ description: TODO \u2014 one-line description that helps the model match this sk
24087
25212
  <div class="ssl-h">
24088
25213
  <input
24089
25214
  type="text"
24090
- placeholder="filter skills"
25215
+ placeholder=${t4("skills.filterPlaceholder")}
24091
25216
  value=${filter}
24092
25217
  onInput=${(e3) => setFilter(e3.target.value)}
24093
25218
  style="flex:1"
24094
25219
  />
24095
25220
  </div>
24096
25221
  <div class="chips" style="padding:0 12px 8px">
24097
- <span class="chip-f active">all <span class="ct">${allWith.length}</span></span>
24098
- <span class="chip-f">project <span class="ct">${data.project.length}</span></span>
24099
- <span class="chip-f">global <span class="ct">${data.global.length}</span></span>
24100
- <span class="chip-f">builtin <span class="ct">${data.builtin.length}</span></span>
25222
+ <span class="chip-f active">${t4("common.all")} <span class="ct">${allWith.length}</span></span>
25223
+ <span class="chip-f">${t4("skills.project")} <span class="ct">${data.project.length}</span></span>
25224
+ <span class="chip-f">${t4("skills.global")} <span class="ct">${data.global.length}</span></span>
25225
+ <span class="chip-f">${t4("skills.builtin")} <span class="ct">${data.builtin.length}</span></span>
24101
25226
  </div>
24102
25227
 
24103
25228
  <div style="padding:0 12px 8px;display:flex;gap:6px;flex-wrap:wrap">
@@ -24106,12 +25231,12 @@ description: TODO \u2014 one-line description that helps the model match this sk
24106
25231
  onChange=${(e3) => setNewScope(e3.target.value)}
24107
25232
  style="flex:0 0 auto;font-size:11.5px;padding:5px 6px"
24108
25233
  >
24109
- <option value="global">global</option>
24110
- ${data.paths.project ? html4`<option value="project">project</option>` : null}
25234
+ <option value="global">${t4("skills.global")}</option>
25235
+ ${data.paths.project ? html4`<option value="project">${t4("skills.project")}</option>` : null}
24111
25236
  </select>
24112
25237
  <input
24113
25238
  type="text"
24114
- placeholder="new skill"
25239
+ placeholder=${t4("skills.newSkill")}
24115
25240
  value=${newName}
24116
25241
  onInput=${(e3) => setNewName(e3.target.value)}
24117
25242
  style="flex:1;min-width:0"
@@ -24129,11 +25254,11 @@ description: TODO \u2014 one-line description that helps the model match this sk
24129
25254
  >
24130
25255
  <span class="name">
24131
25256
  ${s3.name}
24132
- ${s3.scope === "builtin" ? html4`<span class="pill">builtin</span>` : null}
25257
+ ${s3.scope === "builtin" ? html4`<span class="pill">${t4("skills.builtin")}</span>` : null}
24133
25258
  </span>
24134
- <span class="preview">${s3.description ?? "(no description)"}</span>
25259
+ <span class="preview">${s3.description ?? t4("skills.noDescription")}</span>
24135
25260
  <span class="meta">
24136
- ${typeof s3.runs7d === "number" && s3.runs7d > 0 ? html4`<span><span class="v">${s3.runs7d}</span> runs · 7d</span>` : null}
25261
+ ${typeof s3.runs7d === "number" && s3.runs7d > 0 ? html4`<span><span class="v">${s3.runs7d}</span> ${t4("skills.runs7d")}</span>` : null}
24137
25262
  <span class="dim">${s3.scope}</span>
24138
25263
  </span>
24139
25264
  </div>
@@ -24144,22 +25269,22 @@ description: TODO \u2014 one-line description that helps the model match this sk
24144
25269
 
24145
25270
  <div class="sessions-detail">
24146
25271
  ${open == null ? html4`<div style="color:var(--fg-3);font-size:13px;text-align:center;padding:60px 20px">
24147
- Pick a skill on the left, or create a new one above.
25272
+ ${t4("skills.pickHint")}
24148
25273
  </div>` : open.scope === "builtin" ? (() => {
24149
25274
  const builtin = data.builtin.find((b2) => b2.name === open.name);
24150
25275
  return html4`
24151
25276
  <div class="sessions-detail-h">
24152
25277
  <span class="name">${open.scope}/${open.name}</span>
24153
- <span class="ws"><span class="pill">read-only · builtin</span></span>
25278
+ <span class="ws"><span class="pill">${t4("skills.readOnlyBuiltin")}</span></span>
24154
25279
  <span class="actions">
24155
- <button class="btn ghost" onClick=${() => setOpen(null)}>← back</button>
25280
+ <button class="btn ghost" onClick=${() => setOpen(null)}>${t4("common.back")}</button>
24156
25281
  </span>
24157
25282
  </div>
24158
25283
  <div style="color:var(--fg-2);font-size:13px;line-height:1.6">
24159
- ${builtin?.description ?? "(no description)"}
25284
+ ${builtin?.description ?? t4("skills.noDescription")}
24160
25285
  </div>
24161
25286
  <div style="margin-top:14px;color:var(--fg-3);font-size:11.5px">
24162
- Built-in skills ship with Reasonix; the model picks them up automatically. To customize, create a project- or global-scoped skill with the same name.
25287
+ ${t4("skills.builtinDesc")}
24163
25288
  </div>
24164
25289
  `;
24165
25290
  })() : html4`
@@ -24167,10 +25292,10 @@ description: TODO \u2014 one-line description that helps the model match this sk
24167
25292
  <span class="name">${open.scope}/${open.name}</span>
24168
25293
  <span class="ws">${body.length.toLocaleString()} chars</span>
24169
25294
  <span class="actions">
24170
- <button class="btn primary" disabled=${busy} onClick=${save}>Save</button>
25295
+ <button class="btn primary" disabled=${busy} onClick=${save}>${t4("common.save")}</button>
24171
25296
  <button class="btn" disabled=${busy} onClick=${remove}
24172
- style="border-color:var(--c-err);color:var(--c-err)">Delete</button>
24173
- <button class="btn ghost" onClick=${() => setOpen(null)}>← back</button>
25297
+ style="border-color:var(--c-err);color:var(--c-err)">${t4("common.delete")}</button>
25298
+ <button class="btn ghost" onClick=${() => setOpen(null)}>${t4("common.back")}</button>
24174
25299
  </span>
24175
25300
  </div>
24176
25301
  ${info ? html4`<div style="margin-bottom:8px"><span class="pill ok">${info}</span></div>` : null}
@@ -24182,7 +25307,7 @@ description: TODO \u2014 one-line description that helps the model match this sk
24182
25307
  disabled=${busy}
24183
25308
  ></textarea>
24184
25309
  <div style="margin-top:8px;color:var(--fg-3);font-size:11.5px">
24185
- re-loaded on next <code class="mono">/new</code> or session restart
25310
+ ${t4("skills.reloadHint")}
24186
25311
  </div>
24187
25312
  `}
24188
25313
  </div>
@@ -24192,76 +25317,77 @@ description: TODO \u2014 one-line description that helps the model match this sk
24192
25317
 
24193
25318
  // dashboard/src/panels/system.ts
24194
25319
  function SystemPanel() {
25320
+ useLang();
24195
25321
  const { data, error, loading } = usePoll("/health", 5e3);
24196
25322
  if (loading && !data)
24197
- return html4`<div class="card" style="color:var(--fg-3)">loading health…</div>`;
24198
- if (error) return html4`<div class="card accent-err">health failed: ${error.message}</div>`;
25323
+ return html4`<div class="card" style="color:var(--fg-3)">${t4("system.loading")}</div>`;
25324
+ if (error) return html4`<div class="card accent-err">${t4("common.loadingFailed", { name: "health", error: error.message })}</div>`;
24199
25325
  if (!data) return null;
24200
25326
  const h3 = data;
24201
25327
  const upToDate = h3.latestVersion ? h3.latestVersion === h3.version : null;
24202
25328
  return html4`
24203
25329
  <div style="display:flex;flex-direction:column;gap:14px">
24204
- <h3 style="margin:0;font-family:var(--font-mono);font-size:11px;color:var(--fg-3);text-transform:uppercase;letter-spacing:.1em">Health checks</h3>
25330
+ <h3 style="margin:0;font-family:var(--font-mono);font-size:11px;color:var(--fg-3);text-transform:uppercase;letter-spacing:.1em">${t4("system.healthChecks")}</h3>
24205
25331
  <div class="health-grid">
24206
25332
  <div class=${`health-item ${upToDate === false ? "warn" : ""}`}>
24207
25333
  <div class="lbl">
24208
- version
24209
- ${upToDate === null ? html4`<span class="pill">checking</span>` : upToDate ? html4`<span class="pill ok">● latest</span>` : html4`<span class="pill warn">● out of date</span>`}
25334
+ ${t4("system.version")}
25335
+ ${upToDate === null ? html4`<span class="pill">${t4("system.checking")}</span>` : upToDate ? html4`<span class="pill ok">${t4("system.latest")}</span>` : html4`<span class="pill warn">${t4("system.outOfDate")}</span>`}
24210
25336
  </div>
24211
25337
  <div class="v">${h3.version}</div>
24212
- <div class="meta">${upToDate === null ? "version check pending" : upToDate ? "up to date" : `latest: ${h3.latestVersion}`}</div>
25338
+ <div class="meta">${upToDate === null ? t4("system.versionPending") : upToDate ? t4("system.upToDate") : t4("system.latestVer", { version: h3.latestVersion ?? "" })}</div>
24213
25339
  </div>
24214
25340
 
24215
25341
  <div class="health-item">
24216
- <div class="lbl">sessions <span class="pill ok">● ok</span></div>
25342
+ <div class="lbl">${t4("system.sessions")} <span class="pill ok">${t4("system.ok")}</span></div>
24217
25343
  <div class="v">${fmtBytes(h3.sessions.totalBytes)}</div>
24218
- <div class="meta">${fmtNum(h3.sessions.count)} files</div>
25344
+ <div class="meta">${fmtNum(h3.sessions.count)} ${t4("system.files")}</div>
24219
25345
  </div>
24220
25346
 
24221
25347
  <div class="health-item">
24222
- <div class="lbl">memory <span class="pill ok">● ok</span></div>
25348
+ <div class="lbl">${t4("system.memory")} <span class="pill ok">${t4("system.ok")}</span></div>
24223
25349
  <div class="v">${fmtBytes(h3.memory.totalBytes)}</div>
24224
- <div class="meta">${fmtNum(h3.memory.fileCount)} files</div>
25350
+ <div class="meta">${fmtNum(h3.memory.fileCount)} ${t4("system.files")}</div>
24225
25351
  </div>
24226
25352
 
24227
25353
  <div class="health-item">
24228
25354
  <div class="lbl">
24229
- semantic index
24230
- ${h3.semantic.exists ? html4`<span class="pill ok">● built</span>` : html4`<span class="pill">— none</span>`}
25355
+ ${t4("system.semanticIndex")}
25356
+ ${h3.semantic.exists ? html4`<span class="pill ok">${t4("system.built")}</span>` : html4`<span class="pill">${t4("system.none")}</span>`}
24231
25357
  </div>
24232
25358
  <div class="v">${h3.semantic.exists ? fmtBytes(h3.semantic.totalBytes) : "\u2014"}</div>
24233
25359
  <div class="meta">
24234
- ${h3.semantic.exists ? `${fmtNum(h3.semantic.fileCount)} files` : "run reasonix index to build"}
25360
+ ${h3.semantic.exists ? `${fmtNum(h3.semantic.fileCount)} ${t4("system.files")}` : t4("system.runIndex")}
24235
25361
  </div>
24236
25362
  </div>
24237
25363
 
24238
25364
  <div class="health-item">
24239
- <div class="lbl">usage log <span class="pill ok">● ok</span></div>
25365
+ <div class="lbl">${t4("system.usageLog")} <span class="pill ok">${t4("system.ok")}</span></div>
24240
25366
  <div class="v">${fmtBytes(h3.usageLog.bytes)}</div>
24241
25367
  <div class="meta">~/.reasonix/usage.jsonl</div>
24242
25368
  </div>
24243
25369
 
24244
25370
  <div class="health-item">
24245
25371
  <div class="lbl">
24246
- background jobs
24247
- ${h3.jobs === null ? html4`<span class="pill">— no session</span>` : html4`<span class="pill ok">● ${fmtNum(h3.jobs)}</span>`}
25372
+ ${t4("system.backgroundJobs")}
25373
+ ${h3.jobs === null ? html4`<span class="pill">${t4("system.noSession")}</span>` : html4`<span class="pill ok">● ${fmtNum(h3.jobs)}</span>`}
24248
25374
  </div>
24249
- <div class="v">${h3.jobs === null ? "\u2014" : `${fmtNum(h3.jobs)} running`}</div>
24250
- <div class="meta">${h3.jobs === null ? "attach a session to see jobs" : "shell + spawn"}</div>
25375
+ <div class="v">${h3.jobs === null ? "\u2014" : t4("system.running", { count: fmtNum(h3.jobs) })}</div>
25376
+ <div class="meta">${h3.jobs === null ? t4("system.attachHint") : t4("system.shellSpawn")}</div>
24251
25377
  </div>
24252
25378
  </div>
24253
25379
 
24254
25380
  <div class="card" style="padding:0">
24255
25381
  <div class="card-h" style="padding:12px 14px 6px">
24256
- <span class="title">paths</span>
25382
+ <span class="title">${t4("system.paths")}</span>
24257
25383
  </div>
24258
25384
  <table class="tbl">
24259
25385
  <tbody style="font-size:11.5px">
24260
- <tr><td class="dim" style="padding:5px 14px">home</td><td class="path">${h3.reasonixHome}</td></tr>
24261
- <tr><td class="dim" style="padding:5px 14px">sessions</td><td class="path">${h3.sessions.path}</td></tr>
24262
- <tr><td class="dim" style="padding:5px 14px">memory</td><td class="path">${h3.memory.path}</td></tr>
24263
- <tr><td class="dim" style="padding:5px 14px">semantic</td><td class="path">${h3.semantic.path}</td></tr>
24264
- <tr><td class="dim" style="padding:5px 14px">usage</td><td class="path">${h3.usageLog.path}</td></tr>
25386
+ <tr><td class="dim" style="padding:5px 14px">${t4("system.home")}</td><td class="path">${h3.reasonixHome}</td></tr>
25387
+ <tr><td class="dim" style="padding:5px 14px">${t4("system.sessionsPath")}</td><td class="path">${h3.sessions.path}</td></tr>
25388
+ <tr><td class="dim" style="padding:5px 14px">${t4("system.memoryPath")}</td><td class="path">${h3.memory.path}</td></tr>
25389
+ <tr><td class="dim" style="padding:5px 14px">${t4("system.semanticPath")}</td><td class="path">${h3.semantic.path}</td></tr>
25390
+ <tr><td class="dim" style="padding:5px 14px">${t4("system.usagePath")}</td><td class="path">${h3.usageLog.path}</td></tr>
24265
25391
  </tbody>
24266
25392
  </table>
24267
25393
  </div>
@@ -24271,41 +25397,42 @@ function SystemPanel() {
24271
25397
 
24272
25398
  // dashboard/src/panels/tools.ts
24273
25399
  function ToolsPanel() {
25400
+ useLang();
24274
25401
  const { data, error, loading } = usePoll("/tools", 4e3);
24275
25402
  if (loading && !data)
24276
- return html4`<div class="card" style="color:var(--fg-3)">loading tools…</div>`;
25403
+ return html4`<div class="card" style="color:var(--fg-3)">${t4("tools.loading")}</div>`;
24277
25404
  const e3 = error;
24278
25405
  if (e3?.status === 503) {
24279
- return html4`<div class="card accent-warn">${e3.body?.error ?? "live tools view requires an attached session"}</div>`;
25406
+ return html4`<div class="card accent-warn">${e3.body?.error ?? t4("common.loadingFailed", { name: "tools", error: "" })}</div>`;
24280
25407
  }
24281
- if (e3) return html4`<div class="card accent-err">tools failed: ${e3.message}</div>`;
25408
+ if (e3) return html4`<div class="card accent-err">${t4("common.loadingFailed", { name: "tools", error: e3.message })}</div>`;
24282
25409
  if (!data) return null;
24283
- const t4 = data;
25410
+ const d3 = data;
24284
25411
  return html4`
24285
25412
  <div style="display:flex;flex-direction:column;gap:14px">
24286
25413
  <div class="chips">
24287
- <span class="chip-f active">all <span class="ct">${t4.total}</span></span>
24288
- ${t4.planMode ? html4`<span class="chip-f" style="border-color:var(--c-warn);color:var(--c-warn)">plan mode — writes gated</span>` : null}
25414
+ <span class="chip-f active">${t4("common.all")} <span class="ct">${d3.total}</span></span>
25415
+ ${d3.planMode ? html4`<span class="chip-f" style="border-color:var(--c-warn);color:var(--c-warn)">${t4("tools.planMode")}</span>` : null}
24289
25416
  </div>
24290
25417
 
24291
- ${t4.tools.length === 0 ? html4`<div class="card" style="color:var(--fg-3)">No tools registered.</div>` : html4`
25418
+ ${d3.tools.length === 0 ? html4`<div class="card" style="color:var(--fg-3)">${t4("tools.noTools")}</div>` : html4`
24292
25419
  <div class="card" style="padding:0;overflow:hidden">
24293
25420
  <table class="tbl">
24294
25421
  <thead>
24295
25422
  <tr>
24296
- <th>tool</th>
24297
- <th>flags</th>
24298
- <th>description</th>
25423
+ <th>${t4("tools.colTool")}</th>
25424
+ <th>${t4("tools.colFlags")}</th>
25425
+ <th>${t4("tools.colDesc")}</th>
24299
25426
  </tr>
24300
25427
  </thead>
24301
25428
  <tbody>
24302
- ${t4.tools.map(
25429
+ ${d3.tools.map(
24303
25430
  (tool) => html4`
24304
25431
  <tr>
24305
25432
  <td><code class="mono">${tool.name}</code></td>
24306
25433
  <td>
24307
- ${tool.readOnly ? html4`<span class="pill ok">read-only</span>` : html4`<span class="pill acc">write</span>`}
24308
- ${tool.flattened ? html4` <span class="pill">flat</span>` : null}
25434
+ ${tool.readOnly ? html4`<span class="pill ok">${t4("tools.readOnly")}</span>` : html4`<span class="pill acc">${t4("tools.write")}</span>`}
25435
+ ${tool.flattened ? html4` <span class="pill">${t4("tools.flat")}</span>` : null}
24309
25436
  </td>
24310
25437
  <td class="dim">${tool.description ?? ""}</td>
24311
25438
  </tr>
@@ -24406,6 +25533,7 @@ function UsageChart({ days: days2 }) {
24406
25533
  return html4`<div ref=${containerRef} style="width: 100%; min-height: 280px;"></div>`;
24407
25534
  }
24408
25535
  function UsagePanel() {
25536
+ useLang();
24409
25537
  const { data: summary, error, loading } = usePoll("/usage", 5e3);
24410
25538
  const [series, setSeries] = p2(null);
24411
25539
  _2(() => {
@@ -24417,7 +25545,7 @@ function UsagePanel() {
24417
25545
  } catch {
24418
25546
  }
24419
25547
  })();
24420
- const t4 = setInterval(async () => {
25548
+ const interval = setInterval(async () => {
24421
25549
  try {
24422
25550
  const s3 = await api("/usage/series");
24423
25551
  if (!cancelled) setSeries(s3.days ?? []);
@@ -24426,12 +25554,12 @@ function UsagePanel() {
24426
25554
  }, 3e4);
24427
25555
  return () => {
24428
25556
  cancelled = true;
24429
- clearInterval(t4);
25557
+ clearInterval(interval);
24430
25558
  };
24431
25559
  }, []);
24432
25560
  if (loading && !summary)
24433
- return html4`<div class="card" style="color:var(--fg-3)">loading usage…</div>`;
24434
- if (error) return html4`<div class="card accent-err">usage failed: ${error.message}</div>`;
25561
+ return html4`<div class="card" style="color:var(--fg-3)">${t4("usage.loading")}</div>`;
25562
+ if (error) return html4`<div class="card accent-err">${t4("common.loadingFailed", { name: "usage", error: error.message })}</div>`;
24435
25563
  if (!summary) return null;
24436
25564
  const u3 = summary;
24437
25565
  const sectionH3 = (text) => html4`
@@ -24440,36 +25568,35 @@ function UsagePanel() {
24440
25568
  return html4`
24441
25569
  <div style="display:flex;flex-direction:column;gap:6px">
24442
25570
  <div class="chips">
24443
- <span class="chip-f active">${u3.recordCount.toLocaleString()} records</span>
25571
+ <span class="chip-f active">${t4("usage.records", { count: u3.recordCount.toLocaleString() })}</span>
24444
25572
  <span class="chip-f">${u3.logSize}</span>
24445
25573
  </div>
24446
25574
 
24447
25575
  ${series && series.length > 0 ? html4`
24448
25576
  <div class="card" style="padding:18px">
24449
25577
  <div class="card-h">
24450
- <span class="title">Daily usage</span>
24451
- <span class="meta">cost · cache saved · turns</span>
25578
+ <span class="title">${t4("usage.dailyUsage")}</span>
25579
+ <span class="meta">${t4("usage.dailyMeta")}</span>
24452
25580
  </div>
24453
25581
  <${UsageChart} days=${series} />
24454
25582
  </div>
24455
25583
  ` : null}
24456
25584
 
24457
25585
  ${u3.recordCount === 0 ? html4`<div class="card" style="color:var(--fg-3);margin-top:8px">
24458
- No usage data yet — run a turn in <code class="mono">reasonix chat</code> /
24459
- <code class="mono">code</code> / <code class="mono">run</code> and refresh.
25586
+ ${t4("usage.noData")}
24460
25587
  </div>` : html4`
24461
- ${sectionH3("Rolling windows")}
25588
+ ${sectionH3(t4("usage.windows"))}
24462
25589
  <div class="card" style="padding:0;overflow:hidden">
24463
25590
  <table class="tbl">
24464
25591
  <thead>
24465
25592
  <tr>
24466
25593
  <th></th>
24467
- <th>turns</th>
24468
- <th>cache hit</th>
24469
- <th>cost (USD)</th>
24470
- <th>cache saved</th>
24471
- <th>vs Claude</th>
24472
- <th>saved</th>
25594
+ <th>${t4("usage.colTurns")}</th>
25595
+ <th>${t4("usage.colCacheHit")}</th>
25596
+ <th>${t4("usage.colCost")}</th>
25597
+ <th>${t4("usage.colCacheSaved")}</th>
25598
+ <th>${t4("usage.colVsClaude")}</th>
25599
+ <th>${t4("usage.colSaved")}</th>
24473
25600
  </tr>
24474
25601
  </thead>
24475
25602
  <tbody>
@@ -24494,13 +25621,13 @@ function UsagePanel() {
24494
25621
  `}
24495
25622
 
24496
25623
  ${u3.byModel.length > 0 ? html4`
24497
- ${sectionH3("Most used models")}
25624
+ ${sectionH3(t4("usage.mostUsed"))}
24498
25625
  <div class="card" style="padding:0;overflow:hidden">
24499
25626
  <table class="tbl">
24500
25627
  <thead>
24501
25628
  <tr>
24502
- <th>model</th>
24503
- <th>turns</th>
25629
+ <th>${t4("usage.colModel")}</th>
25630
+ <th>${t4("usage.colTurns")}</th>
24504
25631
  </tr>
24505
25632
  </thead>
24506
25633
  <tbody>
@@ -24522,40 +25649,51 @@ function UsagePanel() {
24522
25649
 
24523
25650
  // dashboard/app.js
24524
25651
  var html5 = htm_module_default.bind(_);
24525
- var TAB_SECTIONS = [
24526
- {
24527
- label: "workspace",
24528
- tabs: [
24529
- { id: "chat", name: "Chat", glyph: "\u25C6", panel: () => html5`<${ChatPanel} />` },
24530
- { id: "plans", name: "Plans", glyph: "\u229E", panel: () => html5`<${PlansPanel} />` },
24531
- { id: "sessions", name: "Sessions", glyph: "\u203A", panel: () => html5`<${SessionsPanel} />` }
24532
- ]
24533
- },
24534
- {
24535
- label: "observe",
24536
- tabs: [
24537
- { id: "overview", name: "Overview", glyph: "\u25C8", panel: () => html5`<${OverviewPanel} />` },
24538
- { id: "usage", name: "Usage", glyph: "$", panel: () => html5`<${UsagePanel} />` },
24539
- { id: "health", name: "System", glyph: "+", panel: () => html5`<${SystemPanel} />` },
24540
- { id: "semantic", name: "Semantic", glyph: "\u2248", panel: () => html5`<${SemanticPanel} />` }
24541
- ]
24542
- },
24543
- {
24544
- label: "configure",
24545
- tabs: [
24546
- { id: "tools", name: "Tools", glyph: "\u25A3", panel: () => html5`<${ToolsPanel} />` },
24547
- { id: "permissions", name: "Permissions", glyph: "\u258E", panel: () => html5`<${PermissionsPanel} />` },
24548
- { id: "mcp", name: "MCP", glyph: "M", panel: () => html5`<${McpPanel} />` },
24549
- { id: "skills", name: "Skills", glyph: "S", panel: () => html5`<${SkillsPanel} />` },
24550
- { id: "memory", name: "Memory", glyph: "\xB7", panel: () => html5`<${MemoryPanel} />` },
24551
- { id: "hooks", name: "Hooks", glyph: "H", panel: () => html5`<${HooksPanel} />` },
24552
- { id: "settings", name: "Settings", glyph: "\u2318", panel: () => html5`<${SettingsPanel} />` }
24553
- ]
24554
- }
24555
- ];
24556
- var ALL_TABS = TAB_SECTIONS.flatMap((s3) => s3.tabs);
25652
+ function tabSections() {
25653
+ return [
25654
+ {
25655
+ label: t4("app.sectionWorkspace"),
25656
+ tabs: [
25657
+ { id: "chat", name: t4("app.tabChat"), glyph: "\u25C6", panel: () => html5`<${ChatPanel} />` },
25658
+ { id: "plans", name: t4("app.tabPlans"), glyph: "\u229E", panel: () => html5`<${PlansPanel} />` },
25659
+ { id: "sessions", name: t4("app.tabSessions"), glyph: "\u203A", panel: () => html5`<${SessionsPanel} />` }
25660
+ ]
25661
+ },
25662
+ {
25663
+ label: t4("app.sectionObserve"),
25664
+ tabs: [
25665
+ { id: "overview", name: t4("app.tabOverview"), glyph: "\u25C8", panel: () => html5`<${OverviewPanel} />` },
25666
+ { id: "usage", name: t4("app.tabUsage"), glyph: "$", panel: () => html5`<${UsagePanel} />` },
25667
+ { id: "health", name: t4("app.tabSystem"), glyph: "+", panel: () => html5`<${SystemPanel} />` },
25668
+ { id: "semantic", name: t4("app.tabSemantic"), glyph: "\u2248", panel: () => html5`<${SemanticPanel} />` }
25669
+ ]
25670
+ },
25671
+ {
25672
+ label: t4("app.sectionConfigure"),
25673
+ tabs: [
25674
+ { id: "tools", name: t4("app.tabTools"), glyph: "\u25A3", panel: () => html5`<${ToolsPanel} />` },
25675
+ { id: "permissions", name: t4("app.tabPermissions"), glyph: "\u258E", panel: () => html5`<${PermissionsPanel} />` },
25676
+ { id: "mcp", name: t4("app.tabMcp"), glyph: "M", panel: () => html5`<${McpPanel} />` },
25677
+ { id: "skills", name: t4("app.tabSkills"), glyph: "S", panel: () => html5`<${SkillsPanel} />` },
25678
+ { id: "memory", name: t4("app.tabMemory"), glyph: "\xB7", panel: () => html5`<${MemoryPanel} />` },
25679
+ { id: "hooks", name: t4("app.tabHooks"), glyph: "H", panel: () => html5`<${HooksPanel} />` },
25680
+ { id: "settings", name: t4("app.tabSettings"), glyph: "\u2318", panel: () => html5`<${SettingsPanel} />` }
25681
+ ]
25682
+ }
25683
+ ];
25684
+ }
24557
25685
  function App() {
24558
- const [activeId, setActiveId] = p2("chat");
25686
+ useLang();
25687
+ _2(() => {
25688
+ initLangFromServer();
25689
+ }, []);
25690
+ const [activeId, setActiveId] = p2(() => {
25691
+ try {
25692
+ return localStorage.getItem("rx.activeTab") ?? "chat";
25693
+ } catch {
25694
+ return "chat";
25695
+ }
25696
+ });
24559
25697
  const [sidebarCollapsed, setSidebarCollapsed] = p2(() => {
24560
25698
  try {
24561
25699
  return localStorage.getItem("rx.sidebarCollapsed") === "1";
@@ -24569,7 +25707,18 @@ function App() {
24569
25707
  } catch {
24570
25708
  }
24571
25709
  }, [sidebarCollapsed]);
24572
- const active = ALL_TABS.find((t4) => t4.id === activeId) ?? ALL_TABS[0];
25710
+ _2(() => {
25711
+ try {
25712
+ localStorage.setItem("rx.activeTab", activeId);
25713
+ } catch {
25714
+ }
25715
+ }, [activeId]);
25716
+ const TAB_SECTIONS = tabSections();
25717
+ const ALL_TABS = TAB_SECTIONS.flatMap((s3) => s3.tabs);
25718
+ const active = ALL_TABS.find((t5) => t5.id === activeId) ?? ALL_TABS[0];
25719
+ _2(() => {
25720
+ if (active.id !== activeId) setActiveId(active.id);
25721
+ }, [active.id, activeId]);
24573
25722
  _2(() => {
24574
25723
  const onNav = (ev) => {
24575
25724
  const id = ev.detail?.tabId;
@@ -24628,7 +25777,7 @@ function App() {
24628
25777
  </div>
24629
25778
  <footer class="app-status">
24630
25779
  <span class="grow"></span>
24631
- <span class="item">127.0.0.1 only · token-gated</span>
25780
+ <span class="item">${t4("app.footer")}</span>
24632
25781
  </footer>
24633
25782
  </div>
24634
25783
  <${ToastStack} />