@hanzo/build 0.2.80 → 0.2.81

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 (165) hide show
  1. package/README.md +13 -0
  2. package/lib/activity.d.ts +3 -7
  3. package/lib/activity.js +18 -5
  4. package/lib/api/agents.d.ts +1 -20
  5. package/lib/api/agents.js +16 -8
  6. package/lib/api/auto.d.ts +3 -23
  7. package/lib/api/auto.js +39 -22
  8. package/lib/api/billing.d.ts +0 -35
  9. package/lib/api/billing.js +6 -5
  10. package/lib/api/call.d.ts +16 -8
  11. package/lib/api/call.js +32 -1
  12. package/lib/api/capabilities.d.ts +0 -11
  13. package/lib/api/capabilities.js +2 -1
  14. package/lib/api/codebases.d.ts +0 -10
  15. package/lib/api/codebases.js +3 -2
  16. package/lib/api/coding.d.ts +3 -32
  17. package/lib/api/coding.js +13 -7
  18. package/lib/api/connectors.d.ts +1 -17
  19. package/lib/api/connectors.js +5 -4
  20. package/lib/api/environment.d.ts +6 -20
  21. package/lib/api/environment.js +22 -19
  22. package/lib/api/github.d.ts +0 -22
  23. package/lib/api/github.js +3 -2
  24. package/lib/api/keys.d.ts +0 -14
  25. package/lib/api/keys.js +4 -3
  26. package/lib/api/library.d.ts +3 -17
  27. package/lib/api/library.js +23 -22
  28. package/lib/api/machines.d.ts +0 -15
  29. package/lib/api/machines.js +6 -5
  30. package/lib/api/mcp.d.ts +0 -9
  31. package/lib/api/mcp.js +3 -2
  32. package/lib/api/members.d.ts +0 -15
  33. package/lib/api/members.js +4 -3
  34. package/lib/api/memory.d.ts +0 -11
  35. package/lib/api/memory.js +2 -1
  36. package/lib/api/places.d.ts +1 -9
  37. package/lib/api/places.js +12 -1
  38. package/lib/api/plugins.d.ts +1 -16
  39. package/lib/api/plugins.js +4 -3
  40. package/lib/api/profile.d.ts +0 -10
  41. package/lib/api/profile.js +5 -4
  42. package/lib/api/projects.d.ts +0 -14
  43. package/lib/api/projects.js +2 -1
  44. package/lib/api/provider.d.ts +2 -14
  45. package/lib/api/provider.js +12 -10
  46. package/lib/api/sandbox.d.ts +0 -17
  47. package/lib/api/sandbox.js +5 -4
  48. package/lib/api/sessions.d.ts +0 -17
  49. package/lib/api/sessions.js +5 -4
  50. package/lib/api/skills.d.ts +1 -15
  51. package/lib/api/skills.js +5 -4
  52. package/lib/api/studio.d.ts +0 -14
  53. package/lib/api/studio.js +6 -5
  54. package/lib/api/turn.d.ts +6 -28
  55. package/lib/api/turn.js +105 -49
  56. package/lib/api/verdict.d.ts +0 -9
  57. package/lib/api/verdict.js +2 -1
  58. package/lib/api/webhooks.d.ts +1 -14
  59. package/lib/api/webhooks.js +5 -4
  60. package/lib/ask.js +7 -4
  61. package/lib/automations.d.ts +5 -4
  62. package/lib/automations.js +95 -74
  63. package/lib/back.js +10 -4
  64. package/lib/builder.js +7 -5
  65. package/lib/carry.js +20 -16
  66. package/lib/customize/agents.js +78 -31
  67. package/lib/customize/connectors.js +62 -48
  68. package/lib/customize/index.js +43 -10
  69. package/lib/customize/plugins.js +34 -22
  70. package/lib/customize/say.d.ts +13 -1
  71. package/lib/customize/say.js +15 -21
  72. package/lib/customize/skills.js +31 -22
  73. package/lib/customize/ui.d.ts +5 -9
  74. package/lib/customize/ui.js +26 -16
  75. package/lib/data.d.ts +1 -1
  76. package/lib/data.js +3 -2
  77. package/lib/desk.js +45 -33
  78. package/lib/door.js +35 -10
  79. package/lib/environment.js +25 -19
  80. package/lib/files.js +43 -34
  81. package/lib/find.js +12 -9
  82. package/lib/foot.js +7 -4
  83. package/lib/forge.js +61 -40
  84. package/lib/gear.d.ts +2 -1
  85. package/lib/gear.js +6 -4
  86. package/lib/git.js +29 -19
  87. package/lib/landing.d.ts +5 -5
  88. package/lib/landing.js +52 -44
  89. package/lib/library.js +73 -51
  90. package/lib/meter.d.ts +1 -1
  91. package/lib/meter.js +14 -5
  92. package/lib/mic.js +12 -5
  93. package/lib/mind.d.ts +1 -0
  94. package/lib/mind.js +5 -3
  95. package/lib/nav.d.ts +6 -5
  96. package/lib/nav.js +37 -30
  97. package/lib/panel.d.ts +2 -1
  98. package/lib/panel.js +52 -36
  99. package/lib/plan.d.ts +4 -20
  100. package/lib/plan.js +26 -10
  101. package/lib/plans.js +49 -30
  102. package/lib/plugins/design.js +3 -2
  103. package/lib/plugins/index.d.ts +23 -4
  104. package/lib/plugins/index.js +11 -26
  105. package/lib/plugins/pdf.js +2 -1
  106. package/lib/plugins/project.js +6 -3
  107. package/lib/plugins/research.js +3 -2
  108. package/lib/plugins/sketch.js +6 -3
  109. package/lib/plugins/slides.js +3 -2
  110. package/lib/plugins/template.js +6 -3
  111. package/lib/plus.js +25 -13
  112. package/lib/prefs.d.ts +0 -14
  113. package/lib/prefs.js +2 -1
  114. package/lib/project.js +69 -62
  115. package/lib/prompt.d.ts +12 -11
  116. package/lib/prompt.js +38 -20
  117. package/lib/publish.js +17 -12
  118. package/lib/reply.js +6 -3
  119. package/lib/run.js +72 -54
  120. package/lib/say.d.ts +2 -0
  121. package/lib/say.js +7 -0
  122. package/lib/scheduled.js +54 -43
  123. package/lib/section.d.ts +1 -0
  124. package/lib/section.js +8 -2
  125. package/lib/settings/account.d.ts +1 -0
  126. package/lib/settings/account.js +31 -21
  127. package/lib/settings/billing.js +55 -28
  128. package/lib/settings/capabilities.js +26 -15
  129. package/lib/settings/card.js +19 -12
  130. package/lib/settings/code.js +22 -11
  131. package/lib/settings/credits.d.ts +4 -4
  132. package/lib/settings/credits.js +34 -21
  133. package/lib/settings/environments.js +21 -14
  134. package/lib/settings/general.js +26 -14
  135. package/lib/settings/index.js +6 -3
  136. package/lib/settings/integrations.js +42 -28
  137. package/lib/settings/keys.js +37 -19
  138. package/lib/settings/list.d.ts +0 -5
  139. package/lib/settings/list.js +24 -15
  140. package/lib/settings/machines.d.ts +3 -0
  141. package/lib/settings/machines.js +63 -38
  142. package/lib/settings/members.js +23 -14
  143. package/lib/settings/memory.js +12 -6
  144. package/lib/settings/notifications.js +32 -19
  145. package/lib/settings/plan.d.ts +1 -1
  146. package/lib/settings/plan.js +40 -17
  147. package/lib/settings/privacy.js +26 -13
  148. package/lib/settings/ui.d.ts +3 -2
  149. package/lib/settings/ui.js +9 -3
  150. package/lib/settings/usage.js +7 -3
  151. package/lib/shelf.js +18 -15
  152. package/lib/side.d.ts +1 -26
  153. package/lib/side.js +11 -5
  154. package/lib/standing.d.ts +2 -10
  155. package/lib/standing.js +5 -3
  156. package/lib/suggest.d.ts +8 -28
  157. package/lib/suggest.js +39 -4
  158. package/lib/suggestions.js +14 -5
  159. package/lib/summary.d.ts +1 -1
  160. package/lib/summary.js +4 -1
  161. package/lib/sync.js +22 -12
  162. package/lib/tools.d.ts +4 -3
  163. package/lib/tools.js +10 -7
  164. package/lib/transcript.js +10 -8
  165. package/package.json +4 -2
@@ -5,12 +5,17 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
5
5
  * — and the model and effort, which are the one choice Chat and Dev share
6
6
  * (mind.tsx): changed here, changed in both composers. Beside them, the
7
7
  * codebases' environments and the organization's machines, each on its own page.
8
+ *
9
+ * Its words are the reader's language (@hanzo/i18n `t`); a model's and a
10
+ * machine's names are drawn as given.
8
11
  */
9
12
  import { YStack } from '@hanzo/gui';
13
+ import { useI18n } from '@hanzo/i18n/react';
10
14
  import { ChevronRight } from '@hanzogui/lucide-icons-2';
11
15
  import { ChipSelect } from '@hanzo/ui/product';
12
16
  import { ModelPicker } from '@hanzo/ui/models';
13
17
  import { useState } from 'react';
18
+ import { told } from '../api/call.js';
14
19
  import { unhonoured } from '../api/coding.js';
15
20
  import { ENSO } from '../api/models.js';
16
21
  import { SANDBOX } from '../api/places.js';
@@ -20,29 +25,31 @@ import { MODES } from '../landing.js';
20
25
  import { EFFORTS, useCatalog, useChoice } from '../mind.js';
21
26
  import { usePrefs } from '../prefs.js';
22
27
  import { path } from '../route.js';
28
+ import { said } from './machines.js';
23
29
  import { Card, Group, Heading, Note, Row, Soft } from './ui.js';
24
30
  export function Code() {
31
+ const { t } = useI18n();
25
32
  const host = useHost();
26
- const t = useTarget();
33
+ const target = useTarget();
27
34
  const signed = Boolean(host.person);
28
35
  const { prefs, save } = usePrefs();
29
- const catalog = useCatalog(t);
36
+ const catalog = useCatalog(target);
30
37
  const [choice, choose] = useChoice();
31
- const places = usePlaces(t, signed);
38
+ const places = usePlaces(target, signed);
32
39
  const [note, setNote] = useState('');
33
40
  if (!signed) {
34
- return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "Code" }), _jsx(Soft, { children: "Sign in to set how your runs start." })] }));
41
+ return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: t('Code') }), _jsx(Soft, { children: t('Sign in to set how your runs start.') })] }));
35
42
  }
36
43
  const now = { mode: 'build', place: '', ...prefs.code };
37
44
  const keep = (change) => {
38
45
  setNote('');
39
- save({ code: { ...prefs.code, ...change } }).catch((e) => setNote(e.message));
46
+ save({ code: { ...prefs.code, ...change } }).catch((e) => setNote(told(e, t)));
40
47
  };
41
48
  const where = (places.value.length ? places.value : [SANDBOX]).map((p) => ({
42
49
  id: p.id || 'sandbox',
43
- label: p.id ? p.label : 'Default',
50
+ label: p.id ? p.label : t('Default'),
44
51
  // A machine offline now can still be the default; its status shows beside it.
45
- hint: p.id ? [p.status, p.capacity].filter(Boolean).join(' · ') : 'The platform’s own sandbox',
52
+ hint: p.id ? [said(p.status, t), p.capacity].filter(Boolean).join(' · ') : t('The platform’s own sandbox'),
46
53
  place: p.id,
47
54
  }));
48
55
  // The sandbox is always listed, so a place not found is a machine that is not: it reads as its id.
@@ -50,10 +57,14 @@ export function Code() {
50
57
  // A default is never premium: a premium model is picked for one run, in New.
51
58
  const offered = catalog.models.filter((m) => m.class !== 'premium');
52
59
  const go = (section) => host.go(path({ kind: 'settings', section }));
53
- const effort = EFFORTS.find((e) => e.id === choice.effort);
54
- const mode = MODES.find((m) => m.id === now.mode);
55
- return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: "Code", detail: "How a new run starts. The model and effort are the ones Chat uses too." }), _jsxs(Group, { title: "Defaults", children: [_jsxs(Card, { children: [_jsx(Row, { first: true, title: "Model", detail: "Enso picks one for each step; any other model runs them all.", trailing: _jsx(ModelPicker, { size: "sm", name: "Default model", models: offered, scope: "chat", value: choice.model || ENSO, onChange: (id) => choose({ model: id }), loading: catalog.loading, error: catalog.error }) }), _jsx(Row, { title: "Effort", detail: "How long the agent thinks before it acts.", trailing: _jsx(ChipSelect, { name: "Default effort", label: effort.label, chosen: effort, items: EFFORTS, onChange: (e) => choose({ effort: e.id }), placement: "bottom-end", width: 180 }) }), _jsx(Row, { title: "Mode", detail: mode.hint, trailing: _jsx(ChipSelect, { name: "Default mode", label: mode.label, chosen: mode, items: MODES, onChange: (m) => keep({ mode: m.id }), placement: "bottom-end", width: 260 }) }), _jsx(Row, { title: "Where it runs", detail: "The Hanzo sandbox, or one of your organization\u2019s machines.", trailing: _jsx(ChipSelect, { name: "Default place", label: place.label, chosen: place, items: where, onChange: (w) => keep({ place: w.place }), placeholder: "Search machines\u2026", placement: "bottom-end", loading: places.loading, error: places.error?.message ?? null }) })] }), _jsx(Note, { children: unhonoured(now.mode, now.place) })] }), _jsx(Group, { title: "Workspace", children: _jsxs(Card, { children: [_jsx(Row, { first: true, title: "Environments", detail: "What a run installs and starts in each codebase, and the secrets it exports.", trailing: _jsx(Link, { label: "Environments", onPress: () => go('environments') }) }), _jsx(Row, { title: "Machines", detail: "Your organization\u2019s own computers that take runs.", trailing: _jsx(Link, { label: "Machines", onPress: () => go('machines') }) })] }) }), _jsx(Note, { children: note })] }));
60
+ // The choices in the reader's words, kept by id.
61
+ const efforts = EFFORTS.map((e) => ({ ...e, label: t(e.label) }));
62
+ const modes = MODES.map((m) => ({ ...m, label: t(m.label), hint: t(m.hint) }));
63
+ const effort = efforts.find((e) => e.id === choice.effort);
64
+ const mode = modes.find((m) => m.id === now.mode);
65
+ return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: t('Code'), detail: t('How a new run starts. The model and effort are the ones Chat uses too.') }), _jsxs(Group, { title: t('Defaults'), children: [_jsxs(Card, { children: [_jsx(Row, { first: true, title: t('Model'), detail: t('Enso picks one for each step; any other model runs them all.'), trailing: _jsx(ModelPicker, { size: "sm", name: t('Default model'), models: offered, scope: "chat", value: choice.model || ENSO, onChange: (id) => choose({ model: id }), loading: catalog.loading, error: catalog.error }) }), _jsx(Row, { title: t('Effort'), detail: t('How long the agent thinks before it acts.'), trailing: _jsx(ChipSelect, { name: t('Default effort'), label: effort.label, chosen: effort, items: efforts, onChange: (e) => choose({ effort: e.id }), placement: "bottom-end", width: 180 }) }), _jsx(Row, { title: t('Mode'), detail: mode.hint, trailing: _jsx(ChipSelect, { name: t('Default mode'), label: mode.label, chosen: mode, items: modes, onChange: (m) => keep({ mode: m.id }), placement: "bottom-end", width: 260 }) }), _jsx(Row, { title: t('Where it runs'), detail: t('The Hanzo sandbox, or one of your organization’s machines.'), trailing: _jsx(ChipSelect, { name: t('Default place'), label: place.label, chosen: place, items: where, onChange: (w) => keep({ place: w.place }), placeholder: t('Search machines…'), placement: "bottom-end", loading: places.loading, error: places.error ? told(places.error, t) : null }) })] }), _jsx(Note, { children: unhonoured(now.mode, now.place, t) })] }), _jsx(Group, { title: t('Workspace'), children: _jsxs(Card, { children: [_jsx(Row, { first: true, title: t('Environments'), detail: t('What a run installs and starts in each codebase, and the secrets it exports.'), trailing: _jsx(Link, { label: t('Open Environments'), onPress: () => go('environments') }) }), _jsx(Row, { title: t('Machines'), detail: t('Your organization’s own computers that take runs.'), trailing: _jsx(Link, { label: t('Open Machines'), onPress: () => go('machines') }) })] }) }), _jsx(Note, { children: note })] }));
56
66
  }
67
+ /** A row's way to its own page; `label` is what it opens, already said. */
57
68
  function Link({ label, onPress }) {
58
- return (_jsx(YStack, { render: "button", "aria-label": `Open ${label}`, p: "$1", rounded: "$2", hoverStyle: { bg: '$hover' }, onPress: onPress, children: _jsx(ChevronRight, { size: 16 }) }));
69
+ return (_jsx(YStack, { render: "button", "aria-label": label, p: "$1", rounded: "$2", hoverStyle: { bg: '$hover' }, onPress: onPress, children: _jsx(ChevronRight, { size: 16, 'data-i18n-mirror': '' }) }));
59
70
  }
