@yiln-dsh/dsh-plugin-file-explorer 0.5.0 → 0.5.1

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 (3) hide show
  1. package/README.md +2 -2
  2. package/client.js +259 -80
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -36,7 +36,7 @@ A DSH `dsh.bundle` that contributes a workspace file explorer page to the
36
36
 
37
37
  ## Install
38
38
 
39
- The package version is `@yiln-dsh/dsh-plugin-file-explorer@0.5.0`.
39
+ The package version is `@yiln-dsh/dsh-plugin-file-explorer@0.5.1`.
40
40
 
41
41
  The right-panel package must be installed in the same `web` profile:
42
42
 
@@ -53,7 +53,7 @@ pnpm pack
53
53
  ```
54
54
 
55
55
  ```bash
56
- dsh plugin --profile web add ./yiln-dsh-dsh-plugin-file-explorer-0.5.0.tgz
56
+ dsh plugin --profile web add ./yiln-dsh-dsh-plugin-file-explorer-0.5.1.tgz
57
57
  ```
58
58
 
59
59
  ### npm package
package/client.js CHANGED
@@ -21,11 +21,180 @@ window.__ModuleLoader__.load({
21
21
 
22
22
  const inject = ["slots", "rightPanel"];
23
23
 
24
+ const LOCALE_NS = "file-explorer";
25
+ const ZH_DICT = {
26
+ "files.title": "文件",
27
+ "git.title": "Git 图谱",
28
+ "workspace": "工作区",
29
+ "files.refresh": "刷新",
30
+ "files.refreshList": "刷新文件列表",
31
+ "git.refreshGraph": "刷新图谱",
32
+ "files.up": "上一级",
33
+ "files.goUp": "返回上级目录",
34
+ "files.delete": "删除",
35
+ "files.deleteFile": "删除 {name}",
36
+ "files.confirmDelete": "确认删除 {name}",
37
+ "files.clickAgain": "再次点击确认删除",
38
+ "files.deleteFailed": "删除失败",
39
+ "files.view": "查看",
40
+ "files.viewFile": "查看 {name}",
41
+ "files.download": "下载",
42
+ "files.downloadFile": "下载 {name}",
43
+ "files.downloadFailed": "下载失败",
44
+ "files.closePreview": "关闭预览",
45
+ "files.preview": "预览",
46
+ "files.fullscreen": "全屏",
47
+ "files.enterFullscreen": "进入全屏",
48
+ "files.exitFullscreen": "退出全屏",
49
+ "files.imageControls": "图片控制",
50
+ "files.zoomIn": "放大",
51
+ "files.zoomOut": "缩小",
52
+ "files.resetZoom": "重置缩放",
53
+ "files.resetImageZoom": "重置图片缩放",
54
+ "files.imagePreview": "图片预览",
55
+ "files.previewDialog": "文件预览",
56
+ "files.workspaceRoot": "工作区根目录",
57
+ "files.loading": "加载中…",
58
+ "git.loading": "加载中…",
59
+ "files.empty": "空文件夹",
60
+ "files.listFailed": "文件列表加载失败",
61
+ "files.readFailed": "文件读取失败",
62
+ "files.imageMeta": "图片 / {size}",
63
+ "files.binary": "二进制文件",
64
+ "files.fileTooLarge": "文件过大,无法在此预览",
65
+ "files.imageTooLarge": "图片过大,无法在此预览",
66
+ "files.noPreview": "(无预览)",
67
+ "files.binaryNoPreview": "二进制文件无法预览",
68
+ "git.logFailed": "Git 提交记录加载失败",
69
+ "git.commitFailed": "提交详情加载失败",
70
+ "git.diffFailed": "差异加载失败",
71
+ "git.loadingChanges": "加载变更…",
72
+ "git.noDiff": "(无差异)",
73
+ "git.noMessage": "(无提交信息)",
74
+ "git.diffTruncated": "…(差异已截断)",
75
+ "git.uncommitted": "未提交的更改",
76
+ "git.workingTree": "工作区",
77
+ "git.workingTreeLabel": "工作区",
78
+ "git.noCommitsYet": "暂无提交",
79
+ "git.noCommitsYetSuffix": "(暂无提交)",
80
+ "git.noData": "暂无数据",
81
+ "git.detachedHead": "游离 HEAD",
82
+ "git.allBranches": "所有分支",
83
+ "git.branchFilter": "分支筛选",
84
+ "git.currentBranch": "当前分支",
85
+ "git.diff": "差异",
86
+ "git.closeDiff": "关闭差异",
87
+ "git.authorUnknown": "未知",
88
+ "git.changedFiles.one": "共 {count} 个变更文件",
89
+ "git.changedFiles.other": "共 {count} 个变更文件",
90
+ "git.changedVsHead.one": "共 {count} 个变更文件(对比 HEAD)",
91
+ "git.changedVsHead.other": "共 {count} 个变更文件(对比 HEAD)",
92
+ "git.changesCount.one": "{count} 个变更",
93
+ "git.changesCount.other": "{count} 个变更",
94
+ "time.justNow": "刚刚",
95
+ "time.minutesAgo": "{n} 分钟前",
96
+ "time.hoursAgo": "{n} 小时前",
97
+ "time.daysAgo": "{n} 天前",
98
+ "time.weeksAgo": "{n} 周前",
99
+ "time.monthsAgo": "{n} 个月前",
100
+ "time.yearsAgo": "{n} 年前",
101
+ };
102
+ const EN_DICT = {
103
+ "files.title": "Files",
104
+ "git.title": "Git Graph",
105
+ "workspace": "Workspace",
106
+ "files.refresh": "Refresh",
107
+ "files.refreshList": "Refresh file list",
108
+ "git.refreshGraph": "Refresh graph",
109
+ "files.up": "Up",
110
+ "files.goUp": "Go to parent directory",
111
+ "files.delete": "Delete",
112
+ "files.deleteFile": "Delete {name}",
113
+ "files.confirmDelete": "Confirm deleting {name}",
114
+ "files.clickAgain": "Click again to delete",
115
+ "files.deleteFailed": "Delete failed",
116
+ "files.view": "View",
117
+ "files.viewFile": "View {name}",
118
+ "files.download": "Download",
119
+ "files.downloadFile": "Download {name}",
120
+ "files.downloadFailed": "Download failed",
121
+ "files.closePreview": "Close preview",
122
+ "files.preview": "Preview",
123
+ "files.fullscreen": "Fullscreen",
124
+ "files.enterFullscreen": "Enter fullscreen",
125
+ "files.exitFullscreen": "Exit fullscreen",
126
+ "files.imageControls": "Image controls",
127
+ "files.zoomIn": "Zoom in",
128
+ "files.zoomOut": "Zoom out",
129
+ "files.resetZoom": "Reset zoom",
130
+ "files.resetImageZoom": "Reset image zoom",
131
+ "files.imagePreview": "Image preview",
132
+ "files.previewDialog": "File preview",
133
+ "files.workspaceRoot": "workspace root",
134
+ "files.loading": "Loading...",
135
+ "git.loading": "Loading…",
136
+ "files.empty": "Empty folder",
137
+ "files.listFailed": "Failed to list files",
138
+ "files.readFailed": "Failed to read file",
139
+ "files.imageMeta": "Image / {size}",
140
+ "files.binary": "Binary",
141
+ "files.fileTooLarge": "File is too large to preview here",
142
+ "files.imageTooLarge": "Image is too large to preview here",
143
+ "files.noPreview": "(No preview)",
144
+ "files.binaryNoPreview": "Binary file cannot be previewed",
145
+ "git.logFailed": "Failed to load git log",
146
+ "git.commitFailed": "Failed to load commit",
147
+ "git.diffFailed": "Failed to load diff",
148
+ "git.loadingChanges": "Loading changes…",
149
+ "git.noDiff": "(no diff)",
150
+ "git.noMessage": "(no message)",
151
+ "git.diffTruncated": "… (diff truncated)",
152
+ "git.uncommitted": "Uncommitted changes",
153
+ "git.workingTree": "Working tree",
154
+ "git.workingTreeLabel": "working tree",
155
+ "git.noCommitsYet": "No commits yet",
156
+ "git.noCommitsYetSuffix": " (no commits yet)",
157
+ "git.noData": "No data",
158
+ "git.detachedHead": "detached HEAD",
159
+ "git.allBranches": "All branches",
160
+ "git.branchFilter": "Branch filter",
161
+ "git.currentBranch": "Current branch",
162
+ "git.diff": "Diff",
163
+ "git.closeDiff": "Close diff",
164
+ "git.authorUnknown": "unknown",
165
+ "git.changedFiles.one": "{count} changed file",
166
+ "git.changedFiles.other": "{count} changed files",
167
+ "git.changedVsHead.one": "{count} changed file (vs HEAD)",
168
+ "git.changedVsHead.other": "{count} changed files (vs HEAD)",
169
+ "git.changesCount.one": "{count} change",
170
+ "git.changesCount.other": "{count} changes",
171
+ "time.justNow": "just now",
172
+ "time.minutesAgo": "{n}m ago",
173
+ "time.hoursAgo": "{n}h ago",
174
+ "time.daysAgo": "{n}d ago",
175
+ "time.weeksAgo": "{n}w ago",
176
+ "time.monthsAgo": "{n}mo ago",
177
+ "time.yearsAgo": "{n}y ago",
178
+ };
179
+
180
+ function applyParams(template, params) {
181
+ if (!params) return template;
182
+ return template.replace(/\{(\w+)\}/g, (match, name) => name in params ? String(params[name]) : match);
183
+ }
184
+
24
185
  function apply(ctx) {
25
186
  const slots = ctx.get('slots')
26
187
  const rightPanel = ctx.get('rightPanel')
27
188
  if (slots === undefined || rightPanel === undefined) return
28
189
 
190
+ const locale = ctx.get("locale");
191
+ if (locale !== undefined) {
192
+ ctx.effect(() => locale.register(LOCALE_NS, { zh: ZH_DICT, en: EN_DICT }), "file-explorer: locale");
193
+ }
194
+ const t = locale !== undefined
195
+ ? locale.bind(LOCALE_NS)
196
+ : (key, params) => applyParams(ZH_DICT[key] ?? EN_DICT[key] ?? key, params);
197
+
29
198
  const style = document.createElement("style");
30
199
  style.id = "dsh-plugin-file-explorer-style";
31
200
  style.setAttribute("data-plugin", "dsh-plugin-file-explorer");
@@ -703,16 +872,16 @@ window.__ModuleLoader__.load({
703
872
  return STATUS_COLORS[letter] || '#6b7280'
704
873
  }
705
874
 
706
- function relTime(epochSeconds) {
875
+ function relTime(epochSeconds, t) {
707
876
  if (typeof epochSeconds !== 'number' || !Number.isFinite(epochSeconds) || epochSeconds <= 0) return ''
708
877
  const s = Math.max(0, Date.now() / 1000 - epochSeconds)
709
- if (s < 60) return 'just now'
710
- if (s < 3600) return `${Math.floor(s / 60)}m ago`
711
- if (s < 86400) return `${Math.floor(s / 3600)}h ago`
712
- if (s < 604800) return `${Math.floor(s / 86400)}d ago`
713
- if (s < 2592000) return `${Math.floor(s / 604800)}w ago`
714
- if (s < 31536000) return `${Math.floor(s / 2592000)}mo ago`
715
- return `${Math.floor(s / 31536000)}y ago`
878
+ if (s < 60) return t('time.justNow')
879
+ if (s < 3600) return t('time.minutesAgo', { n: Math.floor(s / 60) })
880
+ if (s < 86400) return t('time.hoursAgo', { n: Math.floor(s / 3600) })
881
+ if (s < 604800) return t('time.daysAgo', { n: Math.floor(s / 86400) })
882
+ if (s < 2592000) return t('time.weeksAgo', { n: Math.floor(s / 604800) })
883
+ if (s < 31536000) return t('time.monthsAgo', { n: Math.floor(s / 2592000) })
884
+ return t('time.yearsAgo', { n: Math.floor(s / 31536000) })
716
885
  }
717
886
 
718
887
  function shortHash(hash) {
@@ -927,7 +1096,8 @@ window.__ModuleLoader__.load({
927
1096
  // line, the commit lane rows with an expandable per-commit detail, and a
928
1097
  // diff modal. Data comes entirely from the /_dsh/file-explorer/git-*
929
1098
  // JSON API.
930
- function GitGraphView(props) {
1099
+ function createGitGraphView(t) {
1100
+ return function GitGraphView(props) {
931
1101
  const api = props.api
932
1102
  const cwd = props.cwd
933
1103
  const [scope, setScope] = React.useState('all')
@@ -955,7 +1125,7 @@ window.__ModuleLoader__.load({
955
1125
  if (raw && raw.ok === true) {
956
1126
  setLog(raw)
957
1127
  } else {
958
- setError(raw && typeof raw.error === 'string' ? raw.error : 'Failed to load git log')
1128
+ setError(raw && typeof raw.error === 'string' ? raw.error : t('git.logFailed'))
959
1129
  }
960
1130
  })
961
1131
  .catch((err) => {
@@ -981,7 +1151,7 @@ window.__ModuleLoader__.load({
981
1151
  if (raw && raw.ok === true) {
982
1152
  setDetails((prev) => ({ ...prev, [selected]: raw }))
983
1153
  } else {
984
- setDetails((prev) => ({ ...prev, [selected]: { error: raw && typeof raw.error === 'string' ? raw.error : 'Failed to load commit' } }))
1154
+ setDetails((prev) => ({ ...prev, [selected]: { error: raw && typeof raw.error === 'string' ? raw.error : t('git.commitFailed') } }))
985
1155
  }
986
1156
  })
987
1157
  .catch((err) => {
@@ -1000,10 +1170,10 @@ window.__ModuleLoader__.load({
1000
1170
  api('git-diff', { path: cwd, hash, file: file.path })
1001
1171
  .then((raw) => {
1002
1172
  if (raw && raw.ok === true) {
1003
- const note = raw.truncated ? '\n\n (diff truncated)' : ''
1173
+ const note = raw.truncated ? '\n\n' + t('git.diffTruncated') : ''
1004
1174
  setDiff({ title: file.path, hash, loading: false, text: (raw.patch || '') + note, error: null })
1005
1175
  } else {
1006
- setDiff({ title: file.path, hash, loading: false, text: null, error: raw && typeof raw.error === 'string' ? raw.error : 'Failed to load diff' })
1176
+ setDiff({ title: file.path, hash, loading: false, text: null, error: raw && typeof raw.error === 'string' ? raw.error : t('git.diffFailed') })
1007
1177
  }
1008
1178
  })
1009
1179
  .catch((err) => {
@@ -1017,7 +1187,7 @@ window.__ModuleLoader__.load({
1017
1187
  const renderCommitDetail = (hash, d) => {
1018
1188
  if (d === undefined) {
1019
1189
  return React.createElement('div', { className: 'dsh-git-detail' },
1020
- React.createElement('div', { className: 'dsh-git-files-title' }, 'Loading changes…'))
1190
+ React.createElement('div', { className: 'dsh-git-files-title' }, t('git.loadingChanges')))
1021
1191
  }
1022
1192
  if (d.error) {
1023
1193
  return React.createElement('div', { className: 'dsh-git-detail' },
@@ -1025,9 +1195,9 @@ window.__ModuleLoader__.load({
1025
1195
  }
1026
1196
  const files = Array.isArray(d.files) ? d.files : []
1027
1197
  return React.createElement('div', { className: 'dsh-git-detail' },
1028
- React.createElement('div', { className: 'dsh-git-msg' }, d.message || '(no message)'),
1198
+ React.createElement('div', { className: 'dsh-git-msg' }, d.message || t('git.noMessage')),
1029
1199
  React.createElement('div', { className: 'dsh-git-files-title' },
1030
- `${files.length} changed file${files.length === 1 ? '' : 's'}`),
1200
+ files.length === 1 ? t('git.changedFiles.one', { count: files.length }) : t('git.changedFiles.other', { count: files.length })),
1031
1201
  files.length === 0 ? null : files.map((f, i) =>
1032
1202
  React.createElement(GitFileRow, { key: i, file: f, onOpen: (file) => openDiff(hash, file) })),
1033
1203
  )
@@ -1035,7 +1205,7 @@ window.__ModuleLoader__.load({
1035
1205
 
1036
1206
  const rows = []
1037
1207
  if (loading) {
1038
- rows.push(React.createElement('div', { key: 'loading', className: 'dsh-fe-status' }, 'Loading…'))
1208
+ rows.push(React.createElement('div', { key: 'loading', className: 'dsh-fe-status' }, t('git.loading')))
1039
1209
  } else if (error) {
1040
1210
  rows.push(React.createElement('div', { key: 'error', className: 'dsh-fe-status dsh-fe-status-error' }, error))
1041
1211
  } else if (graph !== null) {
@@ -1049,21 +1219,21 @@ window.__ModuleLoader__.load({
1049
1219
  className: 'dsh-git-row',
1050
1220
  onClick: () => toggle('WORKING'),
1051
1221
  role: 'button',
1052
- title: 'Uncommitted changes',
1222
+ title: t('git.uncommitted'),
1053
1223
  },
1054
1224
  React.createElement(WorkTreeCell, { lane: first !== null ? first.lane : 0, connectorColor }),
1055
1225
  React.createElement('div', { className: 'dsh-git-main' },
1056
1226
  React.createElement('div', { className: 'dsh-git-line1' },
1057
- React.createElement('span', { className: 'dsh-git-subject' }, 'Uncommitted changes'),
1227
+ React.createElement('span', { className: 'dsh-git-subject' }, t('git.uncommitted')),
1058
1228
  React.createElement('span', { className: 'dsh-git-pill dsh-git-pill-branch' }, String(changes.length)),
1059
1229
  ),
1060
- React.createElement('div', { className: 'dsh-git-line2' }, 'Working tree'),
1230
+ React.createElement('div', { className: 'dsh-git-line2' }, t('git.workingTree')),
1061
1231
  ),
1062
1232
  ),
1063
1233
  selected === 'WORKING'
1064
1234
  ? React.createElement('div', { className: 'dsh-git-detail' },
1065
1235
  React.createElement('div', { className: 'dsh-git-files-title' },
1066
- `${changes.length} changed file${changes.length === 1 ? '' : 's'} (vs HEAD)`),
1236
+ changes.length === 1 ? t('git.changedVsHead.one', { count: changes.length }) : t('git.changedVsHead.other', { count: changes.length })),
1067
1237
  changes.map((f, i) =>
1068
1238
  React.createElement(GitFileRow, { key: i, file: f, onOpen: (file) => openDiff('WORKING', file) })),
1069
1239
  )
@@ -1086,23 +1256,23 @@ window.__ModuleLoader__.load({
1086
1256
  React.createElement('span', { className: 'dsh-git-subject' }, c.subject),
1087
1257
  ),
1088
1258
  React.createElement('div', { className: 'dsh-git-line2' },
1089
- `${c.author || 'unknown'} · ${relTime(c.date)} · ${shortHash(c.hash)}`),
1259
+ `${c.author || t('git.authorUnknown')} · ${relTime(c.date, t)} · ${shortHash(c.hash)}`),
1090
1260
  ),
1091
1261
  ),
1092
1262
  selected === c.hash ? renderCommitDetail(c.hash, details[c.hash]) : null,
1093
1263
  ))
1094
1264
  })
1095
1265
  if (rows.length === 0) {
1096
- rows.push(React.createElement('div', { key: 'empty', className: 'dsh-fe-status' }, 'No commits yet'))
1266
+ rows.push(React.createElement('div', { key: 'empty', className: 'dsh-fe-status' }, t('git.noCommitsYet')))
1097
1267
  }
1098
1268
  } else {
1099
- rows.push(React.createElement('div', { key: 'none', className: 'dsh-fe-status' }, 'No data'))
1269
+ rows.push(React.createElement('div', { key: 'none', className: 'dsh-fe-status' }, t('git.noData')))
1100
1270
  }
1101
1271
 
1102
1272
  const branchLabel = status
1103
1273
  ? (status.branch === null
1104
- ? 'detached HEAD'
1105
- : status.branch + (status.unborn ? ' (no commits yet)' : ''))
1274
+ ? t('git.detachedHead')
1275
+ : status.branch + (status.unborn ? t('git.noCommitsYetSuffix') : ''))
1106
1276
  : null
1107
1277
  const upstreamBits = []
1108
1278
  if (status && status.ahead > 0) upstreamBits.push('↑' + status.ahead)
@@ -1114,17 +1284,17 @@ window.__ModuleLoader__.load({
1114
1284
  type: 'button',
1115
1285
  className: 'dsh-fe-toolbutton',
1116
1286
  onClick: reload,
1117
- 'aria-label': 'Refresh graph',
1118
- title: 'Refresh',
1287
+ 'aria-label': t('git.refreshGraph'),
1288
+ title: t('files.refresh'),
1119
1289
  }, svgIcon(refreshIcon, 15)),
1120
1290
  React.createElement('select', {
1121
1291
  className: 'dsh-git-scope',
1122
1292
  value: scope,
1123
- 'aria-label': 'Branch filter',
1293
+ 'aria-label': t('git.branchFilter'),
1124
1294
  onChange: (event) => setScope(event.target.value),
1125
1295
  },
1126
- React.createElement('option', { value: 'all' }, 'All branches'),
1127
- React.createElement('option', { value: 'current' }, 'Current branch'),
1296
+ React.createElement('option', { value: 'all' }, t('git.allBranches')),
1297
+ React.createElement('option', { value: 'current' }, t('git.currentBranch')),
1128
1298
  ),
1129
1299
  ),
1130
1300
  branchLabel !== null
@@ -1132,7 +1302,7 @@ window.__ModuleLoader__.load({
1132
1302
  svgIcon(branchIcon, 12),
1133
1303
  React.createElement('span', { className: 'dsh-git-info-name' }, branchLabel),
1134
1304
  upstreamBits.length > 0 ? React.createElement('span', null, upstreamBits.join(' ')) : null,
1135
- React.createElement('span', null, `${changes.length} change${changes.length === 1 ? '' : 's'}`),
1305
+ React.createElement('span', null, changes.length === 1 ? t('git.changesCount.one', { count: changes.length }) : t('git.changesCount.other', { count: changes.length })),
1136
1306
  )
1137
1307
  : null,
1138
1308
  React.createElement('div', { className: 'dsh-git-list' }, rows),
@@ -1144,29 +1314,31 @@ window.__ModuleLoader__.load({
1144
1314
  React.createElement('div', {
1145
1315
  className: 'dsh-fe-modal',
1146
1316
  role: 'dialog',
1147
- 'aria-label': 'Diff',
1317
+ 'aria-label': t('git.diff'),
1148
1318
  onClick: (event) => event.stopPropagation(),
1149
1319
  },
1150
1320
  React.createElement('div', { className: 'dsh-fe-modal-head' },
1151
1321
  React.createElement('div', { className: 'dsh-fe-modal-title' }, diff.title),
1152
1322
  React.createElement('div', { className: 'dsh-fe-modal-meta' },
1153
- diff.hash === 'WORKING' ? 'working tree' : shortHash(diff.hash)),
1323
+ diff.hash === 'WORKING' ? t('git.workingTreeLabel') : shortHash(diff.hash)),
1154
1324
  React.createElement('button', {
1155
1325
  type: 'button',
1156
1326
  className: 'dsh-fe-icon',
1157
1327
  onClick: () => setDiff(null),
1158
- 'aria-label': 'Close diff',
1328
+ 'aria-label': t('git.closeDiff'),
1159
1329
  }, svgIcon(closeIcon, 14)),
1160
1330
  ),
1161
1331
  React.createElement('div', { className: 'dsh-fe-modal-body' },
1162
- diff.loading ? 'Loading…' : (diff.error || diff.text || '(no diff)')),
1332
+ diff.loading ? t('git.loading') : (diff.error || diff.text || t('git.noDiff'))),
1163
1333
  ),
1164
1334
  )
1165
1335
  : null,
1166
1336
  )
1167
1337
  }
1338
+ }
1168
1339
 
1169
- function FileExplorerPage(props) {
1340
+ function createFileExplorerPage(t) {
1341
+ return function FileExplorerPage(props) {
1170
1342
  if (typeof props.useSessions !== 'function') return null
1171
1343
 
1172
1344
  const sessionId = props.useSessions((state) => {
@@ -1231,7 +1403,7 @@ window.__ModuleLoader__.load({
1231
1403
  setCurrentPath(requestPath)
1232
1404
  setParentPath(null)
1233
1405
  setEntries([])
1234
- setError(raw && typeof raw.error === 'string' ? raw.error : 'Failed to list files')
1406
+ setError(raw && typeof raw.error === 'string' ? raw.error : t('files.listFailed'))
1235
1407
  }
1236
1408
  })
1237
1409
  .catch((err) => {
@@ -1333,7 +1505,7 @@ window.__ModuleLoader__.load({
1333
1505
  setPreview(null)
1334
1506
  api('read', { path: entry.path })
1335
1507
  .then((raw) => {
1336
- setPreview(raw && raw.ok === true ? raw : { ok: false, error: raw && typeof raw.error === 'string' ? raw.error : 'Failed to read file' })
1508
+ setPreview(raw && raw.ok === true ? raw : { ok: false, error: raw && typeof raw.error === 'string' ? raw.error : t('files.readFailed') })
1337
1509
  })
1338
1510
  .catch((err) => {
1339
1511
  setPreview({ ok: false, error: err && typeof err.message === 'string' ? err.message : String(err) })
@@ -1346,7 +1518,7 @@ window.__ModuleLoader__.load({
1346
1518
  api('download', { path: entry.path })
1347
1519
  .then((raw) => {
1348
1520
  if (!raw || raw.ok !== true) {
1349
- const message = raw && typeof raw.error === 'string' ? raw.error : 'Download failed'
1521
+ const message = raw && typeof raw.error === 'string' ? raw.error : t('files.downloadFailed')
1350
1522
  setError(message)
1351
1523
  return
1352
1524
  }
@@ -1373,7 +1545,7 @@ window.__ModuleLoader__.load({
1373
1545
  api('delete', { path: entry.path })
1374
1546
  .then((raw) => {
1375
1547
  if (!raw || raw.ok !== true) {
1376
- setError(raw && typeof raw.error === 'string' ? raw.error : 'Delete failed')
1548
+ setError(raw && typeof raw.error === 'string' ? raw.error : t('files.deleteFailed'))
1377
1549
  return
1378
1550
  }
1379
1551
  reload()
@@ -1397,9 +1569,9 @@ window.__ModuleLoader__.load({
1397
1569
  : React.createElement('button', {
1398
1570
  type: 'button',
1399
1571
  className: 'dsh-fe-path',
1400
- title: currentPath || 'workspace root',
1572
+ title: currentPath || t('files.workspaceRoot'),
1401
1573
  onClick: startEditPath,
1402
- }, currentPath || 'workspace root')
1574
+ }, currentPath || t('files.workspaceRoot'))
1403
1575
 
1404
1576
  const filesView = React.createElement(React.Fragment, null,
1405
1577
  pathControl,
@@ -1411,24 +1583,24 @@ window.__ModuleLoader__.load({
1411
1583
  onClick: () => {
1412
1584
  if (parentPath !== null && parentPath !== currentPath) setRequestPath(parentPath)
1413
1585
  },
1414
- 'aria-label': 'Go to parent directory',
1415
- title: 'Up',
1586
+ 'aria-label': t('files.goUp'),
1587
+ title: t('files.up'),
1416
1588
  }, svgIcon(upIcon, 15)),
1417
1589
  React.createElement('button', {
1418
1590
  type: 'button',
1419
1591
  className: 'dsh-fe-toolbutton',
1420
1592
  onClick: reload,
1421
- 'aria-label': 'Refresh file list',
1422
- title: 'Refresh',
1593
+ 'aria-label': t('files.refreshList'),
1594
+ title: t('files.refresh'),
1423
1595
  }, svgIcon(refreshIcon, 15)),
1424
1596
  ),
1425
1597
  React.createElement('div', { className: 'dsh-fe-list' },
1426
1598
  loading
1427
- ? React.createElement('div', { className: 'dsh-fe-status' }, 'Loading...')
1599
+ ? React.createElement('div', { className: 'dsh-fe-status' }, t('files.loading'))
1428
1600
  : error
1429
1601
  ? React.createElement('div', { className: 'dsh-fe-status dsh-fe-status-error' }, error)
1430
1602
  : sorted.length === 0
1431
- ? React.createElement('div', { className: 'dsh-fe-status' }, 'Empty folder')
1603
+ ? React.createElement('div', { className: 'dsh-fe-status' }, t('files.empty'))
1432
1604
  : sorted.map((entry) => {
1433
1605
  const isDir = entry.type === 'directory'
1434
1606
  return React.createElement('div', {
@@ -1449,22 +1621,22 @@ window.__ModuleLoader__.load({
1449
1621
  type: 'button',
1450
1622
  className: 'dsh-fe-action',
1451
1623
  onClick: (event) => { event.stopPropagation(); openPreview(entry) },
1452
- 'aria-label': `View ${entry.name}`,
1453
- title: 'View',
1624
+ 'aria-label': t('files.viewFile', { name: entry.name }),
1625
+ title: t('files.view'),
1454
1626
  }, svgIcon(eyeIcon, 14)),
1455
1627
  !isDir && React.createElement('button', {
1456
1628
  type: 'button',
1457
1629
  className: 'dsh-fe-action',
1458
1630
  onClick: (event) => { event.stopPropagation(); downloadFile(entry) },
1459
- 'aria-label': `Download ${entry.name}`,
1460
- title: 'Download',
1631
+ 'aria-label': t('files.downloadFile', { name: entry.name }),
1632
+ title: t('files.download'),
1461
1633
  }, svgIcon(downloadIcon, 14)),
1462
1634
  React.createElement('button', {
1463
1635
  type: 'button',
1464
1636
  className: pendingDelete === entry.path ? 'dsh-fe-action dsh-fe-action-confirm' : 'dsh-fe-action dsh-fe-action-danger',
1465
1637
  onClick: (event) => { event.stopPropagation(); requestDelete(entry) },
1466
- 'aria-label': pendingDelete === entry.path ? `Confirm deleting ${entry.name}` : `Delete ${entry.name}`,
1467
- title: pendingDelete === entry.path ? 'Click again to delete' : 'Delete',
1638
+ 'aria-label': pendingDelete === entry.path ? t('files.confirmDelete', { name: entry.name }) : t('files.deleteFile', { name: entry.name }),
1639
+ title: pendingDelete === entry.path ? t('files.clickAgain') : t('files.delete'),
1468
1640
  }, svgIcon(trashIcon, 14)),
1469
1641
  ),
1470
1642
  ),
@@ -1477,7 +1649,7 @@ window.__ModuleLoader__.load({
1477
1649
  React.createElement('div', {
1478
1650
  className: 'dsh-fe-page',
1479
1651
  role: 'region',
1480
- 'aria-label': 'Files',
1652
+ 'aria-label': t('files.title'),
1481
1653
  },
1482
1654
  filesView,
1483
1655
  ),
@@ -1489,50 +1661,50 @@ window.__ModuleLoader__.load({
1489
1661
  React.createElement('div', {
1490
1662
  className: preview.kind === 'image' && imageFullscreen ? 'dsh-fe-modal dsh-fe-modal-fullscreen' : 'dsh-fe-modal',
1491
1663
  role: 'dialog',
1492
- 'aria-label': 'File preview',
1664
+ 'aria-label': t('files.previewDialog'),
1493
1665
  'aria-modal': true,
1494
1666
  tabIndex: -1,
1495
1667
  onKeyDown: handlePreviewKeyDown,
1496
1668
  onClick: (event) => event.stopPropagation(),
1497
1669
  },
1498
1670
  React.createElement('div', { className: 'dsh-fe-modal-head' },
1499
- React.createElement('div', { className: 'dsh-fe-modal-title' }, preview.name || 'Preview'),
1500
- React.createElement('div', { className: 'dsh-fe-modal-meta' }, preview.kind === 'image' ? `Image / ${formatSize(preview.size)}` : preview.binary ? 'Binary' : formatSize(preview.size)),
1671
+ React.createElement('div', { className: 'dsh-fe-modal-title' }, preview.name || t('files.preview')),
1672
+ React.createElement('div', { className: 'dsh-fe-modal-meta' }, preview.kind === 'image' ? t('files.imageMeta', { size: formatSize(preview.size) }) : preview.binary ? t('files.binary') : formatSize(preview.size)),
1501
1673
  preview.kind === 'image' && preview.dataUrl
1502
1674
  ? React.createElement('div', {
1503
1675
  className: 'dsh-fe-image-tools',
1504
1676
  role: 'toolbar',
1505
- 'aria-label': 'Image controls',
1677
+ 'aria-label': t('files.imageControls'),
1506
1678
  },
1507
1679
  React.createElement('button', {
1508
1680
  type: 'button',
1509
1681
  className: 'dsh-fe-icon dsh-fe-image-tool',
1510
1682
  disabled: imageScale <= 0.25,
1511
1683
  onClick: () => changeImageZoom(-0.25),
1512
- 'aria-label': 'Zoom out',
1513
- title: 'Zoom out',
1684
+ 'aria-label': t('files.zoomOut'),
1685
+ title: t('files.zoomOut'),
1514
1686
  }, svgIcon(zoomOutIcon, 14)),
1515
1687
  React.createElement('button', {
1516
1688
  type: 'button',
1517
1689
  className: 'dsh-fe-icon dsh-fe-image-tool',
1518
1690
  onClick: resetImageZoom,
1519
- 'aria-label': 'Reset image zoom',
1520
- title: 'Reset zoom',
1691
+ 'aria-label': t('files.resetImageZoom'),
1692
+ title: t('files.resetZoom'),
1521
1693
  }, React.createElement('span', { className: 'dsh-fe-image-zoom-label' }, `${Math.round(imageScale * 100)}%`)),
1522
1694
  React.createElement('button', {
1523
1695
  type: 'button',
1524
1696
  className: 'dsh-fe-icon dsh-fe-image-tool',
1525
1697
  disabled: imageScale >= 4,
1526
1698
  onClick: () => changeImageZoom(0.25),
1527
- 'aria-label': 'Zoom in',
1528
- title: 'Zoom in',
1699
+ 'aria-label': t('files.zoomIn'),
1700
+ title: t('files.zoomIn'),
1529
1701
  }, svgIcon(zoomInIcon, 14)),
1530
1702
  React.createElement('button', {
1531
1703
  type: 'button',
1532
1704
  className: 'dsh-fe-icon dsh-fe-image-tool',
1533
1705
  onClick: () => setImageFullscreen((value) => !value),
1534
- 'aria-label': imageFullscreen ? 'Exit fullscreen' : 'Enter fullscreen',
1535
- title: imageFullscreen ? 'Exit fullscreen' : 'Fullscreen',
1706
+ 'aria-label': imageFullscreen ? t('files.exitFullscreen') : t('files.enterFullscreen'),
1707
+ title: imageFullscreen ? t('files.exitFullscreen') : t('files.fullscreen'),
1536
1708
  }, svgIcon(imageFullscreen ? fullscreenExitIcon : fullscreenIcon, 14)),
1537
1709
  )
1538
1710
  : null,
@@ -1540,7 +1712,7 @@ window.__ModuleLoader__.load({
1540
1712
  type: 'button',
1541
1713
  className: 'dsh-fe-icon',
1542
1714
  onClick: closePreview,
1543
- 'aria-label': 'Close preview',
1715
+ 'aria-label': t('files.closePreview'),
1544
1716
  }, svgIcon(closeIcon, 14)),
1545
1717
  ),
1546
1718
  React.createElement('div', {
@@ -1552,7 +1724,7 @@ window.__ModuleLoader__.load({
1552
1724
  onPointerCancel: preview.kind === 'image' && preview.dataUrl ? endImageDrag : undefined,
1553
1725
  },
1554
1726
  previewLoading
1555
- ? 'Loading...'
1727
+ ? t('files.loading')
1556
1728
  : preview.error
1557
1729
  ? preview.error
1558
1730
  : preview.kind === 'image'
@@ -1562,23 +1734,25 @@ window.__ModuleLoader__.load({
1562
1734
  className: 'dsh-fe-preview-image' + (imageScale > 1 ? ' dsh-fe-preview-image-pannable' : '') + (imageDragging ? ' dsh-fe-preview-image-dragging' : ''),
1563
1735
  style: { transform: `translate3d(${imageOffset.x}px, ${imageOffset.y}px, 0) scale(${imageScale})` },
1564
1736
  src: preview.dataUrl,
1565
- alt: preview.name || 'Image preview',
1737
+ alt: preview.name || t('files.imagePreview'),
1566
1738
  draggable: false,
1567
1739
  })
1568
1740
  )
1569
- : preview.tooLarge ? 'Image is too large to preview here' : '(No preview)'
1741
+ : preview.tooLarge ? t('files.imageTooLarge') : t('files.noPreview')
1570
1742
  : preview.text !== undefined && preview.text !== null
1571
1743
  ? preview.text
1572
1744
  : preview.tooLarge
1573
- ? 'File is too large to preview here'
1574
- : preview.binary ? 'Binary file cannot be previewed' : '(No preview)',
1745
+ ? t('files.fileTooLarge')
1746
+ : preview.binary ? t('files.binaryNoPreview') : t('files.noPreview'),
1575
1747
  ),
1576
1748
  ),
1577
1749
  ),
1578
1750
  )
1579
1751
  }
1752
+ }
1580
1753
 
1581
- function FileExplorerGitPage(props) {
1754
+ function createFileExplorerGitPage(t) {
1755
+ return function FileExplorerGitPage(props) {
1582
1756
  if (typeof props.useSessions !== 'function') return null
1583
1757
 
1584
1758
  const sessionId = props.useSessions((state) => {
@@ -1599,23 +1773,28 @@ window.__ModuleLoader__.load({
1599
1773
  return React.createElement('div', {
1600
1774
  className: 'dsh-fe-page',
1601
1775
  role: 'region',
1602
- 'aria-label': 'Git Graph',
1776
+ 'aria-label': t('git.title'),
1603
1777
  }, React.createElement(GitGraphView, { api, cwd }))
1604
1778
  }
1779
+ }
1780
+
1781
+ const GitGraphView = createGitGraphView(t);
1782
+ const FileExplorerPage = createFileExplorerPage(t);
1783
+ const FileExplorerGitPage = createFileExplorerGitPage(t);
1605
1784
 
1606
1785
  ctx.effect(() => {
1607
1786
  const disposeFilesPage = rightPanel.registerPage({
1608
1787
  id: 'file-explorer.files',
1609
- title: 'Files',
1610
- group: 'Workspace',
1788
+ title: t('files.title'),
1789
+ group: t('workspace'),
1611
1790
  order: 20,
1612
1791
  placement: 'rail',
1613
1792
  icon: (size) => svgIcon(folderIcon, size),
1614
1793
  })
1615
1794
  const disposeGitPage = rightPanel.registerPage({
1616
1795
  id: 'file-explorer.git',
1617
- title: 'Git Graph',
1618
- group: 'Workspace',
1796
+ title: t('git.title'),
1797
+ group: t('workspace'),
1619
1798
  order: 21,
1620
1799
  placement: 'rail',
1621
1800
  icon: (size) => svgIcon(branchIcon, size),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yiln-dsh/dsh-plugin-file-explorer",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },