Skip to content

Commit b2ccb31

Browse files
authored
Merge pull request #20 from rtk-ai/fix/lang-switcher
fix(lang): switcher & benchmark translate
2 parents 87659cf + bb5d183 commit b2ccb31

8 files changed

Lines changed: 640 additions & 517 deletions

File tree

public/scripts/benchmarks.js

Lines changed: 39 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
var DATA_BASE = '/data/benchmarks';
1212

13+
var I18N = (typeof window !== 'undefined' && window.__BENCH_I18N__) || {};
14+
1315
// No ecosystem list to maintain: the display name is derived from the id in
1416
// the data, and every ecosystem uses the same dot color (for now). New
1517
// ecosystems appear automatically — nothing to edit here.
@@ -93,7 +95,7 @@
9395
}
9496

9597
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); }
9799
if (elEmpty) elEmpty.hidden = false;
98100
if (elContent) elContent.hidden = true;
99101
}
@@ -141,7 +143,7 @@
141143
var links = document.createElement('div');
142144
links.className = 'bench-vlinks';
143145
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));
145147
(vdata.ecosystems || []).forEach(function (e) {
146148
var m = ecoMeta(e.ecosystem);
147149
links.appendChild(makeLink(v, e.ecosystem, m.name, m.color, (e.sample_size_on || 0) + (e.sample_size_off || 0)));
@@ -159,7 +161,7 @@
159161
var og = document.createElement('optgroup');
160162
og.label = 'v' + v;
161163
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');
163165
og.appendChild(oAll);
164166
(vdata.ecosystems || []).forEach(function (e) {
165167
var o = document.createElement('option');
@@ -235,15 +237,15 @@
235237
var vdata = state.versions[cur.version];
236238
var m = metricFor(cur);
237239
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;
239241

240242
elTitle.textContent = 'RTK v' + cur.version + ' — ' + ecoName;
241243
var sessions = isAll
242244
? (vdata.sample_size || ((m.sample_size_on || 0) + (m.sample_size_off || 0)))
243245
: ((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));
247249

248250
renderPdf(cur, vdata, m);
249251
renderData(cur, vdata);
@@ -259,14 +261,14 @@
259261
if (cur.scope === 'all') {
260262
// Only the combined report here — per-ecosystem PDFs live in their own section.
261263
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)') });
263265
} 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) });
265267
}
266268
if (!links.length) {
267269
var span = document.createElement('span');
268270
span.className = 'bench-content-sub';
269-
span.textContent = 'No PDF report available.';
271+
span.textContent = (I18N.noPdf || 'No PDF report available.');
270272
elPdf.appendChild(span);
271273
return;
272274
}
@@ -313,14 +315,14 @@
313315
if (isAll) {
314316
// "All" view: totals derived for display only (mean × sample size) — the
315317
// 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));
318320
} 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));
321323
}
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)));
324326
}
325327

326328
// "Savings chain": RTK's direct, big win on Bash output cascades downstream
@@ -340,13 +342,13 @@
340342
note.textContent = '';
341343
var strong = function (t) { var s = document.createElement('strong'); s.textContent = t; return s; };
342344
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+
});
350352
}
351353

352354

@@ -356,11 +358,11 @@
356358
host.textContent = '';
357359

358360
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') }
362364
];
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')];
364366
var maxAbs = Math.max(Math.abs(bashSav), Math.abs(tokSav), Math.abs(costSav), 1);
365367

366368
var mk = function (cls, t) { var e = document.createElement('div'); e.className = cls; if (t != null) e.textContent = t; return e; };
@@ -419,38 +421,38 @@
419421
var isSig = sig(pVal);
420422
if (savPct > 0) return isSig ? '<td class="win-on">✓ RTK</td>' : '<td class="muted">~ RTK</td>';
421423
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>';
423425
}
424426