@@ -1,7 +1,7 @@
1
- import type { Target } from '../api/call.ts';
2
- /** What the credits section says first, on every surface. */
3
- export declare const SEPARATE = "Credits are separate from your plan\u2019s usage allowance. They pay for usage past it when you choose to continue with credits, and for all usage when there is no plan.";
4
- export declare function Credits({ target: t, admin, add, onCard }: {
1
+ import { type Target } from '../api/call.ts';
2
+ /** What the credits section says first, on every surface: English, marked (`phrase`), drawn through `t`. */
3
+ export declare const SEPARATE: string;
4
+ export declare function Credits({ target, admin, add, onCard }: {
5
5
  target: Target;
6
6
  admin: boolean;
7
7
  add?: string;
@@ -13,18 +13,25 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
13
13
  * Money moves two ways, the host's choice: `add` names a page that takes it (the
14
14
  * pay site); without one a saved card is charged here (POST /v1/billing/topup)
15
15
  * and a person with no card is sent to add one (`onCard`).
16
+ *
17
+ * Its words are the reader's language (@hanzo/i18n `t`); a grant's, a limit's
18
+ * and a kind of spend's names are the ledger's, drawn as given.
16
19
  */
17
20
  import { SizableText, XStack, YStack } from '@hanzo/gui';
21
+ import { phrase } from '@hanzo/i18n';
22
+ import { useI18n } from '@hanzo/i18n/react';
18
23
  import { Button, Input, Switch } from '@hanzo/ui';
19
24
  import { useEffect, useState } from 'react';
20
25
  import { arm, balance, caps, cents, chosen, credit, grants, label, lift, limit, methods, money, monthly, recharge, spend, topup, } from '../api/billing.js';
26
+ import { told } from '../api/call.js';
21
27
  import { useRead } from '../data.js';
22
28
  import { Card, day, Group, Note, Row, Soft } from './ui.js';
23
- /** What the credits section says first, on every surface. */
24
- export const SEPARATE = 'Credits are separate from your plan’s usage allowance. They pay for usage past it when you choose to continue with credits, and for all usage when there is no plan.';
29
+ /** What the credits section says first, on every surface: English, marked (`phrase`), drawn through `t`. */
30
+ export const SEPARATE = phrase('Credits are separate from your plan’s usage allowance. They pay for usage past it when you choose to continue with credits, and for all usage when there is no plan.');
25
31
  /** A money figure as read: the amount, or why there is none. */
26
32
  function Figure({ read }) {
27
- return (_jsx(SizableText, { size: "$2", color: read.error ? '$soft' : '$ink', numberOfLines: 1, maxW: 200, children: read.error ? 'Unavailable' : read.loading ? '…' : money(read.value) }));
33
+ const { t } = useI18n();
34
+ return (_jsx(SizableText, { size: "$2", color: read.error ? '$soft' : '$ink', numberOfLines: 1, maxW: 200, children: read.error ? t('Unavailable') : read.loading ? '…' : money(read.value) }));
28
35
  }
29
36
  /** A thin bar of a whole, red once a limit is used up. */
30
37
  function Bar({ part, whole, limit }) {
@@ -35,15 +42,16 @@ function Meter({ title, used, of, says, first, limit = true }) {
35
42
  return (_jsxs(YStack, { gap: "$2", px: "$3", py: "$3", borderTopWidth: first ? 0 : 1, borderColor: "$borderColor", children: [_jsxs(XStack, { items: "baseline", justify: "space-between", flexWrap: "wrap", columnGap: "$3", rowGap: "$1", children: [_jsx(SizableText, { size: "$2", color: "$ink", children: title }), _jsx(SizableText, { size: "$1", color: "$soft", children: says })] }), _jsx(Bar, { part: used, whole: of, limit: limit })] }));
36
43
  }
37
44
  /** `Resets Oct 1, 2026`, or '' for none. */
38
- const resets = (iso) => (day(iso) ? `Resets ${day(iso)}` : '');
39
- export function Credits({ target: t, admin, add, onCard }) {
40
- const left = useRead(() => balance(t), 0, [t]);
41
- const granted = useRead(() => credit(t), 0, [t]);
42
- const rows = useRead(() => grants(t), [], [t]);
43
- const spent = useRead(() => spend(t), null, [t]);
44
- const rule = useRead(() => recharge(t), null, [t]);
45
- const ceilings = useRead(() => caps(t), [], [t]);
46
- const cards = useRead(add ? null : () => methods(t), [], [t, add]);
45
+ const resets = (iso, t) => (day(iso) ? t('Resets {date}', { date: day(iso) }) : '');
46
+ export function Credits({ target, admin, add, onCard }) {
47
+ const { t } = useI18n();
48
+ const left = useRead(() => balance(target), 0, [target]);
49
+ const granted = useRead(() => credit(target), 0, [target]);
50
+ const rows = useRead(() => grants(target), [], [target]);
51
+ const spent = useRead(() => spend(target), null, [target]);
52
+ const rule = useRead(() => recharge(target), null, [target]);
53
+ const ceilings = useRead(() => caps(target), [], [target]);
54
+ const cards = useRead(add ? null : () => methods(target), [], [target, add]);
47
55
  const [buying, setBuying] = useState(false);
48
56
  const [amount, setAmount] = useState('25');
49
57
  const [ceiling, setCeiling] = useState('');
@@ -67,7 +75,7 @@ export function Credits({ target: t, admin, add, onCard }) {
67
75
  setNote(done);
68
76
  }
69
77
  catch (e) {
70
- setNote(e.message);
78
+ setNote(told(e, t));
71
79
  }
72
80
  finally {
73
81
  setWorking(false);
@@ -82,8 +90,8 @@ export function Credits({ target: t, admin, add, onCard }) {
82
90
  const live = rows.value.filter((g) => g.active && g.remaining > 0);
83
91
  const buy = () => {
84
92
  if (!adding)
85
- return setNote('Enter an amount in dollars');
86
- void act(() => topup(t, adding, card.id), `${money(adding)} is added to the balance`, () => {
93
+ return setNote(t('Enter an amount in dollars'));
94
+ void act(() => topup(target, adding, card.id), t('{amount} is added to the balance', { amount: money(adding) }), () => {
87
95
  setBuying(false);
88
96
  left.reload();
89
97
  });
@@ -91,8 +99,8 @@ export function Credits({ target: t, admin, add, onCard }) {
91
99
  const setLimit = () => {
92
100
  const c = cents(ceiling);
93
101
  if (!c)
94
- return setNote('Enter a monthly limit in dollars');
95
- void act(() => limit(t, ceilings.value, c), `Spend stops at ${money(c)} a month`, () => {
102
+ return setNote(t('Enter a monthly limit in dollars'));
103
+ void act(() => limit(target, ceilings.value, c), t('Spend stops at {amount} a month', { amount: money(c) }), () => {
96
104
  setCeiling('');
97
105
  ceilings.reload();
98
106
  });
@@ -101,10 +109,15 @@ export function Credits({ target: t, admin, add, onCard }) {
101
109
  const threshold = below.trim() === '' || below.trim() === '0' ? 0 : cents(below);
102
110
  const each = cents(reload);
103
111
  if (enabled && (threshold === null || !each))
104
- return setNote('Enter the balance to reload below and the amount to add, in dollars');
105
- void act(() => arm(t, { enabled, threshold: threshold ?? 0, amount: each ?? rule.value?.amount ?? 0 }), enabled ? `Adds ${money(each ?? 0)} whenever the balance falls below ${money(threshold ?? 0)}` : 'Auto-reload is off', rule.reload);
112
+ return setNote(t('Enter the balance to reload below and the amount to add, in dollars'));
113
+ void act(() => arm(target, { enabled, threshold: threshold ?? 0, amount: each ?? rule.value?.amount ?? 0 }), enabled ? t('Adds {amount} whenever the balance falls below {floor}', { amount: money(each ?? 0), floor: money(threshold ?? 0) }) : t('Auto-reload is off'), rule.reload);
106
114
  };
107
- return (_jsxs(YStack, { gap: "$6", "data-slot": "credits", children: [_jsxs(Group, { title: "Credits", detail: SEPARATE, action: add ? (_jsx(Button, { size: "sm", variant: "outline", onPress: () => window.open(add, '_blank', 'noopener'), children: "Add funds" })) : buying || !admin ? undefined : (_jsx(Button, { size: "sm", variant: "outline", onPress: () => setBuying(true), children: "Buy more" })), children: [_jsxs(Card, { children: [_jsx(Row, { first: true, title: "Prepaid balance", detail: "Bought with a card", trailing: _jsx(Figure, { read: left }) }), _jsx(Row, { title: "Granted credit", detail: "Given by Hanzo; some models take only prepaid", trailing: _jsx(Figure, { read: granted }) }), _jsx(Row, { title: "Spent this month", detail: s && !s.known ? 'The ledger did not answer, so this is not a measurement' : undefined, trailing: _jsx(Figure, { read: { ...spent, value: s?.total ?? 0 } }) }), buying ? (_jsx(YStack, { gap: "$2", px: "$3", py: "$3", borderTopWidth: 1, borderColor: "$borderColor", children: card ? (_jsxs(_Fragment, { children: [_jsxs(XStack, { gap: "$2", items: "center", flexWrap: "wrap", children: [_jsx(YStack, { width: 140, children: _jsx(Input, { value: amount, onChangeText: setAmount, "aria-label": "Amount in dollars", placeholder: "25", inputMode: "decimal" }) }), _jsxs(SizableText, { flex: 1, minW: 160, size: "$1", color: "$soft", children: ["Charged to ", label(card)] })] }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", onPress: () => setBuying(false), children: "Cancel" }), _jsx(Button, { size: "sm", disabled: working, onPress: buy, children: adding ? `Add ${money(adding)}` : 'Add funds' })] })] })) : (_jsxs(XStack, { gap: "$2", items: "center", flexWrap: "wrap", children: [_jsx(SizableText, { flex: 1, minW: 160, size: "$2", color: "$soft", children: cards.loading ? 'Reading cards…' : 'Add a card to buy more.' }), onCard ? (_jsx(Button, { size: "sm", variant: "outline", onPress: onCard, children: "Add card" })) : null] })) })) : null] }), live.length ? (_jsx(Card, { children: live.map((g, i) => (_jsx(Row, { first: i === 0, title: g.name || 'Credit grant', detail: [`${money(g.remaining, g.currency)} left of ${money(g.amount, g.currency)}`, g.expires ? `expires ${day(g.expires)}` : ''].filter(Boolean).join(' · ') }, g.id))) })) : null] }), _jsx(Group, { title: "Auto-reload", detail: "Tops the balance up from the card on file whenever it falls below a floor.", children: rule.error ? (_jsx(Soft, { children: rule.error.message })) : !rule.value ? (_jsx(Soft, { children: "Reading auto-reload\u2026" })) : (_jsxs(Card, { children: [_jsx(Row, { first: true, title: rule.value.enabled ? `Adds ${money(rule.value.amount)} below ${money(rule.value.threshold)}` : 'Off', detail: rule.value.last ? `Last reloaded ${day(rule.value.last)}` : admin ? undefined : 'An org admin sets auto-reload.', trailing: admin ? (_jsx(Switch, { "aria-label": "Auto-reload", checked: rule.value.enabled, disabled: working, onCheckedChange: (on) => saveRule(on) })) : undefined }), admin ? (_jsxs(XStack, { gap: "$2", items: "center", px: "$3", py: "$3", borderTopWidth: 1, borderColor: "$borderColor", flexWrap: "wrap", children: [_jsx(YStack, { width: 150, children: _jsx(Input, { value: below, onChangeText: setBelow, "aria-label": "Reload below, in dollars", placeholder: "Below $5", inputMode: "decimal" }) }), _jsx(YStack, { width: 150, children: _jsx(Input, { value: reload, onChangeText: setReload, "aria-label": "Amount to add, in dollars", placeholder: "Add $25", inputMode: "decimal" }) }), _jsx(XStack, { flex: 1, justify: "flex-end", children: _jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => saveRule(true), children: rule.value.enabled ? 'Save' : 'Turn on' }) })] })) : null] })) }), _jsx(Group, { title: "Spend this month", detail: "What credits paid for, by kind.", children: spent.error ? (_jsx(Soft, { children: spent.error.message })) : !s ? (_jsx(Soft, { children: "Reading spend\u2026" })) : s.categories.length === 0 ? (_jsx(Card, { children: _jsx(Soft, { children: "Nothing spent this month." }) })) : (_jsx(Card, { children: s.categories.map((c, i) => (_jsx(Meter, { first: i === 0, title: c.name, used: c.cents, of: top, says: money(c.cents), limit: false }, c.name))) })) }), _jsxs(Group, { title: "Monthly limit", detail: "Spend from credits past it is refused until the month starts over.", children: [ceilings.error ? (_jsx(Soft, { children: ceilings.error.message })) : (_jsxs(Card, { children: [cap ? (_jsx(Meter, { first: true, title: `${money(cap.threshold)} a month`, used: cap.spent ?? 0, of: cap.threshold, says: [cap.spent === null ? 'Spend unknown' : `${money(cap.spent)} spent`, resets(cap.resets)].filter(Boolean).join(' · ') })) : (_jsx(Row, { first: true, title: "No monthly limit", detail: "Spend is bounded by the balance alone." })), others.map((c) => (_jsx(Row, { title: c.title || 'Limit', detail: [c.project && `project ${c.project}`, c.service && `service ${c.service}`, c.enforce ? 'refuses spend past it' : 'warns only']
115
+ return (_jsxs(YStack, { gap: "$6", "data-slot": "credits", children: [_jsxs(Group, { title: t('Credits'), detail: t(SEPARATE), action: add ? (_jsx(Button, { size: "sm", variant: "outline", onPress: () => window.open(add, '_blank', 'noopener'), children: t('Add funds') })) : buying || !admin ? undefined : (_jsx(Button, { size: "sm", variant: "outline", onPress: () => setBuying(true), children: t('Buy more') })), children: [_jsxs(Card, { children: [_jsx(Row, { first: true, title: t('Prepaid balance'), detail: t('Bought with a card'), trailing: _jsx(Figure, { read: left }) }), _jsx(Row, { title: t('Granted credit'), detail: t('Given by Hanzo; some models take only prepaid'), trailing: _jsx(Figure, { read: granted }) }), _jsx(Row, { title: t('Spent this month'), detail: s && !s.known ? t('The ledger did not answer, so this is not a measurement') : undefined, trailing: _jsx(Figure, { read: { ...spent, value: s?.total ?? 0 } }) }), buying ? (_jsx(YStack, { gap: "$2", px: "$3", py: "$3", borderTopWidth: 1, borderColor: "$borderColor", children: card ? (_jsxs(_Fragment, { children: [_jsxs(XStack, { gap: "$2", items: "center", flexWrap: "wrap", children: [_jsx(YStack, { width: 140, children: _jsx(Input, { value: amount, onChangeText: setAmount, "aria-label": t('Amount in dollars'), placeholder: "25", inputMode: "decimal" }) }), _jsx(SizableText, { flex: 1, minW: 160, size: "$1", color: "$soft", children: t('Charged to {card}', { card: label(card) }) })] }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", onPress: () => setBuying(false), children: t('Cancel') }), _jsx(Button, { size: "sm", disabled: working, onPress: buy, children: adding ? t('Add {amount}', { amount: money(adding) }) : t('Add funds') })] })] })) : (_jsxs(XStack, { gap: "$2", items: "center", flexWrap: "wrap", children: [_jsx(SizableText, { flex: 1, minW: 160, size: "$2", color: "$soft", children: cards.loading ? t('Reading cards…') : t('Add a card to buy more.') }), onCard ? (_jsx(Button, { size: "sm", variant: "outline", onPress: onCard, children: t('Add card') })) : null] })) })) : null] }), live.length ? (_jsx(Card, { children: live.map((g, i) => (_jsx(Row, { first: i === 0, title: g.name || t('Credit grant'), detail: [
116
+ t('{left} left of {amount}', { left: money(g.remaining, g.currency), amount: money(g.amount, g.currency) }),
117
+ g.expires ? t('expires {date}', { date: day(g.expires) }) : '',
118
+ ]
119
+ .filter(Boolean)
120
+ .join(' · ') }, g.id))) })) : null] }), _jsx(Group, { title: t('Auto-reload'), detail: t('Tops the balance up from the card on file whenever it falls below a floor.'), children: rule.error ? (_jsx(Soft, { children: told(rule.error, t) })) : !rule.value ? (_jsx(Soft, { children: t('Reading auto-reload…') })) : (_jsxs(Card, { children: [_jsx(Row, { first: true, title: rule.value.enabled ? t('Adds {amount} below {floor}', { amount: money(rule.value.amount), floor: money(rule.value.threshold) }) : t('Off'), detail: rule.value.last ? t('Last reloaded {date}', { date: day(rule.value.last) }) : admin ? undefined : t('An org admin sets auto-reload.'), trailing: admin ? (_jsx(Switch, { "aria-label": t('Auto-reload'), checked: rule.value.enabled, disabled: working, onCheckedChange: (on) => saveRule(on) })) : undefined }), admin ? (_jsxs(XStack, { gap: "$2", items: "center", px: "$3", py: "$3", borderTopWidth: 1, borderColor: "$borderColor", flexWrap: "wrap", children: [_jsx(YStack, { width: 150, children: _jsx(Input, { value: below, onChangeText: setBelow, "aria-label": t('Reload below, in dollars'), placeholder: t('Below {amount}', { amount: '$5' }), inputMode: "decimal" }) }), _jsx(YStack, { width: 150, children: _jsx(Input, { value: reload, onChangeText: setReload, "aria-label": t('Amount to add, in dollars'), placeholder: t('Add {amount}', { amount: '$25' }), inputMode: "decimal" }) }), _jsx(XStack, { flex: 1, justify: "flex-end", children: _jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => saveRule(true), children: rule.value.enabled ? t('Save') : t('Turn on') }) })] })) : null] })) }), _jsx(Group, { title: t('Spend this month'), detail: t('What credits paid for, by kind.'), children: spent.error ? (_jsx(Soft, { children: told(spent.error, t) })) : !s ? (_jsx(Soft, { children: t('Reading spend…') })) : s.categories.length === 0 ? (_jsx(Card, { children: _jsx(Soft, { children: t('Nothing spent this month.') }) })) : (_jsx(Card, { children: s.categories.map((c, i) => (_jsx(Meter, { first: i === 0, title: c.name, used: c.cents, of: top, says: money(c.cents), limit: false }, c.name))) })) }), _jsxs(Group, { title: t('Monthly limit'), detail: t('Spend from credits past it is refused until the month starts over.'), children: [ceilings.error ? (_jsx(Soft, { children: told(ceilings.error, t) })) : (_jsxs(Card, { children: [cap ? (_jsx(Meter, { first: true, title: t('{amount} a month', { amount: money(cap.threshold) }), used: cap.spent ?? 0, of: cap.threshold, says: [cap.spent === null ? t('Spend unknown') : t('{amount} spent', { amount: money(cap.spent) }), resets(cap.resets, t)].filter(Boolean).join(' · ') })) : (_jsx(Row, { first: true, title: t('No monthly limit'), detail: t('Spend is bounded by the balance alone.') })), others.map((c) => (_jsx(Row, { title: c.title || t('Limit'), detail: [c.project && t('project {name}', { name: c.project }), c.service && t('service {name}', { name: c.service }), c.enforce ? t('refuses spend past it') : t('warns only')]
108
121
  .filter(Boolean)
109
- .join(' · '), trailing: _jsx(SizableText, { size: "$2", color: "$ink", children: c.threshold ? money(c.threshold) : '—' }) }, c.id))), admin ? (_jsxs(XStack, { gap: "$2", items: "center", px: "$3", py: "$3", borderTopWidth: 1, borderColor: "$borderColor", flexWrap: "wrap", children: [_jsx(YStack, { width: 160, children: _jsx(Input, { value: ceiling, onChangeText: setCeiling, "aria-label": "Monthly limit in dollars", placeholder: cap ? String(cap.threshold / 100) : '500', inputMode: "decimal" }) }), _jsxs(XStack, { flex: 1, gap: "$2", justify: "flex-end", children: [cap ? (_jsx(Button, { size: "sm", variant: "ghost", disabled: working, onPress: () => void act(() => lift(t, cap.id), 'The monthly limit is lifted', ceilings.reload), children: "Remove" })) : null, _jsx(Button, { size: "sm", variant: "outline", disabled: working || !ceiling.trim(), onPress: setLimit, children: cap ? 'Change limit' : 'Set limit' })] })] })) : null] })), !admin ? (_jsx(SizableText, { size: "$1", color: "$soft", children: "An org admin sets the monthly limit." })) : null] }), _jsx(Note, { children: note })] }));
122
+ .join(' · '), trailing: _jsx(SizableText, { size: "$2", color: "$ink", children: c.threshold ? money(c.threshold) : '—' }) }, c.id))), admin ? (_jsxs(XStack, { gap: "$2", items: "center", px: "$3", py: "$3", borderTopWidth: 1, borderColor: "$borderColor", flexWrap: "wrap", children: [_jsx(YStack, { width: 160, children: _jsx(Input, { value: ceiling, onChangeText: setCeiling, "aria-label": t('Monthly limit in dollars'), placeholder: cap ? String(cap.threshold / 100) : '500', inputMode: "decimal" }) }), _jsxs(XStack, { flex: 1, gap: "$2", justify: "flex-end", children: [cap ? (_jsx(Button, { size: "sm", variant: "ghost", disabled: working, onPress: () => void act(() => lift(target, cap.id), t('The monthly limit is lifted'), ceilings.reload), children: t('Remove') })) : null, _jsx(Button, { size: "sm", variant: "outline", disabled: working || !ceiling.trim(), onPress: setLimit, children: cap ? t('Change limit') : t('Set limit') })] })] })) : null] })), !admin ? (_jsx(SizableText, { size: "$1", color: "$soft", children: t('An org admin sets the monthly limit.') })) : null] }), _jsx(Note, { children: note })] }));
110
123
  }
@@ -5,49 +5,56 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
5
5
  * One opens in the same editor a run shows beside it. A member reads; an org
6
6
  * admin saves, sets secrets and forgets an environment, which is the platform's
7
7
  * rule and not a choice made here.
8
+ *
9
+ * Its words are the reader's language (@hanzo/i18n `t`); a codebase's name is
10
+ * its own, drawn as given.
8
11
  */
9
12
  import { SizableText, XStack, YStack } from '@hanzo/gui';
10
13
  import { ChevronLeft, ChevronRight } from '@hanzogui/lucide-icons-2';
14
+ import { phrase } from '@hanzo/i18n';
15
+ import { useI18n } from '@hanzo/i18n/react';
11
16
  import { Button } from '@hanzo/ui';
12
17
  import { useState } from 'react';
18
+ import { told } from '../api/call.js';
13
19
  import { environments, forget } from '../api/environment.js';
14
20
  import { Confirm } from '../ask.js';
15
21
  import { useRead } from '../data.js';
16
22
  import { Environment } from '../environment.js';
17
23
  import { useHost, useTarget } from '../host.js';
18
24
  import { Card, Heading, Note, Row, Soft } from './ui.js';
19
- const STATE = { none: 'Not set up', proposed: 'Proposed, waiting for review', ready: 'Ready' };
20
- /** The line under a codebase: where it stands, its secrets, when it was saved. */
21
- function about(e) {
22
- const parts = [STATE[e.state]];
25
+ const STATE = { none: phrase('Not set up'), proposed: phrase('Proposed, waiting for review'), ready: phrase('Ready') };
26
+ /** The line under a codebase: where it stands, its secrets, when it was saved, each fact in the reader's words. */
27
+ function about(e, t) {
28
+ const parts = [t(STATE[e.state])];
23
29
  if (e.secrets.length)
24
- parts.push(e.secrets.length === 1 ? '1 secret' : `${e.secrets.length} secrets`);
30
+ parts.push(e.secrets.length === 1 ? t('1 secret') : t('{n} secrets', { n: e.secrets.length }));
25
31
  const when = e.updated ? new Date(e.updated) : null;
26
32
  if (when && !Number.isNaN(when.getTime()))
27
- parts.push(`Updated ${when.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}`);
33
+ parts.push(t('Updated {date}', { date: when.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' }) }));
28
34
  return parts.join(' · ');
29
35
  }
30
36
  export function Environments() {
37
+ const { t } = useI18n();
31
38
  const host = useHost();
32
- const t = useTarget();
39
+ const target = useTarget();
33
40
  const signed = Boolean(host.person);
34
- const list = useRead(signed ? () => environments(t) : null, [], [t, signed]);
41
+ const list = useRead(signed ? () => environments(target) : null, [], [target, signed]);
35
42
  const [open, setOpen] = useState('');
36
43
  const [forgetting, setForgetting] = useState(false);
37
44
  const [note, setNote] = useState('');
38
45
  if (!signed) {
39
- return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "Environments" }), _jsx(Soft, { children: "Sign in to see your organization\u2019s environments." })] }));
46
+ return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: t('Environments') }), _jsx(Soft, { children: t('Sign in to see your organization’s environments.') })] }));
40
47
  }
41
48
  if (open) {
42
- return (_jsxs(YStack, { gap: "$5", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsxs(Button, { size: "sm", variant: "ghost", onPress: () => setOpen(''), children: [_jsx(ChevronLeft, { size: 14 }), " All environments"] }), _jsx(XStack, { flex: 1 }), host.admin ? (_jsx(Button, { size: "sm", variant: "outline", onPress: () => setForgetting(true), children: "Forget" })) : null] }), _jsx(Environment, { repo: open, busy: false }), _jsx(Confirm, { open: forgetting, onOpenChange: setForgetting, title: `Forget ${open}’s environment?`, says: "Later runs on this codebase start bare: its scripts are dropped and every secret it holds is removed from KMS. This cannot be undone.", act: "Forget", run: async () => {
43
- await forget(t, open);
44
- setNote(`${open}’s environment is forgotten`);
49
+ return (_jsxs(YStack, { gap: "$5", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsxs(Button, { size: "sm", variant: "ghost", onPress: () => setOpen(''), children: [_jsx(ChevronLeft, { size: 14, 'data-i18n-mirror': '' }), " ", t('All environments')] }), _jsx(XStack, { flex: 1 }), host.admin ? (_jsx(Button, { size: "sm", variant: "outline", onPress: () => setForgetting(true), children: t('Forget') })) : null] }), _jsx(Environment, { repo: open, busy: false }), _jsx(Confirm, { open: forgetting, onOpenChange: setForgetting, title: t('Forget {repo}’s environment?', { repo: open }), says: t('Later runs on this codebase start bare: its scripts are dropped and every secret it holds is removed from KMS. This cannot be undone.'), act: t('Forget'), run: async () => {
50
+ await forget(target, open);
51
+ setNote(t('{repo}’s environment is forgotten', { repo: open }));
45
52
  setOpen('');
46
53
  list.reload();
47
54
  } })] }));
48
55
  }
49
- return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "Environments", detail: "What a sandbox run does to a codebase before its agent starts: the install script, the start command, and the secrets it exports." }), list.error ? (_jsx(Soft, { children: list.error.message })) : list.loading && list.value.length === 0 ? (_jsx(Soft, { children: "Reading environments\u2026" })) : list.value.length === 0 ? (_jsx(Soft, { children: "No codebase has an environment yet. Set one up from New, or from a run\u2019s Environment tab." })) : (_jsx(Card, { children: list.value.map((e, i) => (_jsx(YStack, { render: "button", "aria-label": `Open ${e.repo}`, onPress: () => {
56
+ return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: t('Environments'), detail: t('What a sandbox run does to a codebase before its agent starts: the install script, the start command, and the secrets it exports.') }), list.error ? (_jsx(Soft, { children: told(list.error, t) })) : list.loading && list.value.length === 0 ? (_jsx(Soft, { children: t('Reading environments…') })) : list.value.length === 0 ? (_jsx(Soft, { children: t('No codebase has an environment yet. Set one up from New, or from a run’s Environment tab.') })) : (_jsx(Card, { children: list.value.map((e, i) => (_jsx(YStack, { render: "button", "aria-label": t('Open {repo}', { repo: e.repo }), onPress: () => {
50
57
  setNote('');
51
58
  setOpen(e.repo);
52
- }, cursor: "pointer", hoverStyle: { bg: '$hover' }, style: { textAlign: 'left' }, children: _jsx(Row, { first: i === 0, title: e.repo, detail: about(e), trailing: _jsx(ChevronRight, { size: 16, opacity: 0.6 }) }) }, e.repo))) })), !host.admin ? (_jsx(SizableText, { size: "$1", color: "$soft", children: "An org admin saves an environment and sets its secrets." })) : null, _jsx(Note, { children: note })] }));
59
+ }, cursor: "pointer", hoverStyle: { bg: '$hover' }, "$platform-web": { textAlign: 'start' }, children: _jsx(Row, { first: i === 0, title: e.repo, detail: about(e, t), trailing: _jsx(ChevronRight, { size: 16, opacity: 0.6, 'data-i18n-mirror': '' }) }) }, e.repo))) })), !host.admin ? (_jsx(SizableText, { size: "$1", color: "$soft", children: t('An org admin saves an environment and sets its secrets.') })) : null, _jsx(Note, { children: note })] }));
53
60
  }
