@cat-factory/app 0.47.4 → 0.47.5

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.
@@ -10,6 +10,7 @@ import type { TaskSearchResult, TaskSourceKind } from '~/types/domain'
10
10
  import type { AddTaskPrefill } from '~/stores/ui'
11
11
  import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
12
12
 
13
+ const { t } = useI18n()
13
14
  const ui = useUiStore()
14
15
  const tasks = useTasksStore()
15
16
  const board = useBoardStore()
@@ -30,7 +31,7 @@ const importing = ref(false)
30
31
  // When opened from a service frame the modal is the "create a task from an issue"
31
32
  // surface; opened standalone it's the general tracker-issue browser/importer.
32
33
  const title = computed(() =>
33
- ui.taskImport?.containerId ? 'Create task from issue' : 'Tracker issues',
34
+ ui.taskImport?.containerId ? t('tasks.import.titleCreate') : t('tasks.import.titleBrowse'),
34
35
  )
35
36
 
36
37
  const sourceItems = computed(() =>
@@ -152,10 +153,13 @@ async function doImport() {
152
153
  try {
153
154
  const task = await tasks.importTask(source.value, value)
154
155
  ref_.value = ''
155
- toast.add({ title: `Imported "${task.title}"`, icon: 'i-lucide-file-down' })
156
+ toast.add({
157
+ title: t('tasks.import.imported', { title: task.title }),
158
+ icon: 'i-lucide-file-down',
159
+ })
156
160
  } catch (e) {
157
161
  toast.add({
158
- title: 'Import failed',
162
+ title: t('tasks.import.importFailed'),
159
163
  description: e instanceof Error ? e.message : String(e),
160
164
  icon: 'i-lucide-triangle-alert',
161
165
  color: 'error',
@@ -178,13 +182,13 @@ async function doSpawnEpic() {
178
182
  ui.closeTaskImport()
179
183
  ui.select(epic.id)
180
184
  toast.add({
181
- title: `Spawned epic "${epic.title}"`,
182
- description: `${spawned.length} child task(s) created`,
185
+ title: t('tasks.import.epicSpawned', { title: epic.title }),
186
+ description: t('tasks.import.epicChildren', { count: spawned.length }, spawned.length),
183
187
  icon: 'i-lucide-layers',
184
188
  })
185
189
  } catch (e) {
186
190
  toast.add({
187
- title: 'Could not spawn epic',
191
+ title: t('tasks.import.epicFailed'),
188
192
  description: e instanceof Error ? e.message : String(e),
189
193
  icon: 'i-lucide-triangle-alert',
190
194
  color: 'error',
@@ -204,7 +208,7 @@ async function doSpawnEpic() {
204
208
  <!-- Empty state: no source offered (none connected/installed, or all disabled) -->
205
209
  <div v-if="!tasks.anyOffered" class="space-y-3 text-center">
206
210
  <UIcon name="i-lucide-plug" class="mx-auto h-8 w-8 text-slate-500" />
207
- <p class="text-sm text-slate-400">Connect or enable a task source first.</p>
211
+ <p class="text-sm text-slate-400">{{ t('tasks.import.connectFirst') }}</p>
208
212
  <div class="flex justify-center gap-2">
209
213
  <UButton
210
214
  v-for="s in tasks.sources"
@@ -214,19 +218,23 @@ async function doSpawnEpic() {
214
218
  :icon="s.icon"
215
219
  @click="ui.openTaskConnect(s.source)"
216
220
  >
217
- {{ s.available ? `Enable ${s.label}` : `Connect ${s.label}` }}
221
+ {{
222
+ s.available
223
+ ? t('tasks.import.enableSource', { label: s.label })
224
+ : t('tasks.import.connectSource', { label: s.label })
225
+ }}
218
226
  </UButton>
219
227
  </div>
220
228
  </div>
221
229
 
222
230
  <!-- Main form -->
223
231
  <div v-else class="space-y-4">
224
- <UFormField v-if="sourceItems.length > 1" label="Source">
232
+ <UFormField v-if="sourceItems.length > 1" :label="t('tasks.import.source')">
225
233
  <USelect v-model="source" :items="sourceItems" class="w-full" />
226
234
  </UFormField>
227
235
 
228
236
  <div class="flex items-end gap-2">
229
- <UFormField :label="descriptor?.refLabel ?? 'Issue key or URL'" class="flex-1">
237
+ <UFormField :label="descriptor?.refLabel ?? t('tasks.import.refLabel')" class="flex-1">
230
238
  <UInput
231
239
  v-model="ref_"
232
240
  :placeholder="descriptor?.refPlaceholder"
@@ -241,7 +249,7 @@ async function doSpawnEpic() {
241
249
  :disabled="!ref_.trim()"
242
250
  @click="doImport"
243
251
  >
244
- Import
252
+ {{ t('tasks.import.import') }}
245
253
  </UButton>
246
254
  <UButton
247
255
  color="primary"
@@ -251,12 +259,12 @@ async function doSpawnEpic() {
251
259
  :disabled="!ref_.trim() || !containerId"
252
260
  :title="
253
261
  containerId
254
- ? 'Spawn this epic + its children as a linked task group'
255
- : 'Pick a container for the child tasks first'
262
+ ? t('tasks.import.asEpicTitleReady')
263
+ : t('tasks.import.asEpicTitleNeedsContainer')
256
264
  "
257
265
  @click="doSpawnEpic"
258
266
  >
259
- As epic
267
+ {{ t('tasks.import.asEpic') }}
260
268
  </UButton>
261
269
  </div>
262
270
 
@@ -264,25 +272,25 @@ async function doSpawnEpic() {
264
272
  "Create tasks in" selector below covers the search-results case). -->
265
273
  <UFormField
266
274
  v-if="containerItems.length && !freshHits.length"
267
- label="Epic children container"
275
+ :label="t('tasks.import.epicChildrenContainer')"
268
276
  class="w-72"
269
277
  >
270
278
  <USelect
271
279
  v-model="containerId"
272
280
  :items="containerItems"
273
- placeholder="Pick a frame or module"
281
+ :placeholder="t('tasks.import.pickContainer')"
274
282
  class="w-full"
275
283
  />
276
284
  </UFormField>
277
285
 
278
286
  <!-- Browse: search the tracker by title so an issue can be turned into a
279
287
  task without knowing its key. -->
280
- <UFormField v-if="searchable" label="Search issues">
288
+ <UFormField v-if="searchable" :label="t('tasks.import.searchIssues')">
281
289
  <UInput
282
290
  v-model="searchQuery"
283
291
  :icon="searching ? 'i-lucide-loader-circle' : 'i-lucide-search'"
284
292
  :ui="{ leadingIcon: searching ? 'animate-spin' : '' }"
285
- placeholder="Search by title, paste an issue URL, or type an issue number…"
293
+ :placeholder="t('tasks.import.searchPlaceholder')"
286
294
  class="w-full"
287
295
  />
288
296
  </UFormField>
@@ -290,13 +298,13 @@ async function doSpawnEpic() {
290
298
  <!-- Shared target container for every "Create task" action below. -->
291
299
  <UFormField
292
300
  v-if="containerItems.length && freshHits.length"
293
- label="Create tasks in"
301
+ :label="t('tasks.import.createTasksIn')"
294
302
  class="w-72"
295
303
  >
296
304
  <USelect
297
305
  v-model="containerId"
298
306
  :items="containerItems"
299
- placeholder="Pick a frame or module"
307
+ :placeholder="t('tasks.import.pickContainer')"
300
308
  class="w-full"
301
309
  />
302
310
  </UFormField>
@@ -304,17 +312,17 @@ async function doSpawnEpic() {
304
312
  v-else-if="!containerItems.length && freshHits.length"
305
313
  class="text-[11px] text-slate-500"
306
314
  >
307
- Add a service frame to the board first to create tasks from issues.
315
+ {{ t('tasks.import.needFrameFirst') }}
308
316
  </p>
309
317
 
310
318
  <!-- Search results (not yet imported): click a hit to create a task from it
311
319
  (opens the prefilled add-task form); the icon button views it on GitHub. -->
312
320
  <div v-if="searchError" class="text-[11px] text-amber-400">
313
- Search failed: {{ searchError }}
321
+ {{ t('tasks.import.searchFailed', { error: searchError }) }}
314
322
  </div>
315
323
  <div v-if="freshHits.length" class="space-y-2">
316
324
  <h3 class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
317
- Search results
325
+ {{ t('tasks.import.searchResults') }}
318
326
  </h3>
319
327
  <div
320
328
  v-for="hit in freshHits"
@@ -325,7 +333,11 @@ async function doSpawnEpic() {
325
333
  type="button"
326
334
  class="min-w-0 flex-1 text-left disabled:cursor-not-allowed disabled:opacity-60"
327
335
  :disabled="!containerId"
328
- :title="containerId ? 'Create a task from this issue' : 'Pick a container first'"
336
+ :title="
337
+ containerId
338
+ ? t('tasks.import.createFromIssue')
339
+ : t('tasks.import.pickContainerFirst')
340
+ "
329
341
  @click="selectIssue(hit, true)"
330
342
  >
331
343
  <span class="block truncate text-sm font-medium text-white">
@@ -347,7 +359,7 @@ async function doSpawnEpic() {
347
359
  :to="hit.url"
348
360
  target="_blank"
349
361
  rel="noopener"
350
- :aria-label="`View ${hit.externalId} on GitHub`"
362
+ :aria-label="t('tasks.import.viewOnGitHub', { id: hit.externalId })"
351
363
  />
352
364
  </div>
353
365
  </div>
@@ -357,7 +369,7 @@ async function doSpawnEpic() {
357
369
  v-if="!freshHits.length && !searchQuery.trim()"
358
370
  class="text-center text-xs text-slate-500"
359
371
  >
360
- Search above, or paste an issue URL/key to create a task from it.
372
+ {{ t('tasks.import.emptyHint') }}
361
373
  </p>
362
374
  </div>
363
375
  </template>
@@ -13,6 +13,7 @@
13
13
  // before that.
14
14
  import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
15
15
 
16
+ const { t } = useI18n()
16
17
  const ui = useUiStore()
17
18
  const tasks = useTasksStore()
18
19
  const toast = useToast()
@@ -58,7 +59,7 @@ async function submit() {
58
59
  try {
59
60
  await tasks.connect(source.value, credentials)
60
61
  toast.add({
61
- title: `${descriptor.value!.label} connected`,
62
+ title: t('tasks.connect.connectedToast', { label: descriptor.value!.label }),
62
63
  icon: 'i-lucide-check',
63
64
  color: 'success',
64
65
  })
@@ -66,7 +67,7 @@ async function submit() {
66
67
  await tasks.probe()
67
68
  } catch (e) {
68
69
  toast.add({
69
- title: 'Could not connect',
70
+ title: t('tasks.connect.connectFailed'),
70
71
  description: e instanceof Error ? e.message : String(e),
71
72
  icon: 'i-lucide-triangle-alert',
72
73
  color: 'error',
@@ -81,7 +82,9 @@ async function disconnect() {
81
82
  await tasks.disconnect(source.value)
82
83
  await tasks.probe()
83
84
  toast.add({
84
- title: `${descriptor.value?.label ?? 'Source'} disconnected`,
85
+ title: t('tasks.connect.disconnectedToast', {
86
+ label: descriptor.value?.label ?? t('tasks.connect.sourceFallback'),
87
+ }),
85
88
  icon: 'i-lucide-unplug',
86
89
  })
87
90
  ui.closeTaskConnect()
@@ -94,7 +97,7 @@ async function toggleEnabled(enabled: boolean) {
94
97
  await tasks.setEnabled(source.value, enabled)
95
98
  } catch (e) {
96
99
  toast.add({
97
- title: 'Could not update',
100
+ title: t('tasks.connect.updateFailed'),
98
101
  description: e instanceof Error ? e.message : String(e),
99
102
  icon: 'i-lucide-triangle-alert',
100
103
  color: 'error',
@@ -106,25 +109,23 @@ async function toggleEnabled(enabled: boolean) {
106
109
  </script>
107
110
 
108
111
  <template>
109
- <UModal v-model:open="open" :title="descriptor?.label ?? 'Task source'">
112
+ <UModal v-model:open="open" :title="descriptor?.label ?? t('tasks.connect.title')">
110
113
  <template #title>
111
- <IntegrationBackTitle :title="descriptor?.label ?? 'Task source'" @back="back" />
114
+ <IntegrationBackTitle :title="descriptor?.label ?? t('tasks.connect.title')" @back="back" />
112
115
  </template>
113
116
  <template #body>
114
117
  <div v-if="descriptor" class="space-y-4">
115
118
  <p class="text-sm text-slate-400">
116
- {{ descriptor.label }} lets you import issues and attach them to tasks as agent context.
119
+ {{ t('tasks.connect.intro', { label: descriptor.label }) }}
117
120
  </p>
118
121
 
119
122
  <!-- Credentialless source (GitHub Issues): no form, just the on/off toggle. -->
120
123
  <template v-if="credentialless">
121
124
  <p class="text-[11px] text-slate-500">
122
- This source uses the GitHub App already installed on your workspace — there are no
123
- credentials to enter.
125
+ {{ t('tasks.connect.credentialless') }}
124
126
  </p>
125
127
  <p v-if="!available" class="text-[11px] text-amber-400">
126
- Install the workspace's GitHub App (connect GitHub repos) to offer
127
- {{ descriptor.label }}.
128
+ {{ t('tasks.connect.installAppHint', { label: descriptor.label }) }}
128
129
  </p>
129
130
  </template>
130
131
 
@@ -145,7 +146,11 @@ async function toggleEnabled(enabled: boolean) {
145
146
  </UFormField>
146
147
  </div>
147
148
  <p v-else class="text-[11px] text-slate-500">
148
- Connected{{ connection?.label ? ` to ${connection.label}` : '' }}.
149
+ {{
150
+ connection?.label
151
+ ? t('tasks.connect.connectedTo', { label: connection.label })
152
+ : t('tasks.connect.connected')
153
+ }}
149
154
  </p>
150
155
 
151
156
  <!-- The per-workspace on/off toggle, available once the source is usable. -->
@@ -154,9 +159,9 @@ async function toggleEnabled(enabled: boolean) {
154
159
  class="flex items-center justify-between gap-2 rounded-md border border-slate-800 px-3 py-2"
155
160
  >
156
161
  <div class="text-sm">
157
- <div class="font-medium text-slate-200">Offer to this workspace</div>
162
+ <div class="font-medium text-slate-200">{{ t('tasks.connect.offerToWorkspace') }}</div>
158
163
  <div class="text-[11px] text-slate-500">
159
- When off, {{ descriptor.label }} is hidden from import and linking.
164
+ {{ t('tasks.connect.offerHint', { label: descriptor.label }) }}
160
165
  </div>
161
166
  </div>
162
167
  <USwitch
@@ -174,7 +179,7 @@ async function toggleEnabled(enabled: boolean) {
174
179
  icon="i-lucide-unplug"
175
180
  @click="disconnect"
176
181
  >
177
- Disconnect
182
+ {{ t('tasks.connect.disconnect') }}
178
183
  </UButton>
179
184
  <div v-else />
180
185
  <UButton
@@ -185,7 +190,7 @@ async function toggleEnabled(enabled: boolean) {
185
190
  :disabled="!canSubmit"
186
191
  @click="submit"
187
192
  >
188
- {{ connected ? 'Update connection' : 'Connect' }}
193
+ {{ connected ? t('tasks.connect.updateConnection') : t('tasks.connect.connect') }}
189
194
  </UButton>
190
195
  </div>
191
196
  </div>
@@ -1834,5 +1834,338 @@
1834
1834
  }
1835
1835
  }
1836
1836
  }
1837
+ },
1838
+ "github": {
1839
+ "onboarding": {
1840
+ "title": "Connect cat-factory to GitHub",
1841
+ "intro": "cat-factory works by opening pull requests on your repositories. Install the GitHub App on your account or organization to continue, and you can grant it every repository or pick a subset.",
1842
+ "signedInAs": "Signed in as {login}",
1843
+ "signOut": "Sign out"
1844
+ },
1845
+ "connect": {
1846
+ "yourInstallations": "Your installations",
1847
+ "refresh": "Refresh",
1848
+ "lookingForInstallations": "Looking for installations…",
1849
+ "noInstallations": "No installations found for the App yet. Install it below, or connect by ID.",
1850
+ "installationMeta": "{targetType} · installation {id}",
1851
+ "linked": "linked",
1852
+ "linkedTitle": "Already linked to this workspace",
1853
+ "inUse": "in use",
1854
+ "inUseTitle": "Already connected to another workspace",
1855
+ "connect": "Connect",
1856
+ "or": "or",
1857
+ "installApp": "Install GitHub App",
1858
+ "orConnectById": "or connect by ID",
1859
+ "installationId": "Installation ID",
1860
+ "toast": {
1861
+ "connected": "GitHub connected"
1862
+ },
1863
+ "errors": {
1864
+ "listInstallations": "Could not list GitHub installations",
1865
+ "startInstall": "Could not start the GitHub App install",
1866
+ "connect": "Could not connect"
1867
+ }
1868
+ },
1869
+ "panel": {
1870
+ "connectIntro": "Connect a GitHub App installation to back board blocks with repositories, browse pull requests and issues, and let agents push branches and open PRs.",
1871
+ "installationMeta": "{targetType} · installation {id}",
1872
+ "resync": "Resync",
1873
+ "backfill": "Backfill",
1874
+ "disconnect": "Disconnect",
1875
+ "loading": "Loading…",
1876
+ "tabs": {
1877
+ "repos": "Repositories",
1878
+ "pulls": "Pull requests",
1879
+ "issues": "Issues"
1880
+ },
1881
+ "linkedToBoard": "Linked to this board",
1882
+ "manageRepos": "Manage repos",
1883
+ "manageHint": "Pick the repositories this board should track. The GitHub connection is shared across the account, and each board links its own repos.",
1884
+ "loadingRepos": "Loading repositories…",
1885
+ "noAvailableRepos": "The installation can't access any repositories yet.",
1886
+ "private": "private",
1887
+ "saveSelection": "Save selection",
1888
+ "noLinkedRepos": "No repositories linked yet. Use Manage repos to pick which repositories this board tracks.",
1889
+ "open": "Open",
1890
+ "protected": "protected",
1891
+ "newBranch": "New branch",
1892
+ "fromSha": "From SHA",
1893
+ "commitShaPlaceholder": "commit sha",
1894
+ "createBranch": "Create branch",
1895
+ "openPr": "Open PR",
1896
+ "repository": "Repository",
1897
+ "chooseRepository": "Choose a repository",
1898
+ "prTitle": "Title",
1899
+ "headBranch": "Head branch",
1900
+ "baseBranch": "Base branch",
1901
+ "openPullRequest": "Open pull request",
1902
+ "noPulls": "No pull requests synced.",
1903
+ "mergePr": "Merge pull request",
1904
+ "noIssues": "No issues synced.",
1905
+ "prState": {
1906
+ "open": "open",
1907
+ "closed": "closed",
1908
+ "merged": "merged"
1909
+ },
1910
+ "issueState": {
1911
+ "open": "open",
1912
+ "closed": "closed"
1913
+ },
1914
+ "toast": {
1915
+ "disconnected": "GitHub disconnected",
1916
+ "resync": "Resync {status}",
1917
+ "reposUpdated": "Linked repositories updated",
1918
+ "branchCreated": "Branch {name} created",
1919
+ "prOpened": "Pull request opened",
1920
+ "prMerged": "PR #{number} merged"
1921
+ },
1922
+ "errors": {
1923
+ "disconnect": "Could not disconnect",
1924
+ "resync": "Could not resync",
1925
+ "loadRepos": "Could not load repositories",
1926
+ "updateRepos": "Could not update repositories",
1927
+ "loadBranches": "Could not load branches",
1928
+ "createBranch": "Could not create branch",
1929
+ "openPr": "Could not open pull request",
1930
+ "merge": "Could not merge"
1931
+ }
1932
+ },
1933
+ "addService": {
1934
+ "title": "Add a service from a repository",
1935
+ "intro": "Pick an existing GitHub repository to add as a board service. No bootstrapping: the repo is linked to a new service frame as-is, and tasks you run on it target that repo.",
1936
+ "connectFirst": "Connect this workspace to GitHub first. Link an installation the App is already on, or install it.",
1937
+ "repository": "Repository",
1938
+ "repositoryHint": "Repositories the GitHub App can access. Don't see yours? Grant the App access below, then refresh.",
1939
+ "noReposAvailable": "No repositories available yet. Grant the App access to one below, then refresh.",
1940
+ "filterPlaceholder": "Filter by owner/name…",
1941
+ "clearFilter": "Clear filter",
1942
+ "chooseRepository": "Choose a repository",
1943
+ "showingCount": "Showing {shown} of {total} repositories.",
1944
+ "repoLabel": {
1945
+ "private": " (private)",
1946
+ "monorepo": " · monorepo",
1947
+ "onBoard": " · already on board"
1948
+ },
1949
+ "monorepoLabel": "This is a monorepo (hosts more than one service)",
1950
+ "monorepoDescription": "Add several services from one repo, each pinned to a subdirectory.",
1951
+ "monorepoBrowseHint": "Browse the repository and pick the directory of the service you want to add. Agents working on this service will run within that subdirectory.",
1952
+ "serviceDirectory": "Service directory:",
1953
+ "noDirectorySelected": "No directory selected yet.",
1954
+ "addedConfigure": "{title} added, configure it",
1955
+ "grantAccess": "Grant the App access to a repo",
1956
+ "grantAccessTitle": "Open the App's installation settings to grant it access to a repository",
1957
+ "refreshList": "Refresh list",
1958
+ "done": "Done",
1959
+ "add": "Add service",
1960
+ "addAnother": "Add another service",
1961
+ "toast": {
1962
+ "addedTitle": "Service added",
1963
+ "addedDescription": "{title} is on the board, configure it below.",
1964
+ "addFailedTitle": "Could not add service"
1965
+ }
1966
+ },
1967
+ "repoTree": {
1968
+ "root": "root",
1969
+ "loading": "Loading…",
1970
+ "noSubdirectories": "No subdirectories here.",
1971
+ "empty": "Nothing here.",
1972
+ "select": "Select",
1973
+ "selected": "Selected",
1974
+ "useThisFolder": "Use this folder",
1975
+ "errors": {
1976
+ "listDirectory": "Could not list directory"
1977
+ }
1978
+ }
1979
+ },
1980
+ "slack": {
1981
+ "panel": {
1982
+ "title": "Slack notifications",
1983
+ "intro": "Post board notifications (merge reviews, pipeline completions, CI failures) to Slack. The connection is shared across the account; routing is per board."
1984
+ },
1985
+ "connect": {
1986
+ "addToSlack": "Add to Slack",
1987
+ "orPasteToken": "...or paste a bot token (xoxb-...)",
1988
+ "connect": "Connect"
1989
+ },
1990
+ "connected": {
1991
+ "label": "Connected to {team}",
1992
+ "disconnect": "Disconnect"
1993
+ },
1994
+ "routing": {
1995
+ "heading": "Routing",
1996
+ "channelPlaceholder": "#channel or channel id",
1997
+ "mentionMembers": "{'@'}-mention mapped account members",
1998
+ "save": "Save routing"
1999
+ },
2000
+ "members": {
2001
+ "heading": "Member map (user id to Slack member id)",
2002
+ "hint": "{product} people are mentioned on requirement-review findings; everyone else only when they created the task.",
2003
+ "productLabel": "Product",
2004
+ "userIdPlaceholder": "User id (usr_...)",
2005
+ "slackIdPlaceholder": "Slack member id (U...)",
2006
+ "add": "Add member",
2007
+ "save": "Save map"
2008
+ },
2009
+ "routable": {
2010
+ "merge_review": "Merge review",
2011
+ "pipeline_complete": "Pipeline complete",
2012
+ "ci_failed": "CI failed",
2013
+ "test_failed": "Tests failed",
2014
+ "requirement_review": "Requirement review",
2015
+ "clarity_review": "Clarity review",
2016
+ "release_regression": "Release regression",
2017
+ "human_test_ready": "Ready for human testing",
2018
+ "visual_confirmation_ready": "Ready for visual confirmation"
2019
+ },
2020
+ "role": {
2021
+ "engineering": "Engineering",
2022
+ "product": "Product"
2023
+ },
2024
+ "toast": {
2025
+ "connected": "Slack connected",
2026
+ "routingSaved": "Routing saved",
2027
+ "mapSaved": "Member map saved"
2028
+ },
2029
+ "error": {
2030
+ "loadSettings": "Could not load Slack settings",
2031
+ "startOAuth": "Could not start Slack OAuth",
2032
+ "connect": "Could not connect Slack",
2033
+ "disconnect": "Could not disconnect Slack",
2034
+ "saveRouting": "Could not save routing",
2035
+ "saveMap": "Could not save member map"
2036
+ }
2037
+ },
2038
+ "documents": {
2039
+ "picker": {
2040
+ "searchPlaceholder": "Search pages or paste a URL/ID…",
2041
+ "refPlaceholder": "Paste a page URL or ID…",
2042
+ "searchFailed": "Search failed: {error}",
2043
+ "importedBadge": "imported",
2044
+ "attachByReference": "Attach {ref} by reference",
2045
+ "noMatches": "No matching pages.",
2046
+ "emptySearchable": "Search by title, or pick an imported document.",
2047
+ "emptyRefOnly": "Paste a page URL or ID to attach it."
2048
+ },
2049
+ "import": {
2050
+ "title": "Import from a document source",
2051
+ "connectFirst": "Connect a document source first.",
2052
+ "connectSource": "Connect {source}",
2053
+ "spawningInto": "Spawning into {frame}",
2054
+ "sourceLabel": "Source",
2055
+ "refLabel": "Page URL or ID",
2056
+ "importButton": "Import",
2057
+ "importedHeading": "Imported documents",
2058
+ "previewSpawn": "Preview & spawn",
2059
+ "noneImported": "No documents imported yet.",
2060
+ "imported": "Imported \"{title}\"",
2061
+ "importFailed": "Import failed"
2062
+ },
2063
+ "connect": {
2064
+ "title": "Connect source",
2065
+ "sourceFallback": "Source",
2066
+ "intro": "Connect {source} to import requirements, RFCs and PRDs, then spawn board structure or attach them to tasks as agent context.",
2067
+ "personalNote": "Personal connection — this credential authenticates as you and is stored only for your account, never shared with the rest of the workspace.",
2068
+ "disconnect": "Disconnect",
2069
+ "connect": "Connect",
2070
+ "update": "Update connection",
2071
+ "connected": "{source} connected",
2072
+ "disconnected": "{source} disconnected",
2073
+ "connectFailed": "Could not connect"
2074
+ },
2075
+ "spawn": {
2076
+ "title": "Preview structure",
2077
+ "plannerLlm": "AI-generated",
2078
+ "plannerHeadings": "From headings",
2079
+ "intoFrame": "into {frame}",
2080
+ "asTopLevel": "as new top-level frames",
2081
+ "buildingPlan": "Building plan…",
2082
+ "spawnOntoBoard": "Spawn onto board",
2083
+ "planFailed": "Could not build a plan",
2084
+ "spawned": "Structure spawned",
2085
+ "spawnFailed": "Spawn failed",
2086
+ "summary": "{frames} · {modules} · {tasks}",
2087
+ "frameCount": "{count} frame | {count} frames",
2088
+ "moduleCount": "{count} module | {count} modules",
2089
+ "taskCount": "{count} task | {count} tasks"
2090
+ },
2091
+ "taskDocs": {
2092
+ "heading": "Context documents",
2093
+ "attach": "Attach",
2094
+ "importPage": "Import a page…",
2095
+ "empty": "Attach a requirement, RFC or PRD so agents see it while implementing this task.",
2096
+ "attached": "Document attached",
2097
+ "attachFailed": "Could not attach"
2098
+ }
2099
+ },
2100
+ "tasks": {
2101
+ "picker": {
2102
+ "searchPlaceholder": "Search issues or paste a URL/key…",
2103
+ "refPlaceholder": "Paste an issue URL or key…",
2104
+ "searchFailed": "Search failed: {error}",
2105
+ "imported": "imported",
2106
+ "attachByReference": "Attach {ref} by reference",
2107
+ "noMatches": "No matching issues.",
2108
+ "emptySearchable": "Search by title, or pick an imported issue.",
2109
+ "emptyRefOnly": "Paste an issue URL or key to attach it."
2110
+ },
2111
+ "contextIssues": {
2112
+ "title": "Context issues",
2113
+ "attach": "Attach",
2114
+ "attached": "Issue attached",
2115
+ "attachFailed": "Could not attach",
2116
+ "importIssue": "Import an issue…",
2117
+ "emptyHint": "Attach a Jira issue so agents see its description and comments while implementing this task."
2118
+ },
2119
+ "import": {
2120
+ "titleCreate": "Create task from issue",
2121
+ "titleBrowse": "Tracker issues",
2122
+ "connectFirst": "Connect or enable a task source first.",
2123
+ "enableSource": "Enable {label}",
2124
+ "connectSource": "Connect {label}",
2125
+ "source": "Source",
2126
+ "refLabel": "Issue key or URL",
2127
+ "import": "Import",
2128
+ "asEpic": "As epic",
2129
+ "asEpicTitleReady": "Spawn this epic + its children as a linked task group",
2130
+ "asEpicTitleNeedsContainer": "Pick a container for the child tasks first",
2131
+ "epicChildrenContainer": "Epic children container",
2132
+ "pickContainer": "Pick a frame or module",
2133
+ "searchIssues": "Search issues",
2134
+ "searchPlaceholder": "Search by title, paste an issue URL, or type an issue number…",
2135
+ "createTasksIn": "Create tasks in",
2136
+ "needFrameFirst": "Add a service frame to the board first to create tasks from issues.",
2137
+ "searchFailed": "Search failed: {error}",
2138
+ "searchResults": "Search results",
2139
+ "createFromIssue": "Create a task from this issue",
2140
+ "pickContainerFirst": "Pick a container first",
2141
+ "viewOnGitHub": "View {id} on GitHub",
2142
+ "emptyHint": "Search above, or paste an issue URL/key to create a task from it.",
2143
+ "imported": "Imported \"{title}\"",
2144
+ "importFailed": "Import failed",
2145
+ "epicSpawned": "Spawned epic \"{title}\"",
2146
+ "epicChildren": "{count} child task created | {count} child tasks created",
2147
+ "@epicChildren": {
2148
+ "description": "Count-based: how many child tasks were created when spawning an epic (count is always >= 1). Provide ALL plural forms your language needs (English has 2; Polish/Ukrainian need 3 - one/few/many - via the custom pluralRules in i18n.config.ts)."
2149
+ },
2150
+ "epicFailed": "Could not spawn epic"
2151
+ },
2152
+ "connect": {
2153
+ "title": "Task source",
2154
+ "sourceFallback": "Source",
2155
+ "connectedToast": "{label} connected",
2156
+ "connectFailed": "Could not connect",
2157
+ "disconnectedToast": "{label} disconnected",
2158
+ "updateFailed": "Could not update",
2159
+ "intro": "{label} lets you import issues and attach them to tasks as agent context.",
2160
+ "credentialless": "This source uses the GitHub App already installed on your workspace, so there are no credentials to enter.",
2161
+ "installAppHint": "Install the workspace's GitHub App (connect GitHub repos) to offer {label}.",
2162
+ "connected": "Connected.",
2163
+ "connectedTo": "Connected to {label}.",
2164
+ "offerToWorkspace": "Offer to this workspace",
2165
+ "offerHint": "When off, {label} is hidden from import and linking.",
2166
+ "disconnect": "Disconnect",
2167
+ "updateConnection": "Update connection",
2168
+ "connect": "Connect"
2169
+ }
1837
2170
  }
1838
2171
  }