|
10 | 10 |
|
11 | 11 | var DATA_BASE = '/data/benchmarks'; |
12 | 12 |
|
| 13 | + var I18N = (typeof window !== 'undefined' && window.__BENCH_I18N__) || {}; |
| 14 | + |
13 | 15 | // No ecosystem list to maintain: the display name is derived from the id in |
14 | 16 | // the data, and every ecosystem uses the same dot color (for now). New |
15 | 17 | // ecosystems appear automatically — nothing to edit here. |
|
93 | 95 | } |
94 | 96 |
|
95 | 97 | function showEmpty() { |
96 | | - if (elSidebar) { elSidebar.textContent = ''; var d = document.createElement('div'); d.className = 'bench-sidebar-loading'; d.textContent = 'No data'; elSidebar.appendChild(d); } |
| 98 | + if (elSidebar) { elSidebar.textContent = ''; var d = document.createElement('div'); d.className = 'bench-sidebar-loading'; d.textContent = (I18N.noData || 'No data'); elSidebar.appendChild(d); } |
97 | 99 | if (elEmpty) elEmpty.hidden = false; |
98 | 100 | if (elContent) elContent.hidden = true; |
99 | 101 | } |
|
141 | 143 | var links = document.createElement('div'); |
142 | 144 | links.className = 'bench-vlinks'; |
143 | 145 | var aggSamples = (vdata.aggregate && (vdata.aggregate.sample_size_on + vdata.aggregate.sample_size_off)) || vdata.sample_size || 0; |
144 | | - links.appendChild(makeLink(v, 'all', 'All', null, aggSamples)); |
| 146 | + links.appendChild(makeLink(v, 'all', (I18N.all || 'All'), null, aggSamples)); |
145 | 147 | (vdata.ecosystems || []).forEach(function (e) { |
146 | 148 | var m = ecoMeta(e.ecosystem); |
147 | 149 | links.appendChild(makeLink(v, e.ecosystem, m.name, m.color, (e.sample_size_on || 0) + (e.sample_size_off || 0))); |
|
159 | 161 | var og = document.createElement('optgroup'); |
160 | 162 | og.label = 'v' + v; |
161 | 163 | var oAll = document.createElement('option'); |
162 | | - oAll.value = v + '/all'; oAll.textContent = 'All ecosystems'; |
| 164 | + oAll.value = v + '/all'; oAll.textContent = (I18N.allEco || 'All ecosystems'); |
163 | 165 | og.appendChild(oAll); |
164 | 166 | (vdata.ecosystems || []).forEach(function (e) { |
165 | 167 | var o = document.createElement('option'); |
|
235 | 237 | var vdata = state.versions[cur.version]; |
236 | 238 | var m = metricFor(cur); |
237 | 239 | var isAll = cur.scope === 'all'; |
238 | | - var ecoName = isAll ? 'all ecosystems' : ecoMeta(cur.scope).name; |
| 240 | + var ecoName = isAll ? (I18N.allEco || 'all ecosystems') : ecoMeta(cur.scope).name; |
239 | 241 |
|
240 | 242 | elTitle.textContent = 'RTK v' + cur.version + ' — ' + ecoName; |
241 | 243 | var sessions = isAll |
242 | 244 | ? (vdata.sample_size || ((m.sample_size_on || 0) + (m.sample_size_off || 0))) |
243 | 245 | : ((m.sample_size_on || 0) + (m.sample_size_off || 0)); |
244 | | - elSubtitle.textContent = |
245 | | - sessions + ' sessions (ON ' + (m.sample_size_on || 0) + ' / OFF ' + (m.sample_size_off || 0) + ')' + |
246 | | - ' · pass rate ' + fmtRate(m.pass_rate.on) + ' ON / ' + fmtRate(m.pass_rate.off) + ' OFF'; |
| 246 | + elSubtitle.textContent = (I18N.subtitle || '{n} sessions (ON {on} / OFF {off}) · pass rate {ron} ON / {roff} OFF') |
| 247 | + .replace('{n}', sessions).replace('{on}', (m.sample_size_on || 0)).replace('{off}', (m.sample_size_off || 0)) |
| 248 | + .replace('{ron}', fmtRate(m.pass_rate.on)).replace('{roff}', fmtRate(m.pass_rate.off)); |
247 | 249 |
|
248 | 250 | renderPdf(cur, vdata, m); |
249 | 251 | renderData(cur, vdata); |
|
259 | 261 | if (cur.scope === 'all') { |
260 | 262 | // Only the combined report here — per-ecosystem PDFs live in their own section. |
261 | 263 | if (vdata.pdf_combined_public_url) |
262 | | - links.push({ url: vdata.pdf_combined_public_url, label: 'All ecosystems (combined)' }); |
| 264 | + links.push({ url: vdata.pdf_combined_public_url, label: (I18N.allCombined || 'All ecosystems (combined)') }); |
263 | 265 | } else if (m.pdf_public_url) { |
264 | | - links.push({ url: m.pdf_public_url, label: ecoMeta(cur.scope).name + ' report (PDF)' }); |
| 266 | + links.push({ url: m.pdf_public_url, label: (I18N.reportPdf || '{name} report (PDF)').replace('{name}', ecoMeta(cur.scope).name) }); |
265 | 267 | } |
266 | 268 | if (!links.length) { |
267 | 269 | var span = document.createElement('span'); |
268 | 270 | span.className = 'bench-content-sub'; |
269 | | - span.textContent = 'No PDF report available.'; |
| 271 | + span.textContent = (I18N.noPdf || 'No PDF report available.'); |
270 | 272 | elPdf.appendChild(span); |
271 | 273 | return; |
272 | 274 | } |
|
313 | 315 | if (isAll) { |
314 | 316 | // "All" view: totals derived for display only (mean × sample size) — the |
315 | 317 | // import is untouched; savings % stays the aggregate figure from the data. |
316 | | - bashFoot = 'ON ' + fmtBytes(b.on_mean * nOn) + ' vs OFF ' + fmtBytes(b.off_mean * nOff) + ' total'; |
317 | | - costFoot = 'ON ' + fmtUsdTotal(c.on_mean_usd * nOn) + ' vs OFF ' + fmtUsdTotal(c.off_mean_usd * nOff) + ' total'; |
| 318 | + bashFoot = (I18N.footTotal || 'ON {on} vs OFF {off} total').replace('{on}', fmtBytes(b.on_mean * nOn)).replace('{off}', fmtBytes(b.off_mean * nOff)); |
| 319 | + costFoot = (I18N.footTotal || 'ON {on} vs OFF {off} total').replace('{on}', fmtUsdTotal(c.on_mean_usd * nOn)).replace('{off}', fmtUsdTotal(c.off_mean_usd * nOff)); |
318 | 320 | } else { |
319 | | - bashFoot = 'ON ' + fmtBytes(b.on_mean) + ' vs OFF ' + fmtBytes(b.off_mean) + ' avg/run'; |
320 | | - costFoot = 'ON ' + fmtUsd(c.on_mean_usd) + ' vs OFF ' + fmtUsd(c.off_mean_usd) + ' avg/run'; |
| 321 | + bashFoot = (I18N.footAvg || 'ON {on} vs OFF {off} avg/run').replace('{on}', fmtBytes(b.on_mean)).replace('{off}', fmtBytes(b.off_mean)); |
| 322 | + costFoot = (I18N.footAvg || 'ON {on} vs OFF {off} avg/run').replace('{on}', fmtUsd(c.on_mean_usd)).replace('{off}', fmtUsd(c.off_mean_usd)); |
321 | 323 | } |
322 | | - elKpis.appendChild(kpiCard('Bash output savings', fmtPct(b.savings_pct), sig(b.p_value) ? signClass(b.savings_pct) : 'neutral', bashFoot, sig(b.p_value))); |
323 | | - elKpis.appendChild(kpiCard('Cost savings', fmtPct(c.savings_pct), sig(c.p_value) ? signClass(c.savings_pct) : 'neutral', costFoot, sig(c.p_value))); |
| 324 | + elKpis.appendChild(kpiCard((I18N.bashSavings || 'Bash output savings'), fmtPct(b.savings_pct), sig(b.p_value) ? signClass(b.savings_pct) : 'neutral', bashFoot, sig(b.p_value))); |
| 325 | + elKpis.appendChild(kpiCard((I18N.costSavings || 'Cost savings'), fmtPct(c.savings_pct), sig(c.p_value) ? signClass(c.savings_pct) : 'neutral', costFoot, sig(c.p_value))); |
324 | 326 | } |
325 | 327 |
|
326 | 328 | // "Savings chain": RTK's direct, big win on Bash output cascades downstream |
|
340 | 342 | note.textContent = ''; |
341 | 343 | var strong = function (t) { var s = document.createElement('strong'); s.textContent = t; return s; }; |
342 | 344 | var txt = function (t) { return document.createTextNode(t); }; |
343 | | - note.appendChild(txt('RTK cuts ')); |
344 | | - note.appendChild(strong(absPct(bashSav))); |
345 | | - note.appendChild(txt(' of bash output bytes, which cascades into ')); |
346 | | - note.appendChild(strong(absPct(tokSav))); |
347 | | - note.appendChild(txt(' fewer input tokens for an average of ')); |
348 | | - note.appendChild(strong(absPct(costSav))); |
349 | | - note.appendChild(txt(' lower cost.')); |
| 345 | + var noteTmpl = I18N.cuts || 'RTK cuts {bash} of bash output bytes, which cascades into {tok} fewer input tokens for an average of {cost} lower cost.'; |
| 346 | + noteTmpl.split(/(\{bash\}|\{tok\}|\{cost\})/).forEach(function (part) { |
| 347 | + if (part === '{bash}') note.appendChild(strong(absPct(bashSav))); |
| 348 | + else if (part === '{tok}') note.appendChild(strong(absPct(tokSav))); |
| 349 | + else if (part === '{cost}') note.appendChild(strong(absPct(costSav))); |
| 350 | + else if (part) note.appendChild(txt(part)); |
| 351 | + }); |
350 | 352 | } |
351 | 353 |
|
352 | 354 |
|
|
356 | 358 | host.textContent = ''; |
357 | 359 |
|
358 | 360 | var steps = [ |
359 | | - { label: 'Bash output', sav: bashSav, off: fmtBytes(bash.off_mean), on: fmtBytes(bash.on_mean), tag: 'RTK OSS surface', hero: true }, |
360 | | - { label: 'Input tokens', sav: tokSav, off: fmtTok(tok.off_mean), on: fmtTok(tok.on_mean), tag: 'sent to the model' }, |
361 | | - { label: 'Cost (USD)', sav: costSav, off: fmtUsd(cost.off_mean_usd), on: fmtUsd(cost.on_mean_usd), tag: 'the bottom line' } |
| 361 | + { label: (I18N.bashOutput || 'Bash output'), sav: bashSav, off: fmtBytes(bash.off_mean), on: fmtBytes(bash.on_mean), tag: (I18N.tagSurface || 'RTK OSS surface'), hero: true }, |
| 362 | + { label: (I18N.inputTokens || 'Input tokens'), sav: tokSav, off: fmtTok(tok.off_mean), on: fmtTok(tok.on_mean), tag: (I18N.tagModel || 'sent to the model') }, |
| 363 | + { label: (I18N.costUsd || 'Cost (USD)'), sav: costSav, off: fmtUsd(cost.off_mean_usd), on: fmtUsd(cost.on_mean_usd), tag: (I18N.tagBottom || 'the bottom line') } |
362 | 364 | ]; |
363 | | - var connectors = ['part of input tokens', 'billed per token and additional to Output tokens']; |
| 365 | + var connectors = [(I18N.connInput || 'part of input tokens'), (I18N.connBilled || 'billed per token and additional to Output tokens')]; |
364 | 366 | var maxAbs = Math.max(Math.abs(bashSav), Math.abs(tokSav), Math.abs(costSav), 1); |
365 | 367 |
|
366 | 368 | var mk = function (cls, t) { var e = document.createElement('div'); e.className = cls; if (t != null) e.textContent = t; return e; }; |
|
419 | 421 | var isSig = sig(pVal); |
420 | 422 | if (savPct > 0) return isSig ? '<td class="win-on">✓ RTK</td>' : '<td class="muted">~ RTK</td>'; |
421 | 423 | if (savPct < 0) return isSig ? '<td class="win-off">✗ OFF</td>' : '<td class="muted">~ OFF</td>'; |
422 | | - return '<td>Tie</td>'; |
| 424 | + return '<td>' + esc(I18N.tie || 'Tie') + '</td>'; |
423 | 425 | } |
424 | 426 |
|
425 | 427 | function metricRows(m) { |
426 | 428 | var rows = ''; |
427 | 429 | // Cost |
428 | 430 | rows += '<tr class="metric-row">' + |
429 | | - '<td>Cost (USD)</td>' + |
| 431 | + '<td>' + esc(I18N.costUsd || 'Cost (USD)') + '</td>' + |
430 | 432 | '<td class="num">' + fmtUsd(m.cost.off_mean_usd || 0) + '</td>' + |
431 | 433 | '<td class="num">' + fmtUsd(m.cost.on_mean_usd || 0) + '</td>' + |
432 | 434 | cellPct(m.cost.savings_pct, m.cost.p_value) + |
433 | 435 | '<td class="num muted">' + (m.cost.p_value !== undefined ? Number(m.cost.p_value).toFixed(3) : '—') + '</td>' + |
434 | 436 | winner(m.cost.savings_pct, m.cost.p_value) + '</tr>'; |
435 | 437 | // Tokens |
436 | 438 | rows += '<tr class="metric-row">' + |
437 | | - '<td>Input tokens</td>' + |
| 439 | + '<td>' + esc(I18N.inputTokens || 'Input tokens') + '</td>' + |
438 | 440 | '<td class="num">' + fmtTok(m.tokens.off_mean) + '</td>' + |
439 | 441 | '<td class="num">' + fmtTok(m.tokens.on_mean) + '</td>' + |
440 | 442 | cellPct(m.tokens.savings_pct, m.tokens.p_value) + |
441 | 443 | '<td class="num muted">' + (m.tokens.p_value !== undefined ? Number(m.tokens.p_value).toFixed(3) : '—') + '</td>' + |
442 | 444 | winner(m.tokens.savings_pct, m.tokens.p_value) + '</tr>'; |
443 | 445 | // Bash bytes |
444 | 446 | rows += '<tr class="metric-row">' + |
445 | | - '<td>Bash output bytes <span class="muted">(Primary KPI)</span></td>' + |
| 447 | + '<td>' + esc(I18N.mBash || 'Bash output bytes') + ' <span class="muted">' + esc(I18N.mPrimary || '(Primary KPI)') + '</span></td>' + |
446 | 448 | '<td class="num">' + fmtBytes(m.bash_bytes.off_mean) + '</td>' + |
447 | 449 | '<td class="num">' + fmtBytes(m.bash_bytes.on_mean) + '</td>' + |
448 | 450 | cellPct(m.bash_bytes.savings_pct, m.bash_bytes.p_value) + |
449 | 451 | '<td class="num muted">' + (m.bash_bytes.p_value !== undefined ? Number(m.bash_bytes.p_value).toFixed(3) : '—') + '</td>' + |
450 | 452 | winner(m.bash_bytes.savings_pct, m.bash_bytes.p_value) + '</tr>'; |
451 | 453 | // API calls |
452 | 454 | rows += '<tr class="metric-row">' + |
453 | | - '<td>API calls <span class="muted">(Behavioral)</span></td>' + |
| 455 | + '<td>' + esc(I18N.mApi || 'API calls') + ' <span class="muted">' + esc(I18N.mBehavioral || '(Behavioral)') + '</span></td>' + |
454 | 456 | '<td class="num">' + Number(m.api_calls.off_mean).toFixed(1) + '</td>' + |
455 | 457 | '<td class="num">' + Number(m.api_calls.on_mean).toFixed(1) + '</td>' + |
456 | 458 | '<td class="num muted">—</td>' + |
|
459 | 461 | // Duration |
460 | 462 | if (m.duration_ms) { |
461 | 463 | rows += '<tr class="metric-row">' + |
462 | | - '<td>Duration</td>' + |
| 464 | + '<td>' + esc(I18N.mDuration || 'Duration') + '</td>' + |
463 | 465 | '<td class="num">' + fmtDur(m.duration_ms.off_mean) + '</td>' + |
464 | 466 | '<td class="num">' + fmtDur(m.duration_ms.on_mean) + '</td>' + |
465 | 467 | '<td class="num muted">—</td>' + |
|
468 | 470 | } |
469 | 471 | // Pass rate |
470 | 472 | rows += '<tr class="metric-row">' + |
471 | | - '<td>Pass rate</td>' + |
| 473 | + '<td>' + esc(I18N.mPassrate || 'Pass rate') + '</td>' + |
472 | 474 | '<td class="num">' + fmtRate(m.pass_rate.off) + '</td>' + |
473 | 475 | '<td class="num">' + fmtRate(m.pass_rate.on) + '</td>' + |
474 | 476 | '<td class="num muted">—</td>' + |
|
483 | 485 | } |
484 | 486 |
|
485 | 487 | var TABLE_HEAD = '<thead><tr>' + |
486 | | - '<th>Metric</th><th>OFF Mean</th><th>ON Mean</th>' + |
487 | | - '<th>Savings %</th><th>p-value</th><th>Winner</th>' + |
| 488 | + '<th>' + esc(I18N.thMetric || 'Metric') + '</th><th>' + esc(I18N.thOff || 'OFF Mean') + '</th><th>' + esc(I18N.thOn || 'ON Mean') + '</th>' + |
| 489 | + '<th>' + esc(I18N.thSavings || 'Savings %') + '</th><th>' + esc(I18N.thPvalue || 'p-value') + '</th><th>' + esc(I18N.thWinner || 'Winner') + '</th>' + |
488 | 490 | '</tr></thead>'; |
489 | 491 |
|
490 | 492 | function renderTable(cur, vdata) { |
491 | 493 | var body = ''; |
492 | 494 | if (cur.scope === 'all') { |
493 | 495 | // One section per ecosystem + aggregate |
494 | | - var sections = [{ label: '<strong>All ecosystems</strong>', m: vdata.aggregate }]; |
| 496 | + var sections = [{ label: '<strong>' + esc(I18N.allEco || 'All ecosystems') + '</strong>', m: vdata.aggregate }]; |
495 | 497 | (vdata.ecosystems || []).forEach(function (e) { |
496 | 498 | sections.push({ label: ecoCellHtml(e.ecosystem), m: e }); |
497 | 499 | }); |
|
0 commit comments