@@ -3,43 +3,55 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  * General: how the builder looks and listens — the theme, the text size, the
4
4
  * motion, and the language dictation hears. Each choice is saved to the
5
5
  * person's own settings as it is made, so it follows them to every device.
6
+ *
7
+ * Its words are the reader's language (@hanzo/i18n `t`); a language is named
8
+ * in its own words, as given.
6
9
  */
7
10
  import { YStack } from '@hanzo/gui';
11
+ import { phrase } from '@hanzo/i18n';
12
+ import { useI18n } from '@hanzo/i18n/react';
8
13
  import { ChipSelect } from '@hanzo/ui/product';
9
14
  import { useState } from 'react';
15
+ import { told } from '../api/call.js';
10
16
  import { useHost } from '../host.js';
11
17
  import { usePrefs } from '../prefs.js';
12
18
  import { LANGUAGES, spoken } from '../voice.js';
13
19
  import { Card, Group, Heading, Note, Row, Soft } from './ui.js';
20
+ /** The choices, each word English and marked (`phrase`): drawn through `t`. */
14
21
  const THEMES = [
15
- { id: 'system', label: 'System' },
16
- { id: 'light', label: 'Light' },
17
- { id: 'dark', label: 'Dark' },
22
+ { id: 'system', label: phrase('System') },
23
+ { id: 'light', label: phrase('Light') },
24
+ { id: 'dark', label: phrase('Dark') },
18
25
  ];
