@hozu/devtools 0.17.2 → 0.18.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.
@@ -1,3 +1,4 @@
1
+ import { t } from '../messages.js';
1
2
  import { h, read, write } from './dom.js';
2
3
  import { curl, traced } from './trace.js';
3
4
  const getJson = (path) => fetch(path)
@@ -88,7 +89,7 @@ async function call(effect, input, win) {
88
89
  return done({
89
90
  ok: false,
90
91
  error: 'Unexpected',
91
- data: { message: 'This page has no client: browser-run effects need one' },
92
+ data: { message: t('api.noClient') },
92
93
  }, [], false);
93
94
  const query = effect.kind === 'query';
94
95
  const response = await win.fetch(query ? '/_hozu/query' : '/_hozu/effect', {
@@ -157,7 +158,7 @@ async function send(endpoint, request, win) {
157
158
  ...base,
158
159
  ok: true,
159
160
  error: null,
160
- value: 'A redirect (the browser does not show where to; open the endpoint URL to follow it)',
161
+ value: t('api.redirect.value'),
161
162
  ms: Math.round(performance.now() - started),
162
163
  status: 0,
163
164
  headers: [],
@@ -238,14 +239,16 @@ export function drawer(host) {
238
239
  const asJson = new Set();
239
240
  const requests = new Map();
240
241
  const running = new Set();
241
- const el = h('section', { class: 'api', 'aria-label': 'API', hidden: true });
242
+ const el = h('section', { class: 'api', 'aria-label': t('api.title'), hidden: true });
242
243
  let shown = false;
243
244
  const height = () => (shown ? Math.min(ui.height, innerHeight - 120) : 0);
244
245
  const resize = () => {
245
246
  el.style.height = `${height()}px`;
246
247
  host.resized();
247
248
  };
248
- const where = (runs) => host.plain() ? { server: 'server', either: 'server, then browser', browser: 'browser' }[runs] : runs;
249
+ const where = (runs) => host.plain()
250
+ ? t({ server: 'api.runs.server', either: 'api.runs.either', browser: 'api.runs.browser' }[runs])
251
+ : runs;
249
252
  const remember = (outcome, sent) => {
250
253
  const record = { ...outcome, requests: sent, id: Date.now(), at: Date.now() };
251
254
  ui.history.unshift(record);
@@ -308,7 +311,7 @@ export function drawer(host) {
308
311
  const invalid = invalidOf(last);
309
312
  if (!flat(schema) || asJson.has(effect.ref))
310
313
  return h('div', { class: 'api-fields' }, [
311
- jsonEditor(`${effect.ref} input (JSON)`, JSON.stringify(value, null, 2), (text) => {
314
+ jsonEditor(t('api.input.aria', { ref: effect.ref }), JSON.stringify(value, null, 2), (text) => {
312
315
  try {
313
316
  inputs.set(effect.ref, JSON.parse(text));
314
317
  }
@@ -321,7 +324,7 @@ export function drawer(host) {
321
324
  : null,
322
325
  ]);
323
326
  if (!Object.keys(props).length)
324
- return h('span', { class: 'api-none' }, ['no input']);
327
+ return h('span', { class: 'api-none' }, [t('api.noInput')]);
325
328
  const current = { ...value };
326
329
  return h('div', { class: 'api-fields' }, Object.entries(props).map(([name, s]) => {
327
330
  const id = `api-${effect.ref}-${name}`;
@@ -383,14 +386,14 @@ export function drawer(host) {
383
386
  h('div', { class: 'api-name' }, [
384
387
  h('b', {}, [host.plain() ? effect.label : effect.ref]),
385
388
  h('span', { class: `api-kind ${effect.kind === 'query' ? 'read' : 'write'}` }, [
386
- effect.kind === 'query' ? 'read' : 'write',
389
+ t(effect.kind === 'query' ? 'api.read' : 'api.write'),
387
390
  ]),
388
391
  h('span', { class: `api-runs ${effect.runs}`, title: `runs: ${effect.runs}` }, [
389
392
  where(effect.runs),
390
393
  ]),
391
394
  effect.kind === 'query' ? h('span', { class: 'api-tag' }, [effect.freshness]) : null,
392
395
  effect.implemented
393
- ? h('span', { class: 'api-where', title: 'Where it is implemented' }, [
396
+ ? h('span', { class: 'api-where', title: t('api.implemented') }, [
394
397
  `${effect.implemented.file}:${effect.implemented.line}`,
395
398
  ])
396
399
  : null,
@@ -398,7 +401,7 @@ export function drawer(host) {
398
401
  ? h('button', {
399
402
  type: 'button',
400
403
  class: 'api-switch',
401
- title: 'JSON sends any value, also one the schema rejects',
404
+ title: t('api.switch.title'),
402
405
  onclick: () => {
403
406
  if (asJson.has(effect.ref))
404
407
  asJson.delete(effect.ref);
@@ -406,13 +409,13 @@ export function drawer(host) {
406
409
  asJson.add(effect.ref);
407
410
  draw();
408
411
  },
409
- }, [asJson.has(effect.ref) ? 'Form' : 'JSON'])
412
+ }, [t(asJson.has(effect.ref) ? 'api.form' : 'api.json')])
410
413
  : null,
411
414
  ]),
412
415
  fieldsOf(effect, last),
413
416
  asking
414
417
  ? h('div', { class: 'api-confirm', role: 'alert' }, [
415
- h('span', {}, ['Writes your development data.']),
418
+ h('span', {}, [t('api.confirm')]),
416
419
  h('button', {
417
420
  type: 'button',
418
421
  class: 'api-ghost',
@@ -420,9 +423,9 @@ export function drawer(host) {
420
423
  confirming = null;
421
424
  draw();
422
425
  },
423
- }, ['Cancel']),
426
+ }, [t('api.cancel')]),
424
427
  h('button', { type: 'button', class: 'api-run warn', onclick: () => void run(effect) }, [
425
- 'Run',
428
+ t('api.run'),
426
429
  ]),
427
430
  ])
428
431
  : h('button', {
@@ -430,7 +433,7 @@ export function drawer(host) {
430
433
  class: `api-run${effect.kind === 'mutation' ? ' warn' : ''}`,
431
434
  disabled: busy,
432
435
  onclick: () => ask(effect),
433
- }, [busy ? 'Running…' : 'Run ▶']),
436
+ }, [t(busy ? 'api.running' : 'api.run.go')]),
434
437
  ]);
435
438
  };
436
439
  const endpointRow = (endpoint) => {
@@ -440,13 +443,13 @@ export function drawer(host) {
440
443
  const headerRows = r.headers.map((pair, i) => {
441
444
  const key = h('input', {
442
445
  value: pair[0],
443
- placeholder: 'Header',
444
- 'aria-label': 'Header name',
446
+ placeholder: t('api.header'),
447
+ 'aria-label': t('api.header.name'),
445
448
  });
446
449
  const val = h('input', {
447
450
  value: pair[1],
448
- placeholder: 'Value',
449
- 'aria-label': 'Header value',
451
+ placeholder: t('api.header.value.placeholder'),
452
+ 'aria-label': t('api.header.value'),
450
453
  });
451
454
  key.addEventListener('input', () => {
452
455
  r.headers[i] = [key.value, val.value];
@@ -471,7 +474,7 @@ export function drawer(host) {
471
474
  const input = h('input', {
472
475
  value: r.params[p],
473
476
  placeholder: p,
474
- 'aria-label': `Path :${p}`,
477
+ 'aria-label': t('api.path.aria', { name: p }),
475
478
  });
476
479
  input.addEventListener('input', () => {
477
480
  r.params[p] = input.value;
@@ -480,14 +483,14 @@ export function drawer(host) {
480
483
  }),
481
484
  h('div', { class: 'api-field wide' }, [
482
485
  h('span', {}, [
483
- endpoint.method === 'GET' ? 'Query (JSON)' : endpoint.raw ? 'Body' : 'Body (JSON)',
486
+ t(endpoint.method === 'GET' ? 'api.query' : endpoint.raw ? 'api.body' : 'api.body.json'),
484
487
  ]),
485
- jsonEditor(`${endpoint.ref} body`, r.body, (text) => {
488
+ jsonEditor(t('api.body.aria', { ref: endpoint.ref }), r.body, (text) => {
486
489
  r.body = text;
487
490
  }),
488
491
  ]),
489
492
  h('div', { class: 'api-field wide' }, [
490
- h('span', {}, ['Headers']),
493
+ h('span', {}, [t('api.headers')]),
491
494
  ...headerRows,
492
495
  h('button', {
493
496
  type: 'button',
@@ -496,10 +499,10 @@ export function drawer(host) {
496
499
  r.headers.push(['', '']);
497
500
  draw();
498
501
  },
499
- }, ['+ Header']),
502
+ }, [t('api.header.add')]),
500
503
  ]),
501
504
  ]),
502
- h('button', { type: 'button', class: 'api-run', disabled: busy, onclick: () => void sendTo(endpoint) }, [busy ? 'Sending…' : 'Send ▶']),
505
+ h('button', { type: 'button', class: 'api-run', disabled: busy, onclick: () => void sendTo(endpoint) }, [t(busy ? 'api.sending' : 'api.send')]),
503
506
  ]);
504
507
  };
505
508
  const cell = (v) => v === null || v === undefined ? '' : typeof v === 'object' ? JSON.stringify(v) : String(v);
@@ -529,67 +532,67 @@ export function drawer(host) {
529
532
  title,
530
533
  onclick: async () => {
531
534
  await navigator.clipboard.writeText(text()).catch(() => { });
532
- button.textContent = 'Copied';
535
+ button.textContent = t('api.copied');
533
536
  },
534
537
  }, [label]);
535
538
  return button;
536
539
  };
537
540
  const sentList = (r) => r.requests.length
538
541
  ? h('div', { class: 'api-sent' }, [
539
- h('div', { class: 'api-label' }, ['Requests it sent']),
540
- ...r.requests.map((t) => h('details', { class: 'api-request' }, [
542
+ h('div', { class: 'api-label' }, [t('api.sent')]),
543
+ ...r.requests.map((out) => h('details', { class: 'api-request' }, [
541
544
  h('summary', {}, [
542
- h('span', { class: `api-method ${t.method.toLowerCase()}` }, [t.method]),
543
- h('code', {}, [t.url]),
544
- h('span', { class: `api-code ${t.status && t.status < 400 ? 'ok' : 'bad'}` }, [
545
- t.error ? 'failed' : String(t.status),
545
+ h('span', { class: `api-method ${out.method.toLowerCase()}` }, [out.method]),
546
+ h('code', {}, [out.url]),
547
+ h('span', { class: `api-code ${out.status && out.status < 400 ? 'ok' : 'bad'}` }, [
548
+ out.error ? t('api.failed') : String(out.status),
546
549
  ]),
547
550
  h('span', { class: 'api-tag' }, [
548
- `${t.ms} ms · ${t.side === 'server' ? 'from the server' : 'from this browser'}`,
551
+ `${out.ms} ms · ${t(out.side === 'server' ? 'api.fromServer' : 'api.fromBrowser')}`,
549
552
  ]),
550
553
  ]),
551
554
  h('div', { class: 'api-request-body' }, [
552
- t.error ? h('em', { class: 'api-invalid' }, [t.error]) : null,
553
- h('b', {}, ['Request headers']),
554
- h('pre', {}, [t.requestHeaders.map(([k, v]) => `${k}: ${v}`).join('\n') || '(none)']),
555
- t.requestBody ? h('b', {}, ['Request body']) : null,
556
- t.requestBody ? h('pre', {}, [t.requestBody]) : null,
557
- h('b', {}, ['Response headers']),
558
- h('pre', {}, [t.responseHeaders.map(([k, v]) => `${k}: ${v}`).join('\n') || '(none)']),
559
- t.responseBody ? h('b', {}, ['Response body']) : null,
560
- t.responseBody ? h('pre', {}, [t.responseBody]) : null,
561
- copyButton('Copy as curl', () => curl(t)),
555
+ out.error ? h('em', { class: 'api-invalid' }, [out.error]) : null,
556
+ h('b', {}, [t('api.requestHeaders')]),
557
+ h('pre', {}, [out.requestHeaders.map(([k, v]) => `${k}: ${v}`).join('\n') || t('api.none')]),
558
+ out.requestBody ? h('b', {}, [t('api.requestBody')]) : null,
559
+ out.requestBody ? h('pre', {}, [out.requestBody]) : null,
560
+ h('b', {}, [t('api.responseHeaders')]),
561
+ h('pre', {}, [out.responseHeaders.map(([k, v]) => `${k}: ${v}`).join('\n') || t('api.none')]),
562
+ out.responseBody ? h('b', {}, [t('api.responseBody')]) : null,
563
+ out.responseBody ? h('pre', {}, [out.responseBody]) : null,
564
+ copyButton(t('api.copyCurl'), () => curl(out)),
562
565
  ]),
563
566
  ])),
564
567
  ])
565
568
  : null;
566
569
  function result(r) {
567
570
  if (!r)
568
- return h('div', { class: 'api-empty' }, ['Run something to see its answer here.']);
571
+ return h('div', { class: 'api-empty' }, [t('api.empty')]);
569
572
  const effect = list?.find((e) => e.ref === r.ref);
570
573
  const status = r.kind === 'endpoint'
571
- ? `${r.status === 0 ? 'Redirect' : (r.status ?? 'No answer')}${r.ok ? '' : ` · ${r.error}`}`
574
+ ? `${r.status === 0 ? t('api.redirect') : (r.status ?? t('api.noAnswer'))}${r.ok ? '' : ` · ${r.error}`}`
572
575
  : r.ok
573
- ? 'OK'
576
+ ? t('api.ok')
574
577
  : r.error === 'Unexpected'
575
- ? 'Unexpected error'
576
- : `Declared error: ${r.error}`;
578
+ ? t('api.unexpected')
579
+ : t('api.declared', { error: String(r.error) });
577
580
  const grid = typeof r.value === 'object' ? table(r.value) : null;
578
581
  return h('div', { class: 'api-result' }, [
579
582
  h('div', { class: `api-status ${r.ok ? 'ok' : 'bad'}`, role: 'status' }, [
580
583
  h('b', {}, [status]),
581
- ` · ${r.ms} ms · ${r.kind === 'endpoint' ? 'HTTP' : r.where === 'server' ? 'through the server' : 'in this browser'} · ${host.plain() ? (effect?.label ?? r.ref) : r.ref}`,
584
+ ` · ${r.ms} ms · ${r.kind === 'endpoint' ? 'HTTP' : t(r.where === 'server' ? 'api.throughServer' : 'api.inBrowser')} · ${host.plain() ? (effect?.label ?? r.ref) : r.ref}`,
582
585
  ]),
583
586
  r.tags.length
584
587
  ? h('div', { class: 'api-note' }, [
585
- `Invalidated ${r.tags.join(', ')}`,
588
+ t('api.invalidated', { tags: r.tags.join(', ') }),
586
589
  r.refreshed
587
- ? ' · the page re-read it'
588
- : h('button', { type: 'button', class: 'api-link', onclick: () => host.win().location.reload() }, [' · reload the page to see it']),
590
+ ? ` · ${t('api.reread')}`
591
+ : h('button', { type: 'button', class: 'api-link', onclick: () => host.win().location.reload() }, [` · ${t('api.reload')}`]),
589
592
  ])
590
593
  : null,
591
594
  h('div', { class: 'api-tools' }, [
592
- h('div', { class: 'api-seg', role: 'group', 'aria-label': 'View' }, [
595
+ h('div', { class: 'api-seg', role: 'group', 'aria-label': t('api.view') }, [
593
596
  ...['table', 'json'].map((v) => h('button', {
594
597
  type: 'button',
595
598
  'aria-pressed': ui.view === v ? 'true' : 'false',
@@ -599,13 +602,11 @@ export function drawer(host) {
599
602
  persist();
600
603
  draw();
601
604
  },
602
- }, [v === 'table' ? 'Table' : 'JSON'])),
605
+ }, [t(v === 'table' ? 'api.table' : 'api.json')])),
603
606
  ]),
604
607
  r.kind === 'endpoint'
605
608
  ? null
606
- : copyButton('Copy as hozu call', () => hozuCall(r, effect?.scope ?? 'public'), r.runs === 'browser', r.runs === 'browser'
607
- ? 'hozu call runs server and either effects; this one needs the browser'
608
- : ''),
609
+ : copyButton(t('api.copyCall'), () => hozuCall(r, effect?.scope ?? 'public'), r.runs === 'browser', r.runs === 'browser' ? t('api.copyCall.browser') : ''),
609
610
  ]),
610
611
  ui.view === 'table' && grid
611
612
  ? h('div', { class: 'api-scroll' }, [grid])
@@ -614,7 +615,7 @@ export function drawer(host) {
614
615
  ]),
615
616
  r.kind === 'endpoint' && r.headers.length
616
617
  ? h('details', { class: 'api-request' }, [
617
- h('summary', {}, ['Response headers']),
618
+ h('summary', {}, [t('api.responseHeaders')]),
618
619
  h('pre', {}, [r.headers.map(([k, v]) => `${k}: ${v}`).join('\n')]),
619
620
  ])
620
621
  : null,
@@ -623,7 +624,7 @@ export function drawer(host) {
623
624
  }
624
625
  function historyList() {
625
626
  if (!ui.history.length)
626
- return [h('div', { class: 'api-empty' }, ['No calls yet.'])];
627
+ return [h('div', { class: 'api-empty' }, [t('api.noCalls')])];
627
628
  return ui.history.map((r) => h('div', { class: `api-row history${r.id === selected ? ' on' : ''}` }, [
628
629
  h('span', { class: `api-dot ${r.ok ? 'ok' : 'bad'}`, 'aria-hidden': 'true' }),
629
630
  h('button', {
@@ -652,7 +653,7 @@ export function drawer(host) {
652
653
  ui.tab = effect.kind;
653
654
  ask(effect);
654
655
  },
655
- }, ['Run again']),
656
+ }, [t('api.runAgain')]),
656
657
  ]));
657
658
  }
658
659
  function sessionBox() {
@@ -677,17 +678,19 @@ export function drawer(host) {
677
678
  return h('div', { class: 'api-row session' }, [
678
679
  h('span', {}),
679
680
  h('div', { class: 'api-name' }, [
680
- h('b', {}, ['Act as']),
681
- h('span', { class: 'api-tag' }, ['development only: sets this browser’s session cookie']),
681
+ h('b', {}, [t('api.actAs')]),
682
+ h('span', { class: 'api-tag' }, [t('api.actAs.hint')]),
682
683
  ]),
683
684
  h('div', { class: 'api-fields' }, [
684
- jsonEditor('Session (JSON)', text, (t) => {
685
- text = t;
685
+ jsonEditor(t('api.session'), text, (value) => {
686
+ text = value;
686
687
  }),
687
688
  sessionError ? h('em', { class: 'api-invalid' }, [sessionError]) : null,
688
689
  ]),
689
690
  h('div', { class: 'api-confirm' }, [
690
- h('button', { type: 'button', class: 'api-ghost', onclick: () => void apply(null) }, ['Sign out']),
691
+ h('button', { type: 'button', class: 'api-ghost', onclick: () => void apply(null) }, [
692
+ t('api.signOut'),
693
+ ]),
691
694
  h('button', {
692
695
  type: 'button',
693
696
  class: 'api-run',
@@ -696,11 +699,11 @@ export function drawer(host) {
696
699
  void apply(JSON.parse(text));
697
700
  }
698
701
  catch {
699
- sessionError = 'The session is not valid JSON';
702
+ sessionError = t('api.session.invalid');
700
703
  draw();
701
704
  }
702
705
  },
703
- }, ['Act as']),
706
+ }, [t('api.actAs')]),
704
707
  ]),
705
708
  ]);
706
709
  }
@@ -710,7 +713,7 @@ export function drawer(host) {
710
713
  }
711
714
  catch (error) {
712
715
  el.replaceChildren(h('div', { class: 'api-empty', role: 'alert' }, [
713
- `The API drawer could not draw: ${error instanceof Error ? error.message : String(error)}. `,
716
+ t('api.broken', { error: error instanceof Error ? error.message : String(error) }),
714
717
  h('button', {
715
718
  type: 'button',
716
719
  class: 'api-link',
@@ -720,7 +723,7 @@ export function drawer(host) {
720
723
  persist();
721
724
  draw();
722
725
  },
723
- }, ['Clear its saved state']),
726
+ }, [t('api.broken.clear')]),
724
727
  ]));
725
728
  }
726
729
  }
@@ -737,7 +740,7 @@ export function drawer(host) {
737
740
  draw();
738
741
  },
739
742
  }, [label, h('span', { class: 'count' }, [String(count)])]);
740
- const grip = h('div', { class: 'api-grip', title: 'Drag to resize', 'aria-hidden': 'true' });
743
+ const grip = h('div', { class: 'api-grip', title: t('common.resize'), 'aria-hidden': 'true' });
741
744
  grip.addEventListener('pointerdown', (event) => {
742
745
  event.preventDefault();
743
746
  grip.setPointerCapture(event.pointerId);
@@ -758,38 +761,42 @@ export function drawer(host) {
758
761
  : ui.tab === 'endpoint'
759
762
  ? endpoints.map(endpointRow)
760
763
  : list === null
761
- ? [h('div', { class: 'api-empty' }, ['This page is not a Hozu page.'])]
764
+ ? [h('div', { class: 'api-empty' }, [t('api.notHozu')])]
762
765
  : (ui.tab === 'query' ? queries : mutations).map(row);
763
766
  const who = session?.declared &&
764
767
  h('button', {
765
768
  type: 'button',
766
769
  class: `api-session${sessionOpen ? ' on' : ''}`,
767
- title: 'The session this browser sends; act as someone else',
770
+ title: t('api.session.title'),
768
771
  onclick: () => {
769
772
  sessionOpen = !sessionOpen;
770
773
  draw();
771
774
  },
772
- }, [session.current === null ? 'Signed out' : `As ${JSON.stringify(session.current)}`]);
775
+ }, [
776
+ session.current === null
777
+ ? t('api.signedOut')
778
+ : t('api.as', { who: JSON.stringify(session.current) }),
779
+ ]);
773
780
  el.replaceChildren(grip, h('div', { class: 'api-head' }, [
774
- h('b', { class: 'api-title' }, ['API']),
781
+ h('b', { class: 'api-title' }, [t('api.title')]),
775
782
  h('div', { class: 'api-tabs', role: 'tablist' }, [
776
- tab('query', host.plain() ? 'Reads' : 'Queries', queries.length),
777
- tab('mutation', host.plain() ? 'Changes' : 'Mutations', mutations.length),
778
- endpoints.length ? tab('endpoint', 'Endpoints', endpoints.length) : null,
779
- tab('history', 'History', ui.history.length),
783
+ tab('query', t(host.plain() ? 'api.reads' : 'api.queries'), queries.length),
784
+ tab('mutation', t(host.plain() ? 'api.changes' : 'api.mutations'), mutations.length),
785
+ endpoints.length ? tab('endpoint', t('api.endpoints'), endpoints.length) : null,
786
+ tab('history', t('api.history'), ui.history.length),
780
787
  ]),
781
788
  h('span', { class: 'api-path' }, [host.path()]),
782
789
  who || null,
783
790
  h('button', {
784
791
  type: 'button',
785
792
  class: 'api-close',
786
- 'aria-label': 'Close the API drawer',
793
+ 'aria-label': t('api.close'),
787
794
  onclick: () => show(false),
788
795
  }, ['×']),
789
796
  ]), h('div', { class: 'api-body' }, [
790
797
  h('div', { class: 'api-list' }, [
791
798
  sessionBox(),
792
- ...(rows.length ? rows : [h('div', { class: 'api-empty' }, ['None on this page.'])]),
799
+ ...(rows.length ? rows : [h('div', { class: 'api-empty' }, [t('api.noneHere')])]),
793
800
  ]),
794
801
  result(ui.history.find((r) => r.id === selected)),
795
802
  ]));