425427
function metricRows(m) {
426428
var rows = '';
427429
// Cost
428430
rows += '<tr class="metric-row">' +
429-
'<td>Cost (USD)</td>' +
431+
'<td>' + esc(I18N.costUsd || 'Cost (USD)') + '</td>' +
430432
'<td class="num">' + fmtUsd(m.cost.off_mean_usd || 0) + '</td>' +
431433
'<td class="num">' + fmtUsd(m.cost.on_mean_usd || 0) + '</td>' +
432434
cellPct(m.cost.savings_pct, m.cost.p_value) +
433435
'<td class="num muted">' + (m.cost.p_value !== undefined ? Number(m.cost.p_value).toFixed(3) : '—') + '</td>' +
434436
winner(m.cost.savings_pct, m.cost.p_value) + '</tr>';
435437
// Tokens
436438
rows += '<tr class="metric-row">' +
437-
'<td>Input tokens</td>' +
439+
'<td>' + esc(I18N.inputTokens || 'Input tokens') + '</td>' +
438440
'<td class="num">' + fmtTok(m.tokens.off_mean) + '</td>' +
439441
'<td class="num">' + fmtTok(m.tokens.on_mean) + '</td>' +
440442
cellPct(m.tokens.savings_pct, m.tokens.p_value) +
441443
'<td class="num muted">' + (m.tokens.p_value !== undefined ? Number(m.tokens.p_value).toFixed(3) : '—') + '</td>' +
442444
winner(m.tokens.savings_pct, m.tokens.p_value) + '</tr>';
443445
// Bash bytes
444446
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>' +
446448
'<td class="num">' + fmtBytes(m.bash_bytes.off_mean) + '</td>' +
447449
'<td class="num">' + fmtBytes(m.bash_bytes.on_mean) + '</td>' +
448450
cellPct(m.bash_bytes.savings_pct, m.bash_bytes.p_value) +
449451
'<td class="num muted">' + (m.bash_bytes.p_value !== undefined ? Number(m.bash_bytes.p_value).toFixed(3) : '—') + '</td>' +
450452
winner(m.bash_bytes.savings_pct, m.bash_bytes.p_value) + '</tr>';
451453
// API calls
452454
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>' +
454456
'<td class="num">' + Number(m.api_calls.off_mean).toFixed(1) + '</td>' +
455457
'<td class="num">' + Number(m.api_calls.on_mean).toFixed(1) + '</td>' +
456458
'<td class="num muted">—</td>' +
@@ -459,7 +461,7 @@
459461
// Duration
460462
if (m.duration_ms) {
461463
rows += '<tr class="metric-row">' +
462-
'<td>Duration</td>' +
464+
'<td>' + esc(I18N.mDuration || 'Duration') + '</td>' +
463465
'<td class="num">' + fmtDur(m.duration_ms.off_mean) + '</td>' +
464466
'<td class="num">' + fmtDur(m.duration_ms.on_mean) + '</td>' +
465467
'<td class="num muted">—</td>' +
@@ -468,7 +470,7 @@
468470
}
469471
// Pass rate
470472
rows += '<tr class="metric-row">' +
471-
'<td>Pass rate</td>' +
473+
'<td>' + esc(I18N.mPassrate || 'Pass rate') + '</td>' +
472474
'<td class="num">' + fmtRate(m.pass_rate.off) + '</td>' +
473475
'<td class="num">' + fmtRate(m.pass_rate.on) + '</td>' +
474476
'<td class="num muted">—</td>' +
@@ -483,15 +485,15 @@
483485
}
484486

485487
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>' +
488490
'</tr></thead>';
489491