19
26
  const TEXTS = [
20
- { id: 'small', label: 'Small' },
21
- { id: 'medium', label: 'Medium' },
22
- { id: 'large', label: 'Large' },
27
+ { id: 'small', label: phrase('Small') },
28
+ { id: 'medium', label: phrase('Medium') },
29
+ { id: 'large', label: phrase('Large') },
23
30
  ];
24
31
  const MOTIONS = [
25
- { id: 'system', label: 'System', hint: 'As this device is set' },
26
- { id: 'reduced', label: 'Reduced', hint: 'No animation anywhere' },
32
+ { id: 'system', label: phrase('System'), hint: phrase('As this device is set') },
33
+ { id: 'reduced', label: phrase('Reduced'), hint: phrase('No animation anywhere') },
27
34
  ];
28
35
  export function General() {
36
+ const { t } = useI18n();
29
37
  const host = useHost();
30
38
  const { prefs, save } = usePrefs();
31
39
  const [note, setNote] = useState('');
32
40
  if (!host.person) {
33
- return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "General" }), _jsx(Soft, { children: "Sign in to see your settings." })] }));
41
+ return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: t('General') }), _jsx(Soft, { children: t('Sign in to see your settings.') })] }));
34
42
  }
35
43
  const keep = (patch) => {
36
44
  setNote('');
37
- save(patch).catch((e) => setNote(e.message));
45
+ save(patch).catch((e) => setNote(told(e, t)));
38
46
  };
