Skip to content

Commit 20936ae

Browse files
feat: TH/EN language toggle
Web: top-right toggle, remembers choice, defaults to browser language. Scoring lib: English axis descriptions + class taglines. CLI: prints class tagline in both languages. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent b0b793a commit 20936ae

3 files changed

Lines changed: 146 additions & 35 deletions

File tree

bin/cli.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ async function main() {
213213
lines.push(' CLAUDE STATUS CHECK');
214214
lines.push(' ' + '-'.repeat(46));
215215
lines.push(' ' + cls[0] + ' LV ' + lv);
216+
lines.push(' ' + (cls[2] || cls[1]));
216217
lines.push(' ' + cls[1]);
217218
lines.push(' ' + '-'.repeat(46));
218219
for (const axis of scoring.AXES) {

index.html

Lines changed: 133 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,14 @@
114114

115115
.card-actions { display: flex; gap: 10px; justify-content: center; margin-top: 18px; flex-wrap: wrap; }
116116

117+
.lang-toggle {
118+
position: fixed; top: 16px; right: 18px; z-index: 60;
119+
font-family: 'JetBrains Mono', monospace; font-size: 12px; font-weight: 700; letter-spacing: 1px;
120+
color: #4ade80; background: #0f1d18; border: 1px solid #1f4435; border-radius: 8px;
121+
padding: 6px 12px; cursor: pointer;
122+
}
123+
.lang-toggle:hover { border-color: #34d399; }
124+
117125
/* ---------- toast ---------- */
118126
.toast {
119127
position: fixed; left: 50%; bottom: 28px; transform: translateX(-50%) translateY(8px);
@@ -140,32 +148,34 @@
140148
</head>
141149
<body>
142150

151+
<button class="lang-toggle" id="langBtn" type="button">EN</button>
152+
143153
<div id="landing" class="landing hidden">
144154
<div class="hero">
145155
<div class="kicker">MMORPG Character Sheet · For Claude Users</div>
146156
<h1>Claude Status Check</h1>
147-
<p>คุณใช้ Claude สายไหน? เช็คสเตตัส 6 ด้าน รับ class + level ของตัวเอง — ข้อมูลดิบไม่ออกจากเครื่องคุณ</p>
157+
<p id="heroDesc">คุณใช้ Claude สายไหน? เช็คสเตตัส 6 ด้าน รับ class + level ของตัวเอง — ข้อมูลดิบไม่ออกจากเครื่องคุณ</p>
148158
</div>
149159
<div class="entry-grid">
150160
<div class="entry">
151-
<h2>สาย Claude Code</h2>
152-
<div class="sub">รันคำสั่งเดียวใน terminal — สแกน transcript ในเครื่อง แล้วได้ลิงก์การ์ดทันที</div>
161+
<h2 id="entry1Title">สาย Claude Code</h2>
162+
<div class="sub" id="entry1Sub">รันคำสั่งเดียวใน terminal — สแกน transcript ในเครื่อง แล้วได้ลิงก์การ์ดทันที</div>
153163
<div class="cmd-row">
154164
<div class="cmd" id="npxCmd">npx github:MankhongGarden/claude-stats</div>
155165
<button class="btn" id="copyCmdBtn" type="button">คัดลอก</button>
156166
</div>
157-
<div class="privacy"><b>Privacy:</b> สแกนในเครื่องเท่านั้น · ลิงก์แชร์มีแค่ตัวเลขสรุป 6 ค่า</div>
167+
<div class="privacy"><b>Privacy:</b> <span id="privacy1Text">สแกนในเครื่องเท่านั้น · ลิงก์แชร์มีแค่ตัวเลขสรุป 6 ค่า</span></div>
158168
</div>
159169
<div class="entry">
160-
<h2>สาย claude.ai <span class="badge">BETA</span></h2>
161-
<div class="sub">ใช้ไฟล์ export จาก claude.ai — วิเคราะห์ในเบราว์เซอร์นี้ทั้งหมด</div>
170+
<h2><span id="entry2Title">สาย claude.ai</span> <span class="badge">BETA</span></h2>
171+
<div class="sub" id="entry2Sub">ใช้ไฟล์ export จาก claude.ai — วิเคราะห์ในเบราว์เซอร์นี้ทั้งหมด</div>
162172
<div class="drop" id="dropZone">
163-
<div class="big">วางไฟล์ conversations.json ที่นี่</div>
164-
<div class="hint">หรือคลิกเพื่อเลือกไฟล์</div>
173+
<div class="big" id="dropBig">วางไฟล์ conversations.json ที่นี่</div>
174+
<div class="hint" id="dropHint">หรือคลิกเพื่อเลือกไฟล์</div>
165175
</div>
166176
<input type="file" id="fileInput" accept=".json,application/json" class="hidden">
167-
<div class="steps">ขอไฟล์: Settings → Privacy → Export data → ไฟล์จะส่งมาทางอีเมล แตก zip แล้วเลือก <code>conversations.json</code></div>
168-
<div class="privacy"><b>Privacy:</b> ไฟล์ถูกอ่านในเครื่องเท่านั้น ไม่มีการอัปโหลด</div>
177+
<div class="steps" id="steps">ขอไฟล์: Settings → Privacy → Export data → ไฟล์จะส่งมาทางอีเมล แตก zip แล้วเลือก <code>conversations.json</code></div>
178+
<div class="privacy"><b>Privacy:</b> <span id="privacy2Text">ไฟล์ถูกอ่านในเครื่องเท่านั้น ไม่มีการอัปโหลด</span></div>
169179
</div>
170180
</div>
171181
<div class="demo-row">
@@ -213,6 +223,97 @@ <h2>สาย claude.ai <span class="badge">BETA</span></h2>
213223
var current = null;
214224
var toastTimer = null;
215225

226+
/* ---------- i18n ---------- */
227+
228+
var STR = {
229+
th: {
230+
heroDesc: 'คุณใช้ Claude สายไหน? เช็คสเตตัส 6 ด้าน รับ class + level ของตัวเอง — ข้อมูลดิบไม่ออกจากเครื่องคุณ',
231+
entry1Title: 'สาย Claude Code',
232+
entry1Sub: 'รันคำสั่งเดียวใน terminal — สแกน transcript ในเครื่อง แล้วได้ลิงก์การ์ดทันที',
233+
privacy1Text: 'สแกนในเครื่องเท่านั้น · ลิงก์แชร์มีแค่ตัวเลขสรุป 6 ค่า',
234+
entry2Title: 'สาย claude.ai',
235+
entry2Sub: 'ใช้ไฟล์ export จาก claude.ai — วิเคราะห์ในเบราว์เซอร์นี้ทั้งหมด',
236+
dropBig: 'วางไฟล์ conversations.json ที่นี่',
237+
dropHint: 'หรือคลิกเพื่อเลือกไฟล์',
238+
steps: 'ขอไฟล์: Settings → Privacy → Export data → ไฟล์จะส่งมาทางอีเมล แตก zip แล้วเลือก ',
239+
privacy2Text: 'ไฟล์ถูกอ่านในเครื่องเท่านั้น ไม่มีการอัปโหลด',
240+
demoLink: 'ดูการ์ดตัวอย่างก่อน →',
241+
copyCmdBtn: 'คัดลอก',
242+
pngBtn: 'ดาวน์โหลดการ์ด PNG',
243+
copyLinkBtn: 'คัดลอกลิงก์แชร์',
244+
resetBtn: 'เริ่มใหม่',
245+
badLink: 'ลิงก์ไม่ถูกต้องหรือเสียหาย — กลับสู่หน้าแรก',
246+
copiedCmd: 'คัดลอกคำสั่งแล้ว — ไปวางใน terminal ได้เลย',
247+
copiedLink: 'คัดลอกลิงก์แล้ว — ในลิงก์มีแค่ตัวเลขสรุป 6 ค่า',
248+
copyFail: 'คัดลอกไม่สำเร็จ — copy เองได้เลย: ',
249+
readFail: 'อ่านไฟล์ไม่สำเร็จ ลองใหม่อีกครั้ง',
250+
badFormat: 'รูปแบบไฟล์ไม่ตรงที่คาด (parser ยังเป็น beta)',
251+
issueLink: 'แจ้งปัญหาบน GitHub',
252+
cdnFail: 'โหลดตัวสร้างรูปไม่สำเร็จ (CDN) ลอง refresh',
253+
pngDone: 'ดาวน์โหลดการ์ดแล้ว',
254+
pngFail: 'สร้างรูปไม่สำเร็จ ลองใหม่อีกครั้ง',
255+
scoringFail: 'โหลด lib/scoring.js ไม่สำเร็จ'
256+
},
257+
en: {
258+
heroDesc: 'Which Claude build are you? Check your 6 stats and get your class + level — raw data never leaves your machine.',
259+
entry1Title: 'Claude Code users',
260+
entry1Sub: 'One command in your terminal — scans local transcripts, prints your card and a share link.',
261+
privacy1Text: 'Scans locally only · the share link carries just 6 aggregate numbers',
262+
entry2Title: 'claude.ai users',
263+
entry2Sub: 'Use your claude.ai data export — analyzed entirely in this browser.',
264+
dropBig: 'Drop conversations.json here',
265+
dropHint: 'or click to choose a file',
266+
steps: 'Get the file: Settings → Privacy → Export data → it arrives by email; unzip and pick ',
267+
privacy2Text: 'The file is read locally — nothing is uploaded',
268+
demoLink: 'See a sample card first →',
269+
copyCmdBtn: 'Copy',
270+
pngBtn: 'Download card PNG',
271+
copyLinkBtn: 'Copy share link',
272+
resetBtn: 'Start over',
273+
badLink: 'Invalid or corrupted link — back to home',
274+
copiedCmd: 'Command copied — paste it in your terminal',
275+
copiedLink: 'Link copied — it carries only the 6 aggregate numbers',
276+
copyFail: 'Copy failed — grab it manually: ',
277+
readFail: 'Could not read the file, please try again',
278+
badFormat: 'Unexpected file format (the parser is still beta)',
279+
issueLink: 'Report on GitHub',
280+
cdnFail: 'Image library failed to load (CDN) — try refreshing',
281+
pngDone: 'Card downloaded',
282+
pngFail: 'Could not generate the image, please try again',
283+
scoringFail: 'Failed to load lib/scoring.js'
284+
}
285+
};
286+
287+
var lang = (function () {
288+
try {
289+
var saved = localStorage.getItem('cs_lang');
290+
if (saved === 'th' || saved === 'en') return saved;
291+
} catch (e) {}
292+
return (navigator.language || '').toLowerCase().indexOf('th') === 0 ? 'th' : 'en';
293+
})();
294+
295+
function t(key) {
296+
return (STR[lang] && STR[lang][key]) || STR.th[key] || key;
297+
}
298+
299+
function applyLang() {
300+
document.documentElement.lang = lang;
301+
['heroDesc', 'entry1Title', 'entry1Sub', 'privacy1Text', 'entry2Title', 'entry2Sub',
302+
'dropBig', 'dropHint', 'privacy2Text', 'demoLink',
303+
'copyCmdBtn', 'pngBtn', 'copyLinkBtn', 'resetBtn'].forEach(function (id) {
304+
var node = document.getElementById(id);
305+
if (node) node.textContent = t(id);
306+
});
307+
var steps = document.getElementById('steps');
308+
while (steps.firstChild) steps.removeChild(steps.firstChild);
309+
steps.appendChild(document.createTextNode(t('steps')));
310+
var code = document.createElement('code');
311+
code.textContent = 'conversations.json';
312+
steps.appendChild(code);
313+
document.getElementById('langBtn').textContent = lang === 'th' ? 'EN' : 'ไทย';
314+
if (current) renderCard(current);
315+
}
316+
216317
function el(tag, cls, text) {
217318
var e = document.createElement(tag);
218319
if (cls) e.className = cls;
@@ -225,7 +326,7 @@ <h2>สาย claude.ai <span class="badge">BETA</span></h2>
225326
toastEl.appendChild(document.createTextNode(msg));
226327
if (issueLink) {
227328
toastEl.appendChild(document.createTextNode(' '));
228-
var a = el('a', null, 'แจ้งปัญหาบน GitHub');
329+
var a = el('a', null, t('issueLink'));
229330
a.href = 'https://github.com/MankhongGarden/claude-stats/issues';
230331
a.target = '_blank';
231332
a.rel = 'noopener';
@@ -247,7 +348,7 @@ <h2>สาย claude.ai <span class="badge">BETA</span></h2>
247348
var ok = false;
248349
try { ok = document.execCommand('copy'); } catch (e) { ok = false; }
249350
document.body.removeChild(ta);
250-
toast(ok ? okMsg : 'คัดลอกไม่สำเร็จ — copy เองได้เลย: ' + text);
351+
toast(ok ? okMsg : t('copyFail') + text);
251352
}
252353
if (navigator.clipboard && navigator.clipboard.writeText) {
253354
navigator.clipboard.writeText(text).then(function () { toast(okMsg); }, fallback);
@@ -278,7 +379,7 @@ <h2>สาย claude.ai <span class="badge">BETA</span></h2>
278379
var lv = S.level(effort);
279380

280381
document.getElementById('className').textContent = cls[0];
281-
document.getElementById('classSub').textContent = cls[1];
382+
document.getElementById('classSub').textContent = (lang === 'th' ? cls[1] : cls[2]) || cls[1];
282383
document.getElementById('lv').textContent = String(lv);
283384
document.getElementById('srcBadge').textContent = isWeb ? 'WEB · BETA' : 'CLI';
284385

@@ -287,7 +388,9 @@ <h2>สาย claude.ai <span class="badge">BETA</span></h2>
287388
S.AXES.forEach(function (k) {
288389
var li = document.createElement('li');
289390
var label = el('span', 'stat-label', S.META[k].en);
290-
label.appendChild(el('small', null, S.META[k].th + ' · ' + S.META[k].desc));
391+
label.appendChild(el('small', null, lang === 'th'
392+
? S.META[k].th + ' · ' + S.META[k].desc
393+
: S.META[k].descEn || S.META[k].desc));
291394
var bar = el('span', 'bar');
292395
var fill = el('i');
293396
fill.style.width = stats[k] + '%';
@@ -349,7 +452,7 @@ <h2>สาย claude.ai <span class="badge">BETA</span></h2>
349452
if (h.indexOf('#s=') === 0) {
350453
var payload = S.decodeShare(h.slice(3));
351454
if (payload) { renderCard(payload); return; }
352-
toast('ลิงก์ไม่ถูกต้องหรือเสียหาย — กลับสู่หน้าแรก');
455+
toast(t('badLink'));
353456
}
354457
showLanding();
355458
}
@@ -410,7 +513,7 @@ <h2>สาย claude.ai <span class="badge">BETA</span></h2>
410513
function handleFile(file) {
411514
if (!file) return;
412515
var reader = new FileReader();
413-
reader.onerror = function () { toast('อ่านไฟล์ไม่สำเร็จ ลองใหม่อีกครั้ง'); };
516+
reader.onerror = function () { toast(t('readFail')); };
414517
reader.onload = function () {
415518
var parsed = null;
416519
try {
@@ -420,7 +523,7 @@ <h2>สาย claude.ai <span class="badge">BETA</span></h2>
420523
parsed = null;
421524
}
422525
if (!parsed) {
423-
toast('รูปแบบไฟล์ไม่ตรงที่คาด (parser ยังเป็น beta)', true);
526+
toast(t('badFormat'), true);
424527
return;
425528
}
426529
var stats = S.scoreAxes(parsed.raw, 'web');
@@ -433,7 +536,7 @@ <h2>สาย claude.ai <span class="badge">BETA</span></h2>
433536
/* ---------- wire up ---------- */
434537

435538
document.getElementById('copyCmdBtn').addEventListener('click', function () {
436-
copyText('npx github:MankhongGarden/claude-stats', 'คัดลอกคำสั่งแล้ว — ไปวางใน terminal ได้เลย');
539+
copyText('npx github:MankhongGarden/claude-stats', t('copiedCmd'));
437540
});
438541

439542
var dropZone = document.getElementById('dropZone');
@@ -455,7 +558,7 @@ <h2>สาย claude.ai <span class="badge">BETA</span></h2>
455558
});
456559

457560
document.getElementById('copyLinkBtn').addEventListener('click', function () {
458-
copyText(window.location.href, 'คัดลอกลิงก์แล้ว — ในลิงก์มีแค่ตัวเลขสรุป 6 ค่า');
561+
copyText(window.location.href, t('copiedLink'));
459562
});
460563

461564
document.getElementById('resetBtn').addEventListener('click', function () {
@@ -466,7 +569,7 @@ <h2>สาย claude.ai <span class="badge">BETA</span></h2>
466569
});
467570

468571
document.getElementById('pngBtn').addEventListener('click', function () {
469-
if (!window.htmlToImage) { toast('โหลดตัวสร้างรูปไม่สำเร็จ (CDN) ลอง refresh'); return; }
572+
if (!window.htmlToImage) { toast(t('cdnFail')); return; }
470573
var node = document.getElementById('cardNode');
471574
var topKey = current ? S.topAxis(current.stats) : S.AXES[0];
472575
var effort = current
@@ -480,15 +583,22 @@ <h2>สาย claude.ai <span class="badge">BETA</span></h2>
480583
a.download = filename;
481584
a.href = dataUrl;
482585
a.click();
483-
toast('ดาวน์โหลดการ์ดแล้ว');
586+
toast(t('pngDone'));
484587
})
485-
.catch(function () { toast('สร้างรูปไม่สำเร็จ ลองใหม่อีกครั้ง'); });
588+
.catch(function () { toast(t('pngFail')); });
589+
});
590+
591+
document.getElementById('langBtn').addEventListener('click', function () {
592+
lang = lang === 'th' ? 'en' : 'th';
593+
try { localStorage.setItem('cs_lang', lang); } catch (e) {}
594+
applyLang();
486595
});
487596

488597
window.addEventListener('hashchange', route);
489598

599+
applyLang();
490600
if (!S) {
491-
toast('โหลด lib/scoring.js ไม่สำเร็จ');
601+
toast(t('scoringFail'));
492602
showLanding();
493603
} else {
494604
route();

0 commit comments

Comments
 (0)