490492
function renderTable(cur, vdata) {
491493
var body = '';
492494
if (cur.scope === 'all') {
493495
// 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 }];
495497
(vdata.ecosystems || []).forEach(function (e) {
496498
sections.push({ label: ecoCellHtml(e.ecosystem), m: e });
497499
});

src/components/global/SiteHeader.astro

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,28 @@ function stripLocalePrefix(path: string): string {
2020
}
2121
2222
const currentPath = stripLocalePrefix(Astro.url.pathname)
23+
24+
const homeHref = getRelativeLocaleUrl(currentLocale, '/')
25+
const benchHref = getRelativeLocaleUrl(currentLocale, '/benchmarks/')
2326
---
2427

2528
<nav class="nav" id="main-nav" aria-label={t('aria.main_nav', lang)}>
2629
<div class="nav-inner">
2730

2831
<!-- Logo -->
29-
<a href="/" class="nav-logo">
32+
<a href={homeHref} class="nav-logo">
3033
<img src="/brand/logo.png" alt="rtk logo" width="28" height="28" style="border-radius:var(--radius-sm);">
3134
<span>rtk</span>
3235
</a>
3336

3437
<!-- Desktop nav links -->
3538
<div class="nav-links" id="nav-links">
36-
<a href="/#problem" class="nav-link" data-i18n="nav.why">Why</a>
37-
<a href="/#demo" class="nav-link" data-i18n="nav.demo">Demo</a>
38-
<a href="/#install" class="nav-link" data-i18n="nav.install">Install</a>
39+
<a href={`${homeHref}#problem`} class="nav-link" data-i18n="nav.why">Why</a>
40+
<a href={`${homeHref}#demo`} class="nav-link" data-i18n="nav.demo">Demo</a>
41+
<a href={`${homeHref}#install`} class="nav-link" data-i18n="nav.install">Install</a>
3942
<span class="nav-divider" aria-hidden="true"></span>
4043
<a href="/guide/" class="nav-link">Docs</a>
41-
<a href="/benchmarks/" class="nav-link">Benchmarks</a>
44+
<a href={benchHref} class="nav-link">Benchmarks</a>
4245
<span class="nav-divider" aria-hidden="true"></span>
4346
<a href="https://ko-fi.com/patrickszymkowiak" class="nav-icon-btn" target="_blank" rel="noopener" title="Support on Ko-fi" aria-label="Support on Ko-fi">
4447
<IconKofi size={16} />
@@ -57,12 +60,14 @@ const currentPath = stripLocalePrefix(Astro.url.pathname)
5760
</button>
5861
<span class="nav-divider" aria-hidden="true"></span>
5962
<div class="lang-switch">
60-
<button class="lang-btn active" data-lang="en">EN</button>
61-
<button class="lang-btn" data-lang="fr">FR</button>
62-
<button class="lang-btn" data-lang="es">ES</button>
63-
<button class="lang-btn" data-lang="de">DE</button>
64-
<button class="lang-btn" data-lang="zh">ZH</button>
65-
<button class="lang-btn" data-lang="ja">JA</button>
63+
{LOCALES.map(l => (
64+
<a
65+
href={getRelativeLocaleUrl(l, currentPath)}
66+
class={`lang-btn${l === currentLocale ? ' active' : ''}`}
67+
hreflang={l}
68+
aria-current={l === currentLocale ? 'true' : undefined}
69+
>{l.toUpperCase()}</a>
70+
))}
6671
</div>
6772
</div>
6873

@@ -101,11 +106,11 @@ const currentPath = stripLocalePrefix(Astro.url.pathname)
101106
<div class="nav-drawer" id="nav-drawer" aria-hidden="true">
102107
<div class="nav-drawer-inner">
103108
<div class="nav-drawer-links">
104-
<a href="/#problem" data-i18n="nav.why">Why</a>
105-
<a href="/#demo" data-i18n="nav.demo">Demo</a>
106-
<a href="/#install" data-i18n="nav.install">Install</a>
109+
<a href={`${homeHref}#problem`} data-i18n="nav.why">Why</a>
110+
<a href={`${homeHref}#demo`} data-i18n="nav.demo">Demo</a>
111+
<a href={`${homeHref}#install`} data-i18n="nav.install">Install</a>
107112
<a href="/guide/">Docs</a>
108-
<a href="/benchmarks/">Benchmarks</a>
113+
<a href={benchHref}>Benchmarks</a>
109114
</div>
110115
<div class="nav-drawer-actions">
111116
<a href="https://ko-fi.com/patrickszymkowiak" class="nav-drawer-action" target="_blank" rel="noopener"><IconKofi size={17} /><span>{t('ui.nav_support', lang)}</span></a>

src/components/landing/Install.astro

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,19 @@ const personas = [
1212
{
1313
id: 'claude-code',
1414
tabKey: 'install.tab.cc',
15-
initCmd: 'rtk init --claude-code',
15+
initCmd: 'rtk init --global',
1616
noteKey: 'install.note.cc',
1717
},
1818
{
1919
id: 'cursor',
2020
tabKey: 'install.tab.cursor',
21-
initCmd: 'rtk init --cursor',
21+
initCmd: 'rtk init --global --agent cursor',
2222
noteKey: 'install.note.cursor',
2323
},
2424
{
2525
id: 'other',
2626
tabKey: 'install.tab.other',
27-
initCmd: 'rtk init --global',
27+
initCmd: 'rtk init --global --agent <name>',
2828
noteKey: 'install.note.other',
2929
},
3030
]
@@ -72,14 +72,14 @@ const personas = [
7272
<h3>{t('install.brew', lang)}</h3>
7373
<p>{t('install.brew_desc', lang)}</p>
7474
<div class="code-block">
75-
<code>brew install rtk</code>
76-
<button class="copy-btn" data-copy="brew install rtk" aria-label={t('aria.copy', lang)}>
75+
<code>brew install rtk-ai/tap/rtk</code>
76+
<button class="copy-btn" data-copy="brew install rtk-ai/tap/rtk" aria-label={t('aria.copy', lang)}>
7777
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
7878
</button>
7979
</div>
8080
<div class="code-block">
81-
<code>brew upgrade rtk</code>
82-
<button class="copy-btn" data-copy="brew upgrade rtk" aria-label={t('aria.copy', lang)}>
81+
<code>brew upgrade rtk-ai/tap/rtk</code>
82+
<button class="copy-btn" data-copy="brew upgrade rtk-ai/tap/rtk" aria-label={t('aria.copy', lang)}>
8383
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
8484
</button>
8585
</div>
@@ -122,7 +122,7 @@ const personas = [
122122
</div>
123123
<div class="install-step">
124124
<span class="install-step-num">2</span>
125-
<code id="install-step-2-cmd">rtk init --claude-code</code>
125+
<code id="install-step-2-cmd">rtk init --global</code>
126126
</div>
127127
<div class="install-step">
128128
<span class="install-step-num">3</span>

0 commit comments

Comments
 (0)