47
+ // The choices in the reader's words, kept by id.
48
+ const themes = THEMES.map((x) => ({ ...x, label: t(x.label) }));
49
+ const texts = TEXTS.map((x) => ({ ...x, label: t(x.label) }));
50
+ const motions = MOTIONS.map((x) => ({ ...x, label: t(x.label), hint: t(x.hint) }));
39
51
  // Each choice is one of its list: a saved one was read against it, and a host's theme is one of THEMES.
40
- const theme = THEMES.find((x) => x.id === (prefs.theme ?? host.theme ?? 'dark'));
41
- const text = TEXTS.find((x) => x.id === (prefs.text ?? 'medium'));
42
- const motion = MOTIONS.find((x) => x.id === (prefs.motion ?? 'system'));
52
+ const theme = themes.find((x) => x.id === (prefs.theme ?? host.theme ?? 'dark'));
53
+ const text = texts.find((x) => x.id === (prefs.text ?? 'medium'));
54
+ const motion = motions.find((x) => x.id === (prefs.motion ?? 'system'));
43
55
  const language = LANGUAGES.find((l) => l.id === prefs.language) ?? LANGUAGES.find((l) => l.id === spoken());
44
- return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: "General", detail: "How Hanzo looks and listens. Saved to your account, so it follows you." }), _jsx(Group, { title: "Appearance", children: _jsxs(Card, { children: [host.chooseTheme ? (_jsx(Row, { first: true, title: "Theme", detail: "System follows this device\u2019s light or dark setting.", trailing: _jsx(ChipSelect, { name: "Theme", label: theme.label, chosen: theme, items: THEMES, onChange: (x) => keep({ theme: x.id }), placement: "bottom-end", width: 200 }) })) : null, _jsx(Row, { first: !host.chooseTheme, title: "Text size", detail: "The transcript, the composer and every screen.", trailing: _jsx(ChipSelect, { name: "Text size", label: text.label, chosen: text, items: TEXTS, onChange: (x) => keep({ text: x.id === 'medium' ? null : x.id }), placement: "bottom-end", width: 200 }) }), _jsx(Row, { title: "Motion", detail: "Reduced stops animations and transitions on this page.", trailing: _jsx(ChipSelect, { name: "Motion", label: motion.label, chosen: motion, items: MOTIONS, onChange: (x) => keep({ motion: x.id === 'system' ? null : x.id }), placement: "bottom-end", width: 220 }) })] }) }), _jsx(Group, { title: "Voice", children: _jsx(Card, { children: _jsx(Row, { first: true, title: "Dictation language", detail: "What the microphone in the composer listens for.", trailing: _jsx(ChipSelect, { name: "Dictation language", label: language.label, chosen: language, items: LANGUAGES, onChange: (l) => keep({ language: l.id }), placeholder: "Search languages\u2026", placement: "bottom-end", width: 220 }) }) }) }), _jsx(Note, { children: note })] }));
56
+ return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: t('General'), detail: t('How Hanzo looks and listens. Saved to your account, so it follows you.') }), _jsx(Group, { title: t('Appearance'), children: _jsxs(Card, { children: [host.chooseTheme ? (_jsx(Row, { first: true, title: t('Theme'), detail: t('System follows this device’s light or dark setting.'), trailing: _jsx(ChipSelect, { name: t('Theme'), label: theme.label, chosen: theme, items: themes, onChange: (x) => keep({ theme: x.id }), placement: "bottom-end", width: 200 }) })) : null, _jsx(Row, { first: !host.chooseTheme, title: t('Text size'), detail: t('The transcript, the composer and every screen.'), trailing: _jsx(ChipSelect, { name: t('Text size'), label: text.label, chosen: text, items: texts, onChange: (x) => keep({ text: x.id === 'medium' ? null : x.id }), placement: "bottom-end", width: 200 }) }), _jsx(Row, { title: t('Motion'), detail: t('Reduced stops animations and transitions on this page.'), trailing: _jsx(ChipSelect, { name: t('Motion'), label: motion.label, chosen: motion, items: motions, onChange: (x) => keep({ motion: x.id === 'system' ? null : x.id }), placement: "bottom-end", width: 220 }) })] }) }), _jsx(Group, { title: t('Voice'), children: _jsx(Card, { children: _jsx(Row, { first: true, title: t('Dictation language'), detail: t('What the microphone in the composer listens for.'), trailing: _jsx(ChipSelect, { name: t('Dictation language'), label: language.label, chosen: language, items: LANGUAGES, onChange: (l) => keep({ language: l.id }), placeholder: t('Search languages…'), placement: "bottom-end", width: 220 }) }) }) }), _jsx(Note, { children: note })] }));
45
57
  }
@@ -4,19 +4,22 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
4
  * workspace's environments, machines and keys, what the agent brings to a run,
5
5
  * and the organization's integrations, members, usage and notifications. A
6
6
  * section is its own address, so it can be linked to and comes back on reload.
7
+ * Its words are the reader's language (@hanzo/i18n `t`).
7
8
  */
8
9
  import { SizableText, XStack, YStack } from '@hanzo/gui';
10
+ import { useI18n } from '@hanzo/i18n/react';
9
11
  import { useHost } from '../host.js';
10
12
  import { path } from '../route.js';
11
13
  import { heading } from './list.js';
12
14
  import { ENTRIES, GROUPS } from './sections.js';
13
15
  export function Settings({ section }) {
16
+ const { t } = useI18n();
14
17
  const host = useHost();
15
18
  // Every section the route grammar names has an entry.
16
19
  const entry = ENTRIES.find((e) => e.id === section);
17
20
  const go = (id) => host.go(path({ kind: 'settings', section: id }));
18
- return (_jsxs(XStack, { flex: 1, minH: 0, minW: 0, children: [host.sections ? null : (_jsxs(YStack, { role: "navigation", "aria-label": "Settings", width: 220, shrink: 0, borderRightWidth: 1, borderColor: "$borderColor", px: "$2", py: "$4", gap: "$4", overflow: "scroll", "$max-md": { display: 'none' }, children: [_jsx(SizableText, { size: "$4", color: "$ink", px: "$2", children: "Settings" }), GROUPS.map((g) => {
21
+ return (_jsxs(XStack, { flex: 1, minH: 0, minW: 0, children: [host.sections ? null : (_jsxs(YStack, { role: "navigation", "aria-label": t('Settings'), width: 220, shrink: 0, borderInlineEndWidth: 1, borderColor: "$borderColor", px: "$2", py: "$4", gap: "$4", overflow: "scroll", "$max-md": { display: 'none' }, children: [_jsx(SizableText, { size: "$4", color: "$ink", px: "$2", children: t('Settings') }), GROUPS.map((g) => {
19
22
  const list = ENTRIES.filter((e) => e.group === g);
20
- return (_jsxs(YStack, { gap: "$0.5", children: [heading(g) ? (_jsx(SizableText, { size: "$1", color: "$soft", px: "$2", pb: "$1", children: g })) : null, list.map((e) => (_jsx(XStack, { render: "button", "aria-label": e.label, "aria-current": e.id === entry.id ? 'page' : undefined, onPress: () => go(e.id), px: "$2", py: "$1.5", rounded: "$2", bg: e.id === entry.id ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, children: _jsx(SizableText, { size: "$2", color: e.id === entry.id ? '$ink' : '$soft', children: e.label }) }, e.id)))] }, g));
21
- })] })), _jsxs(YStack, { flex: 1, minW: 0, minH: 0, overflow: "scroll", children: [_jsx(XStack, { flexWrap: "wrap", gap: "$1.5", px: "$4", pt: "$3", $md: { display: 'none' }, children: ENTRIES.map((e) => (_jsx(XStack, { render: "button", "aria-label": e.label, onPress: () => go(e.id), px: "$2.5", py: "$1", rounded: "$10", borderWidth: 1, borderColor: e.id === entry.id ? '$ink' : '$borderColor', children: _jsx(SizableText, { size: "$1", color: e.id === entry.id ? '$ink' : '$soft', children: e.label }) }, e.id))) }), _jsx(YStack, { width: "100%", maxW: 760, self: "center", px: "$5", py: "$6", children: entry.body() })] })] }));
23
+ return (_jsxs(YStack, { gap: "$0.5", children: [heading(g) ? (_jsx(SizableText, { size: "$1", color: "$soft", px: "$2", pb: "$1", children: t(heading(g)) })) : null, list.map((e) => (_jsx(XStack, { render: "button", "aria-label": t(e.label), "aria-current": e.id === entry.id ? 'page' : undefined, onPress: () => go(e.id), px: "$2", py: "$1.5", rounded: "$2", bg: e.id === entry.id ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, children: _jsx(SizableText, { size: "$2", color: e.id === entry.id ? '$ink' : '$soft', children: t(e.label) }) }, e.id)))] }, g));
24
+ })] })), _jsxs(YStack, { flex: 1, minW: 0, minH: 0, overflow: "scroll", children: [_jsx(XStack, { flexWrap: "wrap", gap: "$1.5", px: "$4", pt: "$3", $md: { display: 'none' }, children: ENTRIES.map((e) => (_jsx(XStack, { render: "button", "aria-label": t(e.label), onPress: () => go(e.id), px: "$2.5", py: "$1", rounded: "$10", borderWidth: 1, borderColor: e.id === entry.id ? '$ink' : '$borderColor', children: _jsx(SizableText, { size: "$1", color: e.id === entry.id ? '$ink' : '$soft', children: t(e.label) }) }, e.id))) }), _jsx(YStack, { width: "100%", maxW: 760, self: "center", px: "$5", py: "$6", children: entry.body() })] })] }));
22
25
  }
@@ -9,11 +9,17 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
9
  * (../back.ts), which this page finishes and reports. Anyone connects their own
10
10
  * GitHub; installing the App and connecting Slack are an org admin's, which is
11
11
  * the platform's rule.
12
+ *
13
+ * Its words are the reader's language (@hanzo/i18n `t`); an account's,
14
+ * a channel's and a provider's names, and the platform's own notes, are drawn
15
+ * as given.
12
16
  */
13
17
  import { SizableText, XStack, YStack } from '@hanzo/gui';
18
+ import { useI18n } from '@hanzo/i18n/react';
14
19
  import { Github, Hash, Lock, Megaphone, Slack } from '@hanzogui/lucide-icons-2';
15
20
  import { Button } from '@hanzo/ui';
16
21
  import { useState } from 'react';
22
+ import { told } from '../api/call.js';
17
23
  import { connect, connection, disconnect as unlinkGithub, installations } from '../api/github.js';
18
24
  import { authorize, channels, disconnect, read, SOCIAL } from '../api/provider.js';
19
25
  import { here, useBack } from '../back.js';
@@ -21,25 +27,26 @@ import { useRead } from '../data.js';
21
27
  import { useHost, useTarget } from '../host.js';
22
28
  import { Card, day, Group, Heading, Note, Row, Soft } from './ui.js';
23
29
  export function Integrations() {
30
+ const { t } = useI18n();
24
31
  const host = useHost();
25
- const t = useTarget();
32
+ const target = useTarget();
26
33
  const signed = Boolean(host.person);
27
- const mine = useRead(signed ? () => connection(t) : null, null, [t, signed]);
28
- const installed = useRead(signed ? () => installations(t) : null, [], [t, signed]);
29
- const slack = useRead(signed ? () => read(t, 'slack') : null, null, [t, signed]);
34
+ const mine = useRead(signed ? () => connection(target) : null, null, [target, signed]);
35
+ const installed = useRead(signed ? () => installations(target) : null, [], [target, signed]);
36
+ const slack = useRead(signed ? () => read(target, 'slack') : null, null, [target, signed]);
30
37
  const joined = slack.value?.connected ?? false;
31
- const rooms = useRead(signed && joined ? () => channels(t) : null, { channels: [], next: '' }, [t, signed, joined]);
38
+ const rooms = useRead(signed && joined ? () => channels(target) : null, { channels: [], next: '' }, [target, signed, joined]);
32
39
  const social = useSocial(signed, SOCIAL.map((p) => p.id));
33
40
  const [working, setWorking] = useState(false);
34
41
  const [note, setNote] = useState('');
35
- useBack(t, signed, setNote, () => {
42
+ useBack(target, signed, setNote, () => {
36
43
  mine.reload();
37
44
  installed.reload();
38
45
  slack.reload();
39
46
  social.reload();
40
47
  });
41
48
  if (!signed) {
42
- return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "Integrations" }), _jsx(Soft, { children: "Sign in to see this organization\u2019s integrations." })] }));
49
+ return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: t('Integrations') }), _jsx(Soft, { children: t('Sign in to see this organization’s integrations.') })] }));
43
50
  }
44
51
  const act = async (what, done, after) => {
45
52
  setWorking(true);
@@ -50,7 +57,7 @@ export function Integrations() {
50
57
  setNote(done);
51
58
  }
52
59
  catch (e) {
53
- setNote(e.message);
60
+ setNote(told(e, t));
54
61
  }
55
62
  finally {
56
63
  setWorking(false);
@@ -64,42 +71,49 @@ export function Integrations() {
64
71
  host.open(await where());
65
72
  }
66
73
  catch (e) {
67
- setNote(e.message);
74
+ setNote(told(e, t));
68
75
  setWorking(false);
69
76
  }
70
77
  };
71
78
  const me = mine.value;
72
79
  const s = slack.value;
73
- return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: "Integrations", detail: `What ${host.org ?? 'this organization'} and you have connected.` }), _jsxs(Group, { title: "GitHub", detail: "Your connection brings your repositories; the App brings an account\u2019s to the organization.", children: [_jsxs(Card, { children: [_jsx(Row, { first: true, leading: _jsx(Github, { size: 16 }), title: "Your GitHub", detail: mine.error
74
- ? mine.error.message
80
+ return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: t('Integrations'), detail: host.org ? t('What {org} and you have connected.', { org: host.org }) : t('What this organization and you have connected.') }), _jsxs(Group, { title: "GitHub", detail: t('Your connection brings your repositories; the App brings an account’s to the organization.'), children: [_jsxs(Card, { children: [_jsx(Row, { first: true, leading: _jsx(Github, { size: 16 }), title: t('Your GitHub'), detail: mine.error
81
+ ? told(mine.error, t)
75
82
  : !me
76
- ? 'Reading…'
83
+ ? t('Reading…')
77
84
  : !me.configured
78
- ? 'This deployment cannot connect GitHub yet.'
85
+ ? t('This deployment cannot connect GitHub yet.')
79
86
  : me.connected
80
- ? `Connected as @${me.login}`
81
- : 'Not connected', trailing: me?.configured ? (me.connected ? (_jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void act(() => unlinkGithub(t), 'Your GitHub is disconnected', mine.reload), children: "Disconnect" })) : (_jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void away(() => connect(t, here())), children: "Connect" }))) : null }), installed.error ? (_jsx(Row, { title: "App installations", detail: installed.error.message })) : (installed.value.map((i) => (_jsx(Row, { leading: _jsx(Github, { size: 16 }), title: i.login, detail: [i.type, i.grant === 'all' ? 'every repository' : i.grant === 'selected' ? 'selected repositories' : ''].filter(Boolean).join(' · '), trailing: _jsx(SizableText, { size: "$1", color: i.connected ? '$ink' : '$soft', children: i.connected ? 'Installed' : 'Removed on GitHub' }) }, i.login)))), !installed.error && !installed.loading && installed.value.length === 0 ? (_jsx(Row, { title: "No GitHub accounts installed", detail: "Install the App on a GitHub account to bring its repositories here." })) : null] }), host.admin ? (_jsx(XStack, { children: _jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void away(() => authorize(t, 'github', here())), children: "Install on a GitHub account" }) })) : (_jsx(SizableText, { size: "$1", color: "$soft", children: "An org admin installs the App on a GitHub account." }))] }), _jsxs(Group, { title: "Slack", detail: "Talk to Hanzo from your workspace\u2019s channels.", children: [_jsxs(Card, { children: [_jsx(Row, { first: true, leading: _jsx(Slack, { size: 16 }), title: s?.connected && s.account ? s.account : 'Slack', detail: slack.error
82
- ? slack.error.message
87
+ ? t('Connected as @{login}', { login: me.login })
88
+ : t('Not connected'), trailing: me?.configured ? (me.connected ? (_jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void act(() => unlinkGithub(target), t('Your GitHub is disconnected'), mine.reload), children: t('Disconnect') })) : (_jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void away(() => connect(target, here())), children: t('Connect') }))) : null }), installed.error ? (_jsx(Row, { title: t('App installations'), detail: told(installed.error, t) })) : (installed.value.map((i) => (_jsx(Row, { leading: _jsx(Github, { size: 16 }), title: i.login, detail: [i.type, i.grant === 'all' ? t('every repository') : i.grant === 'selected' ? t('selected repositories') : ''].filter(Boolean).join(' · '), trailing: _jsx(SizableText, { size: "$1", color: i.connected ? '$ink' : '$soft', children: i.connected ? t('Installed') : t('Removed on GitHub') }) }, i.login)))), !installed.error && !installed.loading && installed.value.length === 0 ? (_jsx(Row, { title: t('No GitHub accounts installed'), detail: t('Install the App on a GitHub account to bring its repositories here.') })) : null] }), host.admin ? (_jsx(XStack, { children: _jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void away(() => authorize(target, 'github', here())), children: t('Install on a GitHub account') }) })) : (_jsx(SizableText, { size: "$1", color: "$soft", children: t('An org admin installs the App on a GitHub account.') }))] }), _jsxs(Group, { title: "Slack", detail: t('Talk to Hanzo from your workspace’s channels.'), children: [_jsxs(Card, { children: [_jsx(Row, { first: true, leading: _jsx(Slack, { size: 16 }), title: s?.connected && s.account ? s.account : 'Slack', detail: slack.error
89
+ ? told(slack.error, t)
83
90
  : !s
84
- ? 'Reading…'
91
+ ? t('Reading…')
85
92
  : s.connected
86
- ? `Connected${s.since ? ` since ${day(s.since)}` : ''}`
93
+ ? s.since
94
+ ? t('Connected since {day}', { day: day(s.since) })
95
+ : t('Connected')
87
96
  : s.available
88
- ? 'Not connected'
89
- : s.note || 'This deployment cannot connect Slack yet.', trailing: host.admin && s && (s.connected || s.available) ? (s.connected ? (_jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void act(() => disconnect(t, 'slack'), 'Slack is disconnected', slack.reload), children: "Disconnect" })) : (_jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void away(() => authorize(t, 'slack', here())), children: "Connect" }))) : null }), joined && rooms.error ? _jsx(Row, { title: "Channels", detail: rooms.error.message }) : null, joined
90
- ? rooms.value.channels.map((c) => (_jsx(Row, { leading: c.private ? _jsx(Lock, { size: 14 }) : _jsx(Hash, { size: 14 }), title: c.name || c.id, trailing: _jsx(SizableText, { size: "$1", color: c.member ? '$ink' : '$soft', children: c.member ? 'Joined' : 'Not joined' }) }, c.id)))
91
- : null] }), !host.admin ? (_jsx(SizableText, { size: "$1", color: "$soft", children: "An org admin connects the Slack workspace." })) : null] }), _jsxs(Group, { title: "Social accounts", detail: "Where automations post what their agent writes.", children: [_jsx(Card, { children: SOCIAL.map((p, i) => {
97
+ ? t('Not connected')
98
+ : s.note || t('This deployment cannot connect Slack yet.'), trailing: host.admin && s && (s.connected || s.available) ? (s.connected ? (_jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void act(() => disconnect(target, 'slack'), t('{name} is disconnected', { name: 'Slack' }), slack.reload), children: t('Disconnect') })) : (_jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void away(() => authorize(target, 'slack', here())), children: t('Connect') }))) : null }), joined && rooms.error ? _jsx(Row, { title: t('Channels'), detail: told(rooms.error, t) }) : null, joined
99
+ ? rooms.value.channels.map((c) => (_jsx(Row, { leading: c.private ? _jsx(Lock, { size: 14 }) : _jsx(Hash, { size: 14 }), title: c.name || c.id, trailing: _jsx(SizableText, { size: "$1", color: c.member ? '$ink' : '$soft', children: c.member ? t('Joined') : t('Not joined') }) }, c.id)))
100
+ : null] }), !host.admin ? (_jsx(SizableText, { size: "$1", color: "$soft", children: t('An org admin connects the Slack workspace.') })) : null] }), _jsxs(Group, { title: t('Social accounts'), detail: t('Where automations post what their agent writes.'), children: [_jsx(Card, { children: SOCIAL.map((p, i) => {
92
101
  const c = social.value[p.id];
93
102
  return (_jsx(Row, { first: i === 0, leading: _jsx(Megaphone, { size: 16 }), title: c?.connected && c.account ? `${p.name} · ${c.account}` : p.name, detail: social.error
94
- ? social.error.message
103
+ ? told(social.error, t)
95
104
  : !c
96
- ? 'Reading…'
105
+ ? t('Reading…')
97
106
  : c.connected
98
- ? `Connected${c.since ? ` since ${day(c.since)}` : ''}${p.takes ? `. ${p.takes}` : ''}`
107
+ ? // Connected, then what the account takes, as two sentences.
108
+ p.takes
109
+ ? `${c.since ? t('Connected since {day}.', { day: day(c.since) }) : t('Connected.')} ${t(p.takes)}`
110
+ : c.since
111
+ ? t('Connected since {day}', { day: day(c.since) })
112
+ : t('Connected')
99
113
  : c.available
100
- ? p.takes || 'Not connected'
101
- : p.takes ? `${p.takes} ${p.needs}` : p.needs, trailing: host.admin && c && (c.connected || c.available) ? (c.connected ? (_jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void act(() => disconnect(t, p.id), `${p.name} is disconnected`, social.reload), children: "Disconnect" })) : (_jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void away(() => authorize(t, p.id, here())), "aria-label": `Connect ${p.name}`, children: "Connect" }))) : null }, p.id));
102
- }) }), !host.admin ? (_jsx(SizableText, { size: "$1", color: "$soft", children: "An org admin connects the organization\u2019s social accounts." })) : null] }), _jsx(Note, { children: note })] }));
114
+ ? p.takes ? t(p.takes) : t('Not connected')
115
+ : p.takes ? `${t(p.takes)} ${t(p.needs)}` : t(p.needs), trailing: host.admin && c && (c.connected || c.available) ? (c.connected ? (_jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void act(() => disconnect(target, p.id), t('{name} is disconnected', { name: p.name }), social.reload), children: t('Disconnect') })) : (_jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void away(() => authorize(target, p.id, here())), "aria-label": t('Connect {name}', { name: p.name }), children: t('Connect') }))) : null }, p.id));
116
+ }) }), !host.admin ? (_jsx(SizableText, { size: "$1", color: "$soft", children: t('An org admin connects the organization’s social accounts.') })) : null] }), _jsx(Note, { children: note })] }));
103
117
  }
104
118
  /**
105
119
  * The named social connectors, by id: whether each can be connected here and