omnilane 0.10.3 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +67 -1
- package/README.ja.md +33 -1
- package/README.ko.md +32 -1
- package/README.md +38 -1
- package/README.zh-CN.md +28 -1
- package/README.zh-TW.md +28 -1
- package/VERSION +1 -1
- package/package.json +1 -1
- package/routing.yaml +1 -1
- package/ui/app.js +853 -74
- package/ui/index.html +70 -59
- package/ui/styles.css +28 -0
package/ui/app.js
CHANGED
|
@@ -2,14 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
(function () {
|
|
4
4
|
const TOKEN_STORAGE_KEY = "omnilane.live-ui.token";
|
|
5
|
+
const LANGUAGE_STORAGE_KEY = "omnilane.live-ui.language";
|
|
5
6
|
const DETAIL_CACHE_LIMIT = 50, DETAIL_PREFETCH_LIMIT = 12, DETAIL_CONCURRENCY = 3;
|
|
6
7
|
const OUTPUT_BOTTOM_THRESHOLD = 28;
|
|
7
8
|
const MOBILE_QUERY = "(max-width: 760px)";
|
|
8
9
|
const VALID_STATES = new Set(["starting", "running", "succeeded", "failed", "dead", "invalid"]);
|
|
10
|
+
const DEFAULT_LANGUAGE = "en";
|
|
11
|
+
const LANGUAGES = [
|
|
12
|
+
{ code: "en", label: "English" },
|
|
13
|
+
{ code: "ja", label: "日本語" },
|
|
14
|
+
{ code: "ko", label: "한국어" },
|
|
15
|
+
{ code: "zh-TW", label: "繁體中文" },
|
|
16
|
+
{ code: "zh-CN", label: "简体中文" },
|
|
17
|
+
];
|
|
9
18
|
|
|
10
19
|
const elements = {
|
|
11
20
|
connection: document.getElementById("connection-status"),
|
|
12
21
|
connectionLabel: document.getElementById("connection-label"),
|
|
22
|
+
languageSelect: document.getElementById("language-select"),
|
|
13
23
|
jobCount: document.getElementById("job-count"), search: document.getElementById("job-search"),
|
|
14
24
|
filter: document.getElementById("status-filter"),
|
|
15
25
|
filterButtons: Array.from(document.querySelectorAll(".filter-button")),
|
|
@@ -46,6 +56,8 @@
|
|
|
46
56
|
hasSnapshot: false, unauthorized: false, authProbeInFlight: false,
|
|
47
57
|
reconnectTimer: null, mobileListScroll: 0, mobileFocusId: null,
|
|
48
58
|
currentDetail: null, compareReference: null,
|
|
59
|
+
language: DEFAULT_LANGUAGE, connection: { mode: "waiting", key: "connection.connecting" },
|
|
60
|
+
inspectorView: null, detailPlaceholder: null,
|
|
49
61
|
};
|
|
50
62
|
|
|
51
63
|
function boardUrl() { return window.location.pathname + window.location.search; }
|
|
@@ -83,13 +95,203 @@
|
|
|
83
95
|
}
|
|
84
96
|
}
|
|
85
97
|
|
|
98
|
+
function isSupportedLanguage(code) {
|
|
99
|
+
return LANGUAGES.some(function (language) {
|
|
100
|
+
return language.code === code;
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function readStoredLanguage() {
|
|
105
|
+
try {
|
|
106
|
+
const stored = window.localStorage.getItem(LANGUAGE_STORAGE_KEY);
|
|
107
|
+
return isSupportedLanguage(stored) ? stored : null;
|
|
108
|
+
} catch (_error) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function storeLanguage(code) {
|
|
114
|
+
try {
|
|
115
|
+
window.localStorage.setItem(LANGUAGE_STORAGE_KEY, code);
|
|
116
|
+
} catch (_error) {
|
|
117
|
+
// A blocked local store still leaves the choice active for this page.
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function matchLanguage(tag) {
|
|
122
|
+
if (typeof tag !== "string" || tag.length === 0) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
const lower = tag.toLowerCase();
|
|
126
|
+
if (lower === "zh" || lower.startsWith("zh-hans") || lower.startsWith("zh-cn") || lower.startsWith("zh-sg")) {
|
|
127
|
+
return "zh-CN";
|
|
128
|
+
}
|
|
129
|
+
if (lower.startsWith("zh")) {
|
|
130
|
+
return "zh-TW";
|
|
131
|
+
}
|
|
132
|
+
if (lower.startsWith("ja")) {
|
|
133
|
+
return "ja";
|
|
134
|
+
}
|
|
135
|
+
if (lower.startsWith("ko")) {
|
|
136
|
+
return "ko";
|
|
137
|
+
}
|
|
138
|
+
if (lower.startsWith("en")) {
|
|
139
|
+
return "en";
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function detectLanguage() {
|
|
145
|
+
const stored = readStoredLanguage();
|
|
146
|
+
if (stored) {
|
|
147
|
+
return stored;
|
|
148
|
+
}
|
|
149
|
+
const tags = Array.isArray(window.navigator.languages) && window.navigator.languages.length > 0
|
|
150
|
+
? window.navigator.languages
|
|
151
|
+
: [window.navigator.language];
|
|
152
|
+
for (let index = 0; index < tags.length; index += 1) {
|
|
153
|
+
const matched = matchLanguage(tags[index]);
|
|
154
|
+
if (matched) {
|
|
155
|
+
return matched;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return DEFAULT_LANGUAGE;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function lookupMessage(language, key) {
|
|
162
|
+
const table = MESSAGES[language];
|
|
163
|
+
return table && Object.prototype.hasOwnProperty.call(table, key) ? table[key] : undefined;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function selectPluralForm(forms, params) {
|
|
167
|
+
const count = params && typeof params.n === "number" ? params.n : 0;
|
|
168
|
+
let category;
|
|
169
|
+
try {
|
|
170
|
+
category = new Intl.PluralRules(state.language).select(count);
|
|
171
|
+
} catch (_error) {
|
|
172
|
+
category = count === 1 ? "one" : "other";
|
|
173
|
+
}
|
|
174
|
+
return Object.prototype.hasOwnProperty.call(forms, category) ? forms[category] : forms.other;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function interpolate(template, params) {
|
|
178
|
+
if (!params) {
|
|
179
|
+
return template;
|
|
180
|
+
}
|
|
181
|
+
return template.replace(/\{(\w+)\}/g, function (match, name) {
|
|
182
|
+
return Object.prototype.hasOwnProperty.call(params, name) ? String(params[name]) : match;
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function t(key, params) {
|
|
187
|
+
let entry = lookupMessage(state.language, key);
|
|
188
|
+
if (entry === undefined) {
|
|
189
|
+
entry = lookupMessage(DEFAULT_LANGUAGE, key);
|
|
190
|
+
}
|
|
191
|
+
if (entry === undefined) {
|
|
192
|
+
return key;
|
|
193
|
+
}
|
|
194
|
+
if (typeof entry === "object") {
|
|
195
|
+
entry = selectPluralForm(entry, params);
|
|
196
|
+
}
|
|
197
|
+
return interpolate(entry, params);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function jobStateLabel(value) {
|
|
201
|
+
return t("jobState." + value);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function applyStaticTranslations() {
|
|
205
|
+
document.documentElement.lang = state.language;
|
|
206
|
+
document.querySelectorAll("[data-i18n]").forEach(function (element) {
|
|
207
|
+
element.textContent = t(element.dataset.i18n);
|
|
208
|
+
});
|
|
209
|
+
document.querySelectorAll("[data-i18n-attr]").forEach(function (element) {
|
|
210
|
+
element.dataset.i18nAttr.split(";").forEach(function (pair) {
|
|
211
|
+
const separator = pair.indexOf(":");
|
|
212
|
+
if (separator < 0) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const attribute = pair.slice(0, separator).trim();
|
|
216
|
+
const key = pair.slice(separator + 1).trim();
|
|
217
|
+
if (attribute && key) {
|
|
218
|
+
element.setAttribute(attribute, t(key));
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function populateLanguageSelect() {
|
|
225
|
+
const options = document.createDocumentFragment();
|
|
226
|
+
LANGUAGES.forEach(function (language) {
|
|
227
|
+
const option = document.createElement("option");
|
|
228
|
+
option.value = language.code;
|
|
229
|
+
option.lang = language.code;
|
|
230
|
+
option.textContent = language.label;
|
|
231
|
+
options.appendChild(option);
|
|
232
|
+
});
|
|
233
|
+
elements.languageSelect.replaceChildren(options);
|
|
234
|
+
elements.languageSelect.value = state.language;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function refreshLanguage() {
|
|
238
|
+
applyStaticTranslations();
|
|
239
|
+
renderConnection();
|
|
240
|
+
renderQueue();
|
|
241
|
+
|
|
242
|
+
if (elements.jobDetail.hidden) {
|
|
243
|
+
if (state.inspectorView) {
|
|
244
|
+
showInspectorState(state.inspectorView.code, state.inspectorView.title, state.inspectorView.message);
|
|
245
|
+
}
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (state.currentDetail) {
|
|
250
|
+
renderDetail(state.currentDetail);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const summary = currentSummary();
|
|
255
|
+
if (summary) {
|
|
256
|
+
renderSummary(summary);
|
|
257
|
+
}
|
|
258
|
+
if (state.detailPlaceholder === "loading" && summary) {
|
|
259
|
+
showDetailLoading(summary);
|
|
260
|
+
} else if (state.detailPlaceholder === "error") {
|
|
261
|
+
showDetailError();
|
|
262
|
+
} else {
|
|
263
|
+
renderCompare(null);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function setLanguage(code) {
|
|
268
|
+
if (!isSupportedLanguage(code) || code === state.language) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
state.language = code;
|
|
272
|
+
storeLanguage(code);
|
|
273
|
+
elements.languageSelect.value = code;
|
|
274
|
+
refreshLanguage();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function initializeLanguage() {
|
|
278
|
+
state.language = detectLanguage();
|
|
279
|
+
populateLanguageSelect();
|
|
280
|
+
applyStaticTranslations();
|
|
281
|
+
}
|
|
282
|
+
|
|
86
283
|
function setText(element, value) { element.textContent = value; }
|
|
87
284
|
|
|
88
285
|
function textOrFallback(value, fallback) { return typeof value === "string" && value.length > 0 ? value : fallback; }
|
|
89
286
|
|
|
90
|
-
function
|
|
91
|
-
elements.connection.dataset.mode = mode;
|
|
92
|
-
setText(elements.connectionLabel,
|
|
287
|
+
function renderConnection() {
|
|
288
|
+
elements.connection.dataset.mode = state.connection.mode;
|
|
289
|
+
setText(elements.connectionLabel, t(state.connection.key));
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function setConnection(mode, key) {
|
|
293
|
+
state.connection = { mode: mode, key: key };
|
|
294
|
+
renderConnection();
|
|
93
295
|
}
|
|
94
296
|
|
|
95
297
|
function setControlsDisabled(disabled) {
|
|
@@ -99,10 +301,11 @@
|
|
|
99
301
|
});
|
|
100
302
|
}
|
|
101
303
|
|
|
102
|
-
function showInspectorState(
|
|
103
|
-
|
|
104
|
-
setText(elements.
|
|
105
|
-
setText(elements.
|
|
304
|
+
function showInspectorState(codeKey, titleKey, messageKey) {
|
|
305
|
+
state.inspectorView = { code: codeKey, title: titleKey, message: messageKey };
|
|
306
|
+
setText(elements.stateCode, t(codeKey));
|
|
307
|
+
setText(elements.stateTitle, t(titleKey));
|
|
308
|
+
setText(elements.stateMessage, t(messageKey));
|
|
106
309
|
elements.inspectorState.hidden = false;
|
|
107
310
|
elements.jobDetail.hidden = true;
|
|
108
311
|
}
|
|
@@ -137,41 +340,41 @@
|
|
|
137
340
|
clearStoredToken();
|
|
138
341
|
closeEventStream();
|
|
139
342
|
cancelDetailRequests(true);
|
|
140
|
-
setConnection("unauthorized", "
|
|
141
|
-
setText(elements.jobCount, "0
|
|
343
|
+
setConnection("unauthorized", "connection.unauthorized");
|
|
344
|
+
setText(elements.jobCount, t("count.jobs", { n: 0 }));
|
|
142
345
|
setControlsDisabled(true);
|
|
143
346
|
renderQueue();
|
|
144
347
|
setMobileView("list", false);
|
|
145
348
|
showInspectorState(
|
|
146
|
-
"
|
|
147
|
-
"
|
|
148
|
-
"
|
|
349
|
+
"state.local.code",
|
|
350
|
+
"state.unauthorized.title",
|
|
351
|
+
"state.unauthorized.message"
|
|
149
352
|
);
|
|
150
353
|
}
|
|
151
354
|
|
|
152
355
|
function showNoJobs() {
|
|
153
356
|
showInspectorState(
|
|
154
|
-
"
|
|
155
|
-
"
|
|
156
|
-
"
|
|
357
|
+
"state.empty.code",
|
|
358
|
+
"state.empty.title",
|
|
359
|
+
"state.empty.message"
|
|
157
360
|
);
|
|
158
361
|
}
|
|
159
362
|
|
|
160
363
|
function showNoMatches() {
|
|
161
364
|
showInspectorState(
|
|
162
|
-
"
|
|
163
|
-
"
|
|
164
|
-
"
|
|
365
|
+
"state.noMatch.code",
|
|
366
|
+
"state.noMatch.title",
|
|
367
|
+
"state.noMatch.message"
|
|
165
368
|
);
|
|
166
369
|
}
|
|
167
370
|
|
|
168
371
|
function showReconnecting() {
|
|
169
|
-
setConnection("reconnecting", "
|
|
372
|
+
setConnection("reconnecting", "connection.reconnecting");
|
|
170
373
|
if (!state.hasSnapshot) {
|
|
171
374
|
showInspectorState(
|
|
172
|
-
"
|
|
173
|
-
"
|
|
174
|
-
"
|
|
375
|
+
"state.retry.code",
|
|
376
|
+
"state.retry.title",
|
|
377
|
+
"state.retry.message"
|
|
175
378
|
);
|
|
176
379
|
}
|
|
177
380
|
}
|
|
@@ -226,7 +429,7 @@
|
|
|
226
429
|
function taskSummary(job) {
|
|
227
430
|
const detail = cachedDetail(job.id);
|
|
228
431
|
if (!detail || typeof detail.task !== "string" || detail.task.trim().length === 0) {
|
|
229
|
-
return "
|
|
432
|
+
return t("loading.task");
|
|
230
433
|
}
|
|
231
434
|
const compact = detail.task.replace(/\s+/g, " ").trim();
|
|
232
435
|
return compact.length > 150 ? compact.slice(0, 147) + "…" : compact;
|
|
@@ -253,6 +456,7 @@
|
|
|
253
456
|
const searchText = [
|
|
254
457
|
job.id,
|
|
255
458
|
job.state,
|
|
459
|
+
jobStateLabel(job.state),
|
|
256
460
|
taskSummary(job),
|
|
257
461
|
textOrFallback(meta.lane, ""),
|
|
258
462
|
textOrFallback(meta.vendor, ""),
|
|
@@ -270,9 +474,9 @@
|
|
|
270
474
|
function routeLabel(job) {
|
|
271
475
|
const meta = job.meta;
|
|
272
476
|
return [
|
|
273
|
-
textOrFallback(meta.lane, "
|
|
274
|
-
textOrFallback(meta.vendor, "
|
|
275
|
-
textOrFallback(meta.model, "
|
|
477
|
+
textOrFallback(meta.lane, t("value.unknownLane")),
|
|
478
|
+
textOrFallback(meta.vendor, t("value.unknownVendor")),
|
|
479
|
+
textOrFallback(meta.model, t("value.unknownModel")),
|
|
276
480
|
].join(" · ");
|
|
277
481
|
}
|
|
278
482
|
|
|
@@ -316,7 +520,7 @@
|
|
|
316
520
|
setText(button.querySelector(".card-route"), routeLabel(job));
|
|
317
521
|
const stateElement = button.querySelector(".card-state");
|
|
318
522
|
stateElement.className = "card-state state-" + job.state;
|
|
319
|
-
setText(stateElement, job.state);
|
|
523
|
+
setText(stateElement, jobStateLabel(job.state));
|
|
320
524
|
}
|
|
321
525
|
|
|
322
526
|
function renderQueue() {
|
|
@@ -357,18 +561,18 @@
|
|
|
357
561
|
}
|
|
358
562
|
|
|
359
563
|
if (state.jobs.length === 0) {
|
|
360
|
-
setText(elements.listMessage, state.unauthorized ? "
|
|
564
|
+
setText(elements.listMessage, state.unauthorized ? t("list.unauthorized") : t("list.empty"));
|
|
361
565
|
elements.listMessage.hidden = false;
|
|
362
566
|
} else if (jobs.length === 0) {
|
|
363
|
-
setText(elements.listMessage, "
|
|
567
|
+
setText(elements.listMessage, t("list.noMatch"));
|
|
364
568
|
elements.listMessage.hidden = false;
|
|
365
569
|
} else {
|
|
366
570
|
elements.listMessage.hidden = true;
|
|
367
571
|
}
|
|
368
572
|
|
|
369
573
|
const countLabel = jobs.length === state.jobs.length
|
|
370
|
-
?
|
|
371
|
-
:
|
|
574
|
+
? t("count.jobs", { n: state.jobs.length })
|
|
575
|
+
: t("count.filtered", { shown: jobs.length, total: state.jobs.length });
|
|
372
576
|
setText(elements.jobCount, countLabel);
|
|
373
577
|
}
|
|
374
578
|
|
|
@@ -521,7 +725,7 @@
|
|
|
521
725
|
state.hasSnapshot = true;
|
|
522
726
|
state.unauthorized = false;
|
|
523
727
|
setControlsDisabled(false);
|
|
524
|
-
setConnection("live", "
|
|
728
|
+
setConnection("live", "connection.live");
|
|
525
729
|
|
|
526
730
|
const jobs = reconcileSelection();
|
|
527
731
|
renderQueue();
|
|
@@ -589,7 +793,7 @@
|
|
|
589
793
|
function comparisonOutput(snapshot) {
|
|
590
794
|
const invalidFiles = invalidFileSet(snapshot);
|
|
591
795
|
if (invalidFiles.has("out.txt")) {
|
|
592
|
-
return "
|
|
796
|
+
return t("result.unsafe");
|
|
593
797
|
}
|
|
594
798
|
if (snapshot.output.length > 0) {
|
|
595
799
|
return snapshot.output;
|
|
@@ -601,10 +805,10 @@
|
|
|
601
805
|
const summary = snapshot.summary;
|
|
602
806
|
const meta = summary.meta;
|
|
603
807
|
setText(elements[prefix + "Id"], summary.id);
|
|
604
|
-
setText(elements[prefix + "Lane"], metadataValue(meta, "lane", "
|
|
605
|
-
setText(elements[prefix + "Vendor"], metadataValue(meta, "vendor", "
|
|
606
|
-
setText(elements[prefix + "Model"], metadataValue(meta, "model", "
|
|
607
|
-
setText(elements[prefix + "State"], summary.state);
|
|
808
|
+
setText(elements[prefix + "Lane"], metadataValue(meta, "lane", t("value.unknown")));
|
|
809
|
+
setText(elements[prefix + "Vendor"], metadataValue(meta, "vendor", t("value.unknown")));
|
|
810
|
+
setText(elements[prefix + "Model"], metadataValue(meta, "model", t("value.unknown")));
|
|
811
|
+
setText(elements[prefix + "State"], jobStateLabel(summary.state));
|
|
608
812
|
setText(elements[prefix + "Output"], comparisonOutput(snapshot));
|
|
609
813
|
}
|
|
610
814
|
|
|
@@ -616,11 +820,11 @@
|
|
|
616
820
|
elements.compareToggle.setAttribute("aria-pressed", isReference ? "true" : "false");
|
|
617
821
|
setText(
|
|
618
822
|
elements.compareToggle,
|
|
619
|
-
isReference ? "
|
|
823
|
+
isReference ? t("compare.unpin") : reference ? t("compare.replace") : t("compare.pin")
|
|
620
824
|
);
|
|
621
825
|
setText(
|
|
622
826
|
elements.compareReferenceLabel,
|
|
623
|
-
reference ? "
|
|
827
|
+
reference ? t("compare.pinned", { id: reference.summary.id }) : t("compare.none")
|
|
624
828
|
);
|
|
625
829
|
|
|
626
830
|
if (!reference || !current || isReference) {
|
|
@@ -659,20 +863,26 @@
|
|
|
659
863
|
elements.inspectorState.hidden = true;
|
|
660
864
|
elements.jobDetail.hidden = false;
|
|
661
865
|
setText(elements.selectedJobId, summary.id);
|
|
662
|
-
setText(elements.selectedJobState, summary.state);
|
|
663
|
-
setText(
|
|
866
|
+
setText(elements.selectedJobState, jobStateLabel(summary.state));
|
|
867
|
+
setText(
|
|
868
|
+
elements.selectedJobTime,
|
|
869
|
+
t("detail.timeStarted", { value: metadataValue(meta, "started", t("detail.timeUnavailable")) })
|
|
870
|
+
);
|
|
664
871
|
setStateClass(elements.selectedJobState, summary.state);
|
|
665
872
|
elements.routeTrack.dataset.state = summary.state;
|
|
666
|
-
setText(elements.routeLane, metadataValue(meta, "lane", "
|
|
667
|
-
setText(elements.routeVendor, metadataValue(meta, "vendor", "
|
|
668
|
-
setText(elements.routeModel, metadataValue(meta, "model", "
|
|
669
|
-
setText(elements.routeState, summary.state);
|
|
670
|
-
setText(elements.factEffort, metadataValue(meta, "effort", "
|
|
671
|
-
setText(elements.factMode, metadataValue(meta, "mode", "
|
|
672
|
-
setText(
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
873
|
+
setText(elements.routeLane, metadataValue(meta, "lane", t("value.unknown")));
|
|
874
|
+
setText(elements.routeVendor, metadataValue(meta, "vendor", t("value.unknown")));
|
|
875
|
+
setText(elements.routeModel, metadataValue(meta, "model", t("value.unknown")));
|
|
876
|
+
setText(elements.routeState, jobStateLabel(summary.state));
|
|
877
|
+
setText(elements.factEffort, metadataValue(meta, "effort", t("value.notRecorded")));
|
|
878
|
+
setText(elements.factMode, metadataValue(meta, "mode", t("value.notRecorded")));
|
|
879
|
+
setText(
|
|
880
|
+
elements.factTimeout,
|
|
881
|
+
Number.isInteger(meta.timeout) ? t("value.seconds", { n: meta.timeout }) : t("value.notRecorded")
|
|
882
|
+
);
|
|
883
|
+
setText(elements.factCandidate, metadataValue(meta, "candidate", t("value.notRecorded")));
|
|
884
|
+
setText(elements.factStarted, metadataValue(meta, "started", t("value.notRecorded")));
|
|
885
|
+
setText(elements.factWorkdir, metadataValue(meta, "workdir", t("value.notRecorded")));
|
|
676
886
|
}
|
|
677
887
|
|
|
678
888
|
function isMobile() {
|
|
@@ -759,6 +969,7 @@
|
|
|
759
969
|
|
|
760
970
|
function showDetailLoading(job) {
|
|
761
971
|
state.currentDetail = null;
|
|
972
|
+
state.detailPlaceholder = "loading";
|
|
762
973
|
renderCompare(null);
|
|
763
974
|
clearMarkers(elements.requestMarkers);
|
|
764
975
|
clearMarkers(elements.resultMarkers);
|
|
@@ -766,8 +977,8 @@
|
|
|
766
977
|
elements.resultContent.hidden = true;
|
|
767
978
|
elements.requestEmpty.hidden = false;
|
|
768
979
|
elements.resultEmpty.hidden = false;
|
|
769
|
-
setText(elements.requestEmpty, "
|
|
770
|
-
setText(elements.resultEmpty, job.state === "running" ? "
|
|
980
|
+
setText(elements.requestEmpty, t("loading.task"));
|
|
981
|
+
setText(elements.resultEmpty, job.state === "running" ? t("loading.running") : t("loading.output"));
|
|
771
982
|
}
|
|
772
983
|
|
|
773
984
|
function fetchDetail(jobId, showLoading) {
|
|
@@ -817,22 +1028,23 @@
|
|
|
817
1028
|
|
|
818
1029
|
function emptyResultMessage(summary) {
|
|
819
1030
|
if (summary.state === "starting") {
|
|
820
|
-
return "
|
|
1031
|
+
return t("result.starting");
|
|
821
1032
|
}
|
|
822
1033
|
if (summary.state === "running") {
|
|
823
|
-
return "
|
|
1034
|
+
return t("result.running");
|
|
824
1035
|
}
|
|
825
1036
|
if (summary.state === "failed") {
|
|
826
|
-
|
|
827
|
-
|
|
1037
|
+
return summary.exitCode === null
|
|
1038
|
+
? t("result.failedUnknown")
|
|
1039
|
+
: t("result.failedCode", { code: summary.exitCode });
|
|
828
1040
|
}
|
|
829
1041
|
if (summary.state === "dead") {
|
|
830
|
-
return "
|
|
1042
|
+
return t("result.dead");
|
|
831
1043
|
}
|
|
832
1044
|
if (summary.state === "invalid") {
|
|
833
|
-
return "
|
|
1045
|
+
return t("result.invalid");
|
|
834
1046
|
}
|
|
835
|
-
return "
|
|
1047
|
+
return t("result.none");
|
|
836
1048
|
}
|
|
837
1049
|
|
|
838
1050
|
function renderPlainText(contentElement, emptyElement, value, emptyMessage, followBottom) {
|
|
@@ -872,6 +1084,7 @@
|
|
|
872
1084
|
}
|
|
873
1085
|
|
|
874
1086
|
state.currentDetail = detail;
|
|
1087
|
+
state.detailPlaceholder = null;
|
|
875
1088
|
renderSummary(summary);
|
|
876
1089
|
renderCompare(detail);
|
|
877
1090
|
clearMarkers(elements.requestMarkers);
|
|
@@ -879,53 +1092,54 @@
|
|
|
879
1092
|
const invalidFiles = invalidFileSet(detail);
|
|
880
1093
|
|
|
881
1094
|
if (detail.taskTruncated === true) {
|
|
882
|
-
addMarker(elements.requestMarkers, "
|
|
1095
|
+
addMarker(elements.requestMarkers, t("marker.truncated"), false);
|
|
883
1096
|
}
|
|
884
1097
|
if (detail.outputTruncated === true) {
|
|
885
|
-
addMarker(elements.resultMarkers, "
|
|
1098
|
+
addMarker(elements.resultMarkers, t("marker.truncated"), false);
|
|
886
1099
|
}
|
|
887
1100
|
if (invalidFiles.has("task.txt")) {
|
|
888
|
-
addMarker(elements.requestMarkers, "
|
|
1101
|
+
addMarker(elements.requestMarkers, t("marker.taskUnsafe"), true);
|
|
889
1102
|
}
|
|
890
1103
|
if (invalidFiles.has("out.txt")) {
|
|
891
|
-
addMarker(elements.resultMarkers, "
|
|
1104
|
+
addMarker(elements.resultMarkers, t("marker.resultUnsafe"), true);
|
|
892
1105
|
}
|
|
893
1106
|
if (summary.state === "failed") {
|
|
894
1107
|
addMarker(
|
|
895
1108
|
elements.resultMarkers,
|
|
896
1109
|
summary.exitCode === null
|
|
897
|
-
? "
|
|
898
|
-
: "
|
|
1110
|
+
? t("marker.failedNoCode")
|
|
1111
|
+
: t("marker.failedCode", { code: summary.exitCode }),
|
|
899
1112
|
true
|
|
900
1113
|
);
|
|
901
1114
|
} else if (summary.state === "dead") {
|
|
902
|
-
addMarker(elements.resultMarkers, "
|
|
1115
|
+
addMarker(elements.resultMarkers, t("marker.dead"), true);
|
|
903
1116
|
} else if (summary.state === "invalid") {
|
|
904
|
-
addMarker(elements.resultMarkers, "
|
|
1117
|
+
addMarker(elements.resultMarkers, t("marker.invalid"), true);
|
|
905
1118
|
}
|
|
906
1119
|
|
|
907
1120
|
renderPlainText(
|
|
908
1121
|
elements.requestContent,
|
|
909
1122
|
elements.requestEmpty,
|
|
910
1123
|
detail.task,
|
|
911
|
-
invalidFiles.has("task.txt") ? "
|
|
1124
|
+
invalidFiles.has("task.txt") ? t("task.unsafe") : t("task.none"),
|
|
912
1125
|
false
|
|
913
1126
|
);
|
|
914
1127
|
renderPlainText(
|
|
915
1128
|
elements.resultContent,
|
|
916
1129
|
elements.resultEmpty,
|
|
917
1130
|
detail.output,
|
|
918
|
-
invalidFiles.has("out.txt") ? "
|
|
1131
|
+
invalidFiles.has("out.txt") ? t("result.unsafe") : emptyResultMessage(summary),
|
|
919
1132
|
true
|
|
920
1133
|
);
|
|
921
1134
|
}
|
|
922
1135
|
|
|
923
1136
|
function showDetailError() {
|
|
1137
|
+
state.detailPlaceholder = "error";
|
|
924
1138
|
clearMarkers(elements.requestMarkers);
|
|
925
1139
|
clearMarkers(elements.resultMarkers);
|
|
926
|
-
addMarker(elements.resultMarkers, "
|
|
927
|
-
setText(elements.requestEmpty, "
|
|
928
|
-
setText(elements.resultEmpty, "
|
|
1140
|
+
addMarker(elements.resultMarkers, t("marker.detailUnavailable"), true);
|
|
1141
|
+
setText(elements.requestEmpty, t("error.taskPreserved"));
|
|
1142
|
+
setText(elements.resultEmpty, t("error.reconnecting"));
|
|
929
1143
|
elements.requestEmpty.hidden = false;
|
|
930
1144
|
elements.resultEmpty.hidden = false;
|
|
931
1145
|
}
|
|
@@ -1029,6 +1243,10 @@
|
|
|
1029
1243
|
}
|
|
1030
1244
|
|
|
1031
1245
|
function bindControls() {
|
|
1246
|
+
elements.languageSelect.addEventListener("change", function () {
|
|
1247
|
+
setLanguage(elements.languageSelect.value);
|
|
1248
|
+
});
|
|
1249
|
+
|
|
1032
1250
|
elements.search.addEventListener("input", function () {
|
|
1033
1251
|
state.query = elements.search.value.trim().toLocaleLowerCase();
|
|
1034
1252
|
updateFilterSelection();
|
|
@@ -1088,7 +1306,568 @@
|
|
|
1088
1306
|
setMobileView("list", false);
|
|
1089
1307
|
}
|
|
1090
1308
|
|
|
1309
|
+
// The locale tables sit after the board logic so the readable code stays at the
|
|
1310
|
+
// top of the file; nothing reads MESSAGES until start() runs at the very end.
|
|
1311
|
+
const MESSAGES = {
|
|
1312
|
+
en: {
|
|
1313
|
+
"app.title": "Omnilane Live Board",
|
|
1314
|
+
"header.liveJobs": "Live jobs",
|
|
1315
|
+
"header.readOnly": "Read only",
|
|
1316
|
+
"header.language": "Language",
|
|
1317
|
+
"connection.connecting": "Connecting",
|
|
1318
|
+
"connection.live": "Live local signal",
|
|
1319
|
+
"connection.reconnecting": "Reconnecting",
|
|
1320
|
+
"connection.unauthorized": "Not authorized",
|
|
1321
|
+
"index.sectionLabel": "Recent work",
|
|
1322
|
+
"index.title": "Task record",
|
|
1323
|
+
"search.label": "Find a task",
|
|
1324
|
+
"search.placeholder": "Task, ID, lane, model",
|
|
1325
|
+
"filter.legend": "Show",
|
|
1326
|
+
"filter.all": "All",
|
|
1327
|
+
"filter.active": "Active",
|
|
1328
|
+
"filter.done": "Done",
|
|
1329
|
+
"filter.issues": "Issues",
|
|
1330
|
+
"list.ariaLabel": "Recent tasks",
|
|
1331
|
+
"list.waiting": "Waiting for tasks.",
|
|
1332
|
+
"list.empty": "No tasks yet.",
|
|
1333
|
+
"list.unauthorized": "A fresh local link is required.",
|
|
1334
|
+
"list.noMatch": "No tasks match this filter.",
|
|
1335
|
+
"count.jobs": { one: "{n} job", other: "{n} jobs" },
|
|
1336
|
+
"count.filtered": "{shown} / {total} jobs",
|
|
1337
|
+
"inspector.ariaLabel": "Selected task detail",
|
|
1338
|
+
"state.local.code": "Local · read only",
|
|
1339
|
+
"state.connecting.title": "Connecting to the job board",
|
|
1340
|
+
"state.connecting.message": "The newest task will appear here.",
|
|
1341
|
+
"state.unauthorized.title": "Local access required",
|
|
1342
|
+
"state.unauthorized.message": "Run `omnilane ui url` for a fresh local link.",
|
|
1343
|
+
"state.empty.code": "Queue · empty",
|
|
1344
|
+
"state.empty.title": "No tasks yet",
|
|
1345
|
+
"state.empty.message": "Run an Omnilane task. This board will update automatically.",
|
|
1346
|
+
"state.noMatch.code": "Filter · no match",
|
|
1347
|
+
"state.noMatch.title": "No matching tasks",
|
|
1348
|
+
"state.noMatch.message": "Clear the search or choose another state.",
|
|
1349
|
+
"state.retry.code": "Link · retrying",
|
|
1350
|
+
"state.retry.title": "Local board unavailable",
|
|
1351
|
+
"state.retry.message": "Reconnecting to the local job board.",
|
|
1352
|
+
"mobile.back": "Back to tasks",
|
|
1353
|
+
"detail.sectionLabel": "Selected task",
|
|
1354
|
+
"detail.timeStarted": "Started {value}",
|
|
1355
|
+
"detail.timeUnavailable": "time unavailable",
|
|
1356
|
+
"detail.timeUnknown": "Started time unavailable",
|
|
1357
|
+
"compare.pin": "Pin for compare",
|
|
1358
|
+
"compare.unpin": "Unpin reference",
|
|
1359
|
+
"compare.replace": "Replace reference",
|
|
1360
|
+
"compare.none": "No reference pinned",
|
|
1361
|
+
"compare.pinned": "Reference {id} pinned",
|
|
1362
|
+
"compare.sectionLabel": "Reference snapshot",
|
|
1363
|
+
"compare.heading": "Route and result comparison",
|
|
1364
|
+
"compare.clear": "Clear reference",
|
|
1365
|
+
"compare.roleReference": "Pinned reference",
|
|
1366
|
+
"compare.roleCurrent": "Current selection",
|
|
1367
|
+
"compare.outputLabel": "Public result",
|
|
1368
|
+
"field.lane": "Lane",
|
|
1369
|
+
"field.vendor": "Vendor",
|
|
1370
|
+
"field.model": "Model",
|
|
1371
|
+
"field.state": "State",
|
|
1372
|
+
"field.effort": "Effort",
|
|
1373
|
+
"field.mode": "Mode",
|
|
1374
|
+
"field.watchdog": "Watchdog",
|
|
1375
|
+
"field.candidate": "Candidate",
|
|
1376
|
+
"field.started": "Started",
|
|
1377
|
+
"field.workdir": "Workdir",
|
|
1378
|
+
"task.sectionLabel": "Task",
|
|
1379
|
+
"task.heading": "What the model was asked",
|
|
1380
|
+
"output.sectionLabel": "Output",
|
|
1381
|
+
"output.heading": "Public result",
|
|
1382
|
+
"routing.sectionLabel": "Routing",
|
|
1383
|
+
"routing.heading": "Model path",
|
|
1384
|
+
"meta.summary": "Technical metadata",
|
|
1385
|
+
"meta.ariaLabel": "Task metadata",
|
|
1386
|
+
"value.unknown": "Unknown",
|
|
1387
|
+
"value.notRecorded": "Not recorded",
|
|
1388
|
+
"value.unknownLane": "Unknown lane",
|
|
1389
|
+
"value.unknownVendor": "Unknown vendor",
|
|
1390
|
+
"value.unknownModel": "Unknown model",
|
|
1391
|
+
"value.seconds": "{n} s",
|
|
1392
|
+
"loading.task": "Loading task…",
|
|
1393
|
+
"loading.output": "Loading output…",
|
|
1394
|
+
"loading.running": "Worker is running. Waiting for output…",
|
|
1395
|
+
"result.starting": "Dispatch is starting. No public result has been recorded yet.",
|
|
1396
|
+
"result.running": "Worker is running. The public result will appear here.",
|
|
1397
|
+
"result.failedCode": "Dispatch failed with exit code {code}. No public result was recorded.",
|
|
1398
|
+
"result.failedUnknown": "Dispatch failed with an unknown code. No public result was recorded.",
|
|
1399
|
+
"result.dead": "The worker is gone and no exit code was recorded.",
|
|
1400
|
+
"result.invalid": "This dispatch contains invalid metadata or control files. No safe result is available.",
|
|
1401
|
+
"result.none": "Dispatch completed without a public result.",
|
|
1402
|
+
"result.unsafe": "The public result could not be read safely.",
|
|
1403
|
+
"task.unsafe": "The task could not be read safely.",
|
|
1404
|
+
"task.none": "No task was recorded.",
|
|
1405
|
+
"marker.truncated": "Large content shortened to 512 KiB",
|
|
1406
|
+
"marker.taskUnsafe": "Task file could not be read safely",
|
|
1407
|
+
"marker.resultUnsafe": "Result file could not be read safely",
|
|
1408
|
+
"marker.failedNoCode": "Dispatch failed without a readable exit code",
|
|
1409
|
+
"marker.failedCode": "Dispatch failed with exit code {code}",
|
|
1410
|
+
"marker.dead": "Worker gone · exit code not recorded",
|
|
1411
|
+
"marker.invalid": "Invalid dispatch · safe fields only",
|
|
1412
|
+
"marker.detailUnavailable": "Detail temporarily unavailable",
|
|
1413
|
+
"error.taskPreserved": "The last task snapshot is preserved while detail reloads.",
|
|
1414
|
+
"error.reconnecting": "Reconnecting to the local job board.",
|
|
1415
|
+
"jobState.starting": "starting",
|
|
1416
|
+
"jobState.running": "running",
|
|
1417
|
+
"jobState.succeeded": "succeeded",
|
|
1418
|
+
"jobState.failed": "failed",
|
|
1419
|
+
"jobState.dead": "dead",
|
|
1420
|
+
"jobState.invalid": "invalid",
|
|
1421
|
+
"noscript": "JavaScript is required to read this local board.",
|
|
1422
|
+
},
|
|
1423
|
+
ja: {
|
|
1424
|
+
"app.title": "Omnilane ライブボード",
|
|
1425
|
+
"header.liveJobs": "実行中のジョブ",
|
|
1426
|
+
"header.readOnly": "読み取り専用",
|
|
1427
|
+
"header.language": "言語",
|
|
1428
|
+
"connection.connecting": "接続中",
|
|
1429
|
+
"connection.live": "ローカル信号を受信中",
|
|
1430
|
+
"connection.reconnecting": "再接続中",
|
|
1431
|
+
"connection.unauthorized": "未認証",
|
|
1432
|
+
"index.sectionLabel": "最近の作業",
|
|
1433
|
+
"index.title": "タスク記録",
|
|
1434
|
+
"search.label": "タスクを検索",
|
|
1435
|
+
"search.placeholder": "タスク、ID、レーン、モデル",
|
|
1436
|
+
"filter.legend": "表示",
|
|
1437
|
+
"filter.all": "すべて",
|
|
1438
|
+
"filter.active": "実行中",
|
|
1439
|
+
"filter.done": "完了",
|
|
1440
|
+
"filter.issues": "問題あり",
|
|
1441
|
+
"list.ariaLabel": "最近のタスク",
|
|
1442
|
+
"list.waiting": "タスクを待機しています。",
|
|
1443
|
+
"list.empty": "タスクはまだありません。",
|
|
1444
|
+
"list.unauthorized": "新しいローカルリンクが必要です。",
|
|
1445
|
+
"list.noMatch": "このフィルターに一致するタスクはありません。",
|
|
1446
|
+
"count.jobs": { other: "{n} 件のジョブ" },
|
|
1447
|
+
"count.filtered": "{shown} / {total} 件のジョブ",
|
|
1448
|
+
"inspector.ariaLabel": "選択したタスクの詳細",
|
|
1449
|
+
"state.local.code": "ローカル · 読み取り専用",
|
|
1450
|
+
"state.connecting.title": "ジョブボードに接続しています",
|
|
1451
|
+
"state.connecting.message": "最新のタスクがここに表示されます。",
|
|
1452
|
+
"state.unauthorized.title": "ローカルアクセスが必要です",
|
|
1453
|
+
"state.unauthorized.message": "`omnilane ui url` を実行して新しいローカルリンクを取得してください。",
|
|
1454
|
+
"state.empty.code": "キュー · 空",
|
|
1455
|
+
"state.empty.title": "タスクはまだありません",
|
|
1456
|
+
"state.empty.message": "Omnilane タスクを実行してください。このボードは自動的に更新されます。",
|
|
1457
|
+
"state.noMatch.code": "フィルター · 一致なし",
|
|
1458
|
+
"state.noMatch.title": "一致するタスクがありません",
|
|
1459
|
+
"state.noMatch.message": "検索条件を消すか、別の状態を選んでください。",
|
|
1460
|
+
"state.retry.code": "リンク · 再試行中",
|
|
1461
|
+
"state.retry.title": "ローカルボードに接続できません",
|
|
1462
|
+
"state.retry.message": "ローカルジョブボードに再接続しています。",
|
|
1463
|
+
"mobile.back": "タスク一覧に戻る",
|
|
1464
|
+
"detail.sectionLabel": "選択したタスク",
|
|
1465
|
+
"detail.timeStarted": "開始 {value}",
|
|
1466
|
+
"detail.timeUnavailable": "時刻不明",
|
|
1467
|
+
"detail.timeUnknown": "開始 時刻不明",
|
|
1468
|
+
"compare.pin": "比較用にピン留め",
|
|
1469
|
+
"compare.unpin": "ピン留めを解除",
|
|
1470
|
+
"compare.replace": "基準を差し替える",
|
|
1471
|
+
"compare.none": "基準は未設定",
|
|
1472
|
+
"compare.pinned": "基準 {id} をピン留め中",
|
|
1473
|
+
"compare.sectionLabel": "基準スナップショット",
|
|
1474
|
+
"compare.heading": "ルートと結果の比較",
|
|
1475
|
+
"compare.clear": "基準をクリア",
|
|
1476
|
+
"compare.roleReference": "ピン留めした基準",
|
|
1477
|
+
"compare.roleCurrent": "現在の選択",
|
|
1478
|
+
"compare.outputLabel": "公開結果",
|
|
1479
|
+
"field.lane": "レーン",
|
|
1480
|
+
"field.vendor": "ベンダー",
|
|
1481
|
+
"field.model": "モデル",
|
|
1482
|
+
"field.state": "状態",
|
|
1483
|
+
"field.effort": "エフォート",
|
|
1484
|
+
"field.mode": "モード",
|
|
1485
|
+
"field.watchdog": "ウォッチドッグ",
|
|
1486
|
+
"field.candidate": "候補",
|
|
1487
|
+
"field.started": "開始時刻",
|
|
1488
|
+
"field.workdir": "作業ディレクトリ",
|
|
1489
|
+
"task.sectionLabel": "タスク",
|
|
1490
|
+
"task.heading": "モデルへの依頼内容",
|
|
1491
|
+
"output.sectionLabel": "出力",
|
|
1492
|
+
"output.heading": "公開結果",
|
|
1493
|
+
"routing.sectionLabel": "ルーティング",
|
|
1494
|
+
"routing.heading": "モデル経路",
|
|
1495
|
+
"meta.summary": "技術メタデータ",
|
|
1496
|
+
"meta.ariaLabel": "タスクのメタデータ",
|
|
1497
|
+
"value.unknown": "不明",
|
|
1498
|
+
"value.notRecorded": "記録なし",
|
|
1499
|
+
"value.unknownLane": "レーン不明",
|
|
1500
|
+
"value.unknownVendor": "ベンダー不明",
|
|
1501
|
+
"value.unknownModel": "モデル不明",
|
|
1502
|
+
"value.seconds": "{n} 秒",
|
|
1503
|
+
"loading.task": "タスクを読み込み中…",
|
|
1504
|
+
"loading.output": "出力を読み込み中…",
|
|
1505
|
+
"loading.running": "ワーカーが実行中です。出力を待っています…",
|
|
1506
|
+
"result.starting": "ディスパッチを開始しています。公開結果はまだ記録されていません。",
|
|
1507
|
+
"result.running": "ワーカーが実行中です。公開結果はここに表示されます。",
|
|
1508
|
+
"result.failedCode": "ディスパッチは終了コード {code} で失敗しました。公開結果は記録されていません。",
|
|
1509
|
+
"result.failedUnknown": "ディスパッチは不明なコードで失敗しました。公開結果は記録されていません。",
|
|
1510
|
+
"result.dead": "ワーカーが消失し、終了コードは記録されていません。",
|
|
1511
|
+
"result.invalid": "このディスパッチには無効なメタデータまたは制御ファイルが含まれています。安全な結果はありません。",
|
|
1512
|
+
"result.none": "ディスパッチは公開結果なしで完了しました。",
|
|
1513
|
+
"result.unsafe": "公開結果を安全に読み取れませんでした。",
|
|
1514
|
+
"task.unsafe": "タスクを安全に読み取れませんでした。",
|
|
1515
|
+
"task.none": "タスクは記録されていません。",
|
|
1516
|
+
"marker.truncated": "大きな内容を 512 KiB に短縮しました",
|
|
1517
|
+
"marker.taskUnsafe": "タスクファイルを安全に読み取れませんでした",
|
|
1518
|
+
"marker.resultUnsafe": "結果ファイルを安全に読み取れませんでした",
|
|
1519
|
+
"marker.failedNoCode": "終了コードを読み取れないまま失敗しました",
|
|
1520
|
+
"marker.failedCode": "終了コード {code} で失敗しました",
|
|
1521
|
+
"marker.dead": "ワーカー消失 · 終了コード未記録",
|
|
1522
|
+
"marker.invalid": "無効なディスパッチ · 安全な項目のみ",
|
|
1523
|
+
"marker.detailUnavailable": "詳細を一時的に取得できません",
|
|
1524
|
+
"error.taskPreserved": "詳細の再読み込み中は直前のタスクを表示しています。",
|
|
1525
|
+
"error.reconnecting": "ローカルジョブボードに再接続しています。",
|
|
1526
|
+
"jobState.starting": "開始中",
|
|
1527
|
+
"jobState.running": "実行中",
|
|
1528
|
+
"jobState.succeeded": "成功",
|
|
1529
|
+
"jobState.failed": "失敗",
|
|
1530
|
+
"jobState.dead": "消失",
|
|
1531
|
+
"jobState.invalid": "無効",
|
|
1532
|
+
"noscript": "このローカルボードを表示するには JavaScript が必要です。",
|
|
1533
|
+
},
|
|
1534
|
+
ko: {
|
|
1535
|
+
"app.title": "Omnilane 라이브 보드",
|
|
1536
|
+
"header.liveJobs": "실행 중인 작업",
|
|
1537
|
+
"header.readOnly": "읽기 전용",
|
|
1538
|
+
"header.language": "언어",
|
|
1539
|
+
"connection.connecting": "연결 중",
|
|
1540
|
+
"connection.live": "로컬 신호 수신 중",
|
|
1541
|
+
"connection.reconnecting": "다시 연결 중",
|
|
1542
|
+
"connection.unauthorized": "인증되지 않음",
|
|
1543
|
+
"index.sectionLabel": "최근 작업",
|
|
1544
|
+
"index.title": "작업 기록",
|
|
1545
|
+
"search.label": "작업 찾기",
|
|
1546
|
+
"search.placeholder": "작업, ID, 레인, 모델",
|
|
1547
|
+
"filter.legend": "표시",
|
|
1548
|
+
"filter.all": "전체",
|
|
1549
|
+
"filter.active": "진행 중",
|
|
1550
|
+
"filter.done": "완료",
|
|
1551
|
+
"filter.issues": "문제",
|
|
1552
|
+
"list.ariaLabel": "최근 작업",
|
|
1553
|
+
"list.waiting": "작업을 기다리는 중입니다.",
|
|
1554
|
+
"list.empty": "아직 작업이 없습니다.",
|
|
1555
|
+
"list.unauthorized": "새 로컬 링크가 필요합니다.",
|
|
1556
|
+
"list.noMatch": "이 필터와 일치하는 작업이 없습니다.",
|
|
1557
|
+
"count.jobs": { other: "작업 {n}개" },
|
|
1558
|
+
"count.filtered": "작업 {shown} / {total}개",
|
|
1559
|
+
"inspector.ariaLabel": "선택한 작업 상세",
|
|
1560
|
+
"state.local.code": "로컬 · 읽기 전용",
|
|
1561
|
+
"state.connecting.title": "작업 보드에 연결하는 중",
|
|
1562
|
+
"state.connecting.message": "가장 최근 작업이 여기에 표시됩니다.",
|
|
1563
|
+
"state.unauthorized.title": "로컬 접근 권한이 필요합니다",
|
|
1564
|
+
"state.unauthorized.message": "`omnilane ui url` 을 실행해 새 로컬 링크를 받으세요.",
|
|
1565
|
+
"state.empty.code": "대기열 · 비어 있음",
|
|
1566
|
+
"state.empty.title": "아직 작업이 없습니다",
|
|
1567
|
+
"state.empty.message": "Omnilane 작업을 실행하세요. 이 보드는 자동으로 갱신됩니다.",
|
|
1568
|
+
"state.noMatch.code": "필터 · 일치 없음",
|
|
1569
|
+
"state.noMatch.title": "일치하는 작업이 없습니다",
|
|
1570
|
+
"state.noMatch.message": "검색어를 지우거나 다른 상태를 선택하세요.",
|
|
1571
|
+
"state.retry.code": "링크 · 재시도 중",
|
|
1572
|
+
"state.retry.title": "로컬 보드를 사용할 수 없습니다",
|
|
1573
|
+
"state.retry.message": "로컬 작업 보드에 다시 연결하는 중입니다.",
|
|
1574
|
+
"mobile.back": "작업 목록으로",
|
|
1575
|
+
"detail.sectionLabel": "선택한 작업",
|
|
1576
|
+
"detail.timeStarted": "시작 {value}",
|
|
1577
|
+
"detail.timeUnavailable": "시각 불명",
|
|
1578
|
+
"detail.timeUnknown": "시작 시각 불명",
|
|
1579
|
+
"compare.pin": "비교용으로 고정",
|
|
1580
|
+
"compare.unpin": "고정 해제",
|
|
1581
|
+
"compare.replace": "기준 교체",
|
|
1582
|
+
"compare.none": "고정된 기준 없음",
|
|
1583
|
+
"compare.pinned": "기준 {id} 고정됨",
|
|
1584
|
+
"compare.sectionLabel": "기준 스냅샷",
|
|
1585
|
+
"compare.heading": "경로와 결과 비교",
|
|
1586
|
+
"compare.clear": "기준 지우기",
|
|
1587
|
+
"compare.roleReference": "고정된 기준",
|
|
1588
|
+
"compare.roleCurrent": "현재 선택",
|
|
1589
|
+
"compare.outputLabel": "공개 결과",
|
|
1590
|
+
"field.lane": "레인",
|
|
1591
|
+
"field.vendor": "벤더",
|
|
1592
|
+
"field.model": "모델",
|
|
1593
|
+
"field.state": "상태",
|
|
1594
|
+
"field.effort": "노력 수준",
|
|
1595
|
+
"field.mode": "모드",
|
|
1596
|
+
"field.watchdog": "워치독",
|
|
1597
|
+
"field.candidate": "후보",
|
|
1598
|
+
"field.started": "시작 시각",
|
|
1599
|
+
"field.workdir": "작업 디렉터리",
|
|
1600
|
+
"task.sectionLabel": "작업",
|
|
1601
|
+
"task.heading": "모델에 요청한 내용",
|
|
1602
|
+
"output.sectionLabel": "출력",
|
|
1603
|
+
"output.heading": "공개 결과",
|
|
1604
|
+
"routing.sectionLabel": "라우팅",
|
|
1605
|
+
"routing.heading": "모델 경로",
|
|
1606
|
+
"meta.summary": "기술 메타데이터",
|
|
1607
|
+
"meta.ariaLabel": "작업 메타데이터",
|
|
1608
|
+
"value.unknown": "알 수 없음",
|
|
1609
|
+
"value.notRecorded": "기록 없음",
|
|
1610
|
+
"value.unknownLane": "레인 불명",
|
|
1611
|
+
"value.unknownVendor": "벤더 불명",
|
|
1612
|
+
"value.unknownModel": "모델 불명",
|
|
1613
|
+
"value.seconds": "{n}초",
|
|
1614
|
+
"loading.task": "작업을 불러오는 중…",
|
|
1615
|
+
"loading.output": "출력을 불러오는 중…",
|
|
1616
|
+
"loading.running": "워커가 실행 중입니다. 출력을 기다리는 중…",
|
|
1617
|
+
"result.starting": "디스패치를 시작하는 중입니다. 아직 기록된 공개 결과가 없습니다.",
|
|
1618
|
+
"result.running": "워커가 실행 중입니다. 공개 결과가 여기에 표시됩니다.",
|
|
1619
|
+
"result.failedCode": "디스패치가 종료 코드 {code}로 실패했습니다. 공개 결과가 기록되지 않았습니다.",
|
|
1620
|
+
"result.failedUnknown": "디스패치가 알 수 없는 코드로 실패했습니다. 공개 결과가 기록되지 않았습니다.",
|
|
1621
|
+
"result.dead": "워커가 사라졌고 종료 코드가 기록되지 않았습니다.",
|
|
1622
|
+
"result.invalid": "이 디스패치에는 잘못된 메타데이터나 제어 파일이 있습니다. 안전한 결과가 없습니다.",
|
|
1623
|
+
"result.none": "디스패치가 공개 결과 없이 완료되었습니다.",
|
|
1624
|
+
"result.unsafe": "공개 결과를 안전하게 읽을 수 없습니다.",
|
|
1625
|
+
"task.unsafe": "작업 내용을 안전하게 읽을 수 없습니다.",
|
|
1626
|
+
"task.none": "기록된 작업이 없습니다.",
|
|
1627
|
+
"marker.truncated": "큰 내용을 512 KiB로 줄였습니다",
|
|
1628
|
+
"marker.taskUnsafe": "작업 파일을 안전하게 읽을 수 없습니다",
|
|
1629
|
+
"marker.resultUnsafe": "결과 파일을 안전하게 읽을 수 없습니다",
|
|
1630
|
+
"marker.failedNoCode": "종료 코드를 읽지 못한 채 실패했습니다",
|
|
1631
|
+
"marker.failedCode": "종료 코드 {code}로 실패했습니다",
|
|
1632
|
+
"marker.dead": "워커 사라짐 · 종료 코드 미기록",
|
|
1633
|
+
"marker.invalid": "잘못된 디스패치 · 안전한 항목만",
|
|
1634
|
+
"marker.detailUnavailable": "상세 정보를 일시적으로 볼 수 없습니다",
|
|
1635
|
+
"error.taskPreserved": "상세 정보를 다시 불러오는 동안 마지막 작업 스냅샷을 유지합니다.",
|
|
1636
|
+
"error.reconnecting": "로컬 작업 보드에 다시 연결하는 중입니다.",
|
|
1637
|
+
"jobState.starting": "시작 중",
|
|
1638
|
+
"jobState.running": "실행 중",
|
|
1639
|
+
"jobState.succeeded": "성공",
|
|
1640
|
+
"jobState.failed": "실패",
|
|
1641
|
+
"jobState.dead": "사라짐",
|
|
1642
|
+
"jobState.invalid": "잘못됨",
|
|
1643
|
+
"noscript": "이 로컬 보드를 읽으려면 JavaScript가 필요합니다.",
|
|
1644
|
+
},
|
|
1645
|
+
"zh-TW": {
|
|
1646
|
+
"app.title": "Omnilane 即時看板",
|
|
1647
|
+
"header.liveJobs": "進行中的工作",
|
|
1648
|
+
"header.readOnly": "唯讀",
|
|
1649
|
+
"header.language": "語言",
|
|
1650
|
+
"connection.connecting": "連線中",
|
|
1651
|
+
"connection.live": "本機訊號即時連線",
|
|
1652
|
+
"connection.reconnecting": "重新連線中",
|
|
1653
|
+
"connection.unauthorized": "未授權",
|
|
1654
|
+
"index.sectionLabel": "最近的工作",
|
|
1655
|
+
"index.title": "任務紀錄",
|
|
1656
|
+
"search.label": "尋找任務",
|
|
1657
|
+
"search.placeholder": "任務、ID、車道、模型",
|
|
1658
|
+
"filter.legend": "顯示",
|
|
1659
|
+
"filter.all": "全部",
|
|
1660
|
+
"filter.active": "進行中",
|
|
1661
|
+
"filter.done": "完成",
|
|
1662
|
+
"filter.issues": "有問題",
|
|
1663
|
+
"list.ariaLabel": "最近的任務",
|
|
1664
|
+
"list.waiting": "等待任務中。",
|
|
1665
|
+
"list.empty": "尚無任務。",
|
|
1666
|
+
"list.unauthorized": "需要新的本機連結。",
|
|
1667
|
+
"list.noMatch": "沒有符合此篩選的任務。",
|
|
1668
|
+
"count.jobs": { other: "{n} 筆工作" },
|
|
1669
|
+
"count.filtered": "{shown} / {total} 筆工作",
|
|
1670
|
+
"inspector.ariaLabel": "所選任務的詳細內容",
|
|
1671
|
+
"state.local.code": "本機 · 唯讀",
|
|
1672
|
+
"state.connecting.title": "正在連線到工作看板",
|
|
1673
|
+
"state.connecting.message": "最新的任務會顯示在這裡。",
|
|
1674
|
+
"state.unauthorized.title": "需要本機存取權",
|
|
1675
|
+
"state.unauthorized.message": "執行 `omnilane ui url` 取得新的本機連結。",
|
|
1676
|
+
"state.empty.code": "佇列 · 空的",
|
|
1677
|
+
"state.empty.title": "尚無任務",
|
|
1678
|
+
"state.empty.message": "執行一個 Omnilane 任務,此看板會自動更新。",
|
|
1679
|
+
"state.noMatch.code": "篩選 · 無相符",
|
|
1680
|
+
"state.noMatch.title": "沒有相符的任務",
|
|
1681
|
+
"state.noMatch.message": "清除搜尋條件或改選其他狀態。",
|
|
1682
|
+
"state.retry.code": "連線 · 重試中",
|
|
1683
|
+
"state.retry.title": "本機看板無法使用",
|
|
1684
|
+
"state.retry.message": "正在重新連線到本機工作看板。",
|
|
1685
|
+
"mobile.back": "返回任務清單",
|
|
1686
|
+
"detail.sectionLabel": "所選任務",
|
|
1687
|
+
"detail.timeStarted": "開始於 {value}",
|
|
1688
|
+
"detail.timeUnavailable": "時間不明",
|
|
1689
|
+
"detail.timeUnknown": "開始時間不明",
|
|
1690
|
+
"compare.pin": "釘選以比較",
|
|
1691
|
+
"compare.unpin": "取消釘選",
|
|
1692
|
+
"compare.replace": "更換參考",
|
|
1693
|
+
"compare.none": "未釘選參考",
|
|
1694
|
+
"compare.pinned": "已釘選參考 {id}",
|
|
1695
|
+
"compare.sectionLabel": "參考快照",
|
|
1696
|
+
"compare.heading": "路由與結果比較",
|
|
1697
|
+
"compare.clear": "清除參考",
|
|
1698
|
+
"compare.roleReference": "已釘選的參考",
|
|
1699
|
+
"compare.roleCurrent": "目前選取",
|
|
1700
|
+
"compare.outputLabel": "公開結果",
|
|
1701
|
+
"field.lane": "車道",
|
|
1702
|
+
"field.vendor": "廠商",
|
|
1703
|
+
"field.model": "模型",
|
|
1704
|
+
"field.state": "狀態",
|
|
1705
|
+
"field.effort": "運算力度",
|
|
1706
|
+
"field.mode": "模式",
|
|
1707
|
+
"field.watchdog": "看門狗",
|
|
1708
|
+
"field.candidate": "候選",
|
|
1709
|
+
"field.started": "開始時間",
|
|
1710
|
+
"field.workdir": "工作目錄",
|
|
1711
|
+
"task.sectionLabel": "任務",
|
|
1712
|
+
"task.heading": "詢問模型的內容",
|
|
1713
|
+
"output.sectionLabel": "輸出",
|
|
1714
|
+
"output.heading": "公開結果",
|
|
1715
|
+
"routing.sectionLabel": "路由",
|
|
1716
|
+
"routing.heading": "模型路徑",
|
|
1717
|
+
"meta.summary": "技術中繼資料",
|
|
1718
|
+
"meta.ariaLabel": "任務中繼資料",
|
|
1719
|
+
"value.unknown": "不明",
|
|
1720
|
+
"value.notRecorded": "未記錄",
|
|
1721
|
+
"value.unknownLane": "車道不明",
|
|
1722
|
+
"value.unknownVendor": "廠商不明",
|
|
1723
|
+
"value.unknownModel": "模型不明",
|
|
1724
|
+
"value.seconds": "{n} 秒",
|
|
1725
|
+
"loading.task": "載入任務中…",
|
|
1726
|
+
"loading.output": "載入輸出中…",
|
|
1727
|
+
"loading.running": "工作端執行中,等待輸出…",
|
|
1728
|
+
"result.starting": "派工正在啟動,尚未記錄公開結果。",
|
|
1729
|
+
"result.running": "工作端執行中,公開結果會顯示在這裡。",
|
|
1730
|
+
"result.failedCode": "派工失敗,結束代碼 {code}。沒有記錄公開結果。",
|
|
1731
|
+
"result.failedUnknown": "派工失敗,結束代碼不明。沒有記錄公開結果。",
|
|
1732
|
+
"result.dead": "工作端已消失,且沒有記錄結束代碼。",
|
|
1733
|
+
"result.invalid": "這筆派工含有無效的中繼資料或控制檔,沒有可安全顯示的結果。",
|
|
1734
|
+
"result.none": "派工完成,但沒有公開結果。",
|
|
1735
|
+
"result.unsafe": "無法安全讀取公開結果。",
|
|
1736
|
+
"task.unsafe": "無法安全讀取任務內容。",
|
|
1737
|
+
"task.none": "沒有記錄任務內容。",
|
|
1738
|
+
"marker.truncated": "過長內容已截短為 512 KiB",
|
|
1739
|
+
"marker.taskUnsafe": "無法安全讀取任務檔",
|
|
1740
|
+
"marker.resultUnsafe": "無法安全讀取結果檔",
|
|
1741
|
+
"marker.failedNoCode": "派工失敗,且讀不到結束代碼",
|
|
1742
|
+
"marker.failedCode": "派工失敗,結束代碼 {code}",
|
|
1743
|
+
"marker.dead": "工作端消失 · 未記錄結束代碼",
|
|
1744
|
+
"marker.invalid": "無效派工 · 僅顯示安全欄位",
|
|
1745
|
+
"marker.detailUnavailable": "暫時無法取得詳細內容",
|
|
1746
|
+
"error.taskPreserved": "重新載入詳細內容時,保留上一份任務快照。",
|
|
1747
|
+
"error.reconnecting": "正在重新連線到本機工作看板。",
|
|
1748
|
+
"jobState.starting": "啟動中",
|
|
1749
|
+
"jobState.running": "執行中",
|
|
1750
|
+
"jobState.succeeded": "成功",
|
|
1751
|
+
"jobState.failed": "失敗",
|
|
1752
|
+
"jobState.dead": "消失",
|
|
1753
|
+
"jobState.invalid": "無效",
|
|
1754
|
+
"noscript": "需要 JavaScript 才能閱讀這個本機看板。",
|
|
1755
|
+
},
|
|
1756
|
+
"zh-CN": {
|
|
1757
|
+
"app.title": "Omnilane 实时看板",
|
|
1758
|
+
"header.liveJobs": "进行中的作业",
|
|
1759
|
+
"header.readOnly": "只读",
|
|
1760
|
+
"header.language": "语言",
|
|
1761
|
+
"connection.connecting": "连接中",
|
|
1762
|
+
"connection.live": "本地信号实时连接",
|
|
1763
|
+
"connection.reconnecting": "重新连接中",
|
|
1764
|
+
"connection.unauthorized": "未授权",
|
|
1765
|
+
"index.sectionLabel": "最近的工作",
|
|
1766
|
+
"index.title": "任务记录",
|
|
1767
|
+
"search.label": "查找任务",
|
|
1768
|
+
"search.placeholder": "任务、ID、车道、模型",
|
|
1769
|
+
"filter.legend": "显示",
|
|
1770
|
+
"filter.all": "全部",
|
|
1771
|
+
"filter.active": "进行中",
|
|
1772
|
+
"filter.done": "完成",
|
|
1773
|
+
"filter.issues": "有问题",
|
|
1774
|
+
"list.ariaLabel": "最近的任务",
|
|
1775
|
+
"list.waiting": "等待任务中。",
|
|
1776
|
+
"list.empty": "暂无任务。",
|
|
1777
|
+
"list.unauthorized": "需要新的本地链接。",
|
|
1778
|
+
"list.noMatch": "没有符合此筛选的任务。",
|
|
1779
|
+
"count.jobs": { other: "{n} 个作业" },
|
|
1780
|
+
"count.filtered": "{shown} / {total} 个作业",
|
|
1781
|
+
"inspector.ariaLabel": "所选任务的详细内容",
|
|
1782
|
+
"state.local.code": "本地 · 只读",
|
|
1783
|
+
"state.connecting.title": "正在连接到作业看板",
|
|
1784
|
+
"state.connecting.message": "最新的任务会显示在这里。",
|
|
1785
|
+
"state.unauthorized.title": "需要本地访问权限",
|
|
1786
|
+
"state.unauthorized.message": "运行 `omnilane ui url` 获取新的本地链接。",
|
|
1787
|
+
"state.empty.code": "队列 · 空",
|
|
1788
|
+
"state.empty.title": "暂无任务",
|
|
1789
|
+
"state.empty.message": "运行一个 Omnilane 任务,此看板会自动更新。",
|
|
1790
|
+
"state.noMatch.code": "筛选 · 无匹配",
|
|
1791
|
+
"state.noMatch.title": "没有匹配的任务",
|
|
1792
|
+
"state.noMatch.message": "清除搜索条件或改选其他状态。",
|
|
1793
|
+
"state.retry.code": "连接 · 重试中",
|
|
1794
|
+
"state.retry.title": "本地看板不可用",
|
|
1795
|
+
"state.retry.message": "正在重新连接到本地作业看板。",
|
|
1796
|
+
"mobile.back": "返回任务列表",
|
|
1797
|
+
"detail.sectionLabel": "所选任务",
|
|
1798
|
+
"detail.timeStarted": "开始于 {value}",
|
|
1799
|
+
"detail.timeUnavailable": "时间不详",
|
|
1800
|
+
"detail.timeUnknown": "开始时间不详",
|
|
1801
|
+
"compare.pin": "固定以比较",
|
|
1802
|
+
"compare.unpin": "取消固定",
|
|
1803
|
+
"compare.replace": "更换参考",
|
|
1804
|
+
"compare.none": "未固定参考",
|
|
1805
|
+
"compare.pinned": "已固定参考 {id}",
|
|
1806
|
+
"compare.sectionLabel": "参考快照",
|
|
1807
|
+
"compare.heading": "路由与结果比较",
|
|
1808
|
+
"compare.clear": "清除参考",
|
|
1809
|
+
"compare.roleReference": "已固定的参考",
|
|
1810
|
+
"compare.roleCurrent": "当前选择",
|
|
1811
|
+
"compare.outputLabel": "公开结果",
|
|
1812
|
+
"field.lane": "车道",
|
|
1813
|
+
"field.vendor": "厂商",
|
|
1814
|
+
"field.model": "模型",
|
|
1815
|
+
"field.state": "状态",
|
|
1816
|
+
"field.effort": "算力档位",
|
|
1817
|
+
"field.mode": "模式",
|
|
1818
|
+
"field.watchdog": "看门狗",
|
|
1819
|
+
"field.candidate": "候选",
|
|
1820
|
+
"field.started": "开始时间",
|
|
1821
|
+
"field.workdir": "工作目录",
|
|
1822
|
+
"task.sectionLabel": "任务",
|
|
1823
|
+
"task.heading": "向模型提出的内容",
|
|
1824
|
+
"output.sectionLabel": "输出",
|
|
1825
|
+
"output.heading": "公开结果",
|
|
1826
|
+
"routing.sectionLabel": "路由",
|
|
1827
|
+
"routing.heading": "模型路径",
|
|
1828
|
+
"meta.summary": "技术元数据",
|
|
1829
|
+
"meta.ariaLabel": "任务元数据",
|
|
1830
|
+
"value.unknown": "未知",
|
|
1831
|
+
"value.notRecorded": "未记录",
|
|
1832
|
+
"value.unknownLane": "车道未知",
|
|
1833
|
+
"value.unknownVendor": "厂商未知",
|
|
1834
|
+
"value.unknownModel": "模型未知",
|
|
1835
|
+
"value.seconds": "{n} 秒",
|
|
1836
|
+
"loading.task": "正在加载任务…",
|
|
1837
|
+
"loading.output": "正在加载输出…",
|
|
1838
|
+
"loading.running": "工作进程运行中,等待输出…",
|
|
1839
|
+
"result.starting": "派发正在启动,尚未记录公开结果。",
|
|
1840
|
+
"result.running": "工作进程运行中,公开结果会显示在这里。",
|
|
1841
|
+
"result.failedCode": "派发失败,退出码 {code}。未记录公开结果。",
|
|
1842
|
+
"result.failedUnknown": "派发失败,退出码未知。未记录公开结果。",
|
|
1843
|
+
"result.dead": "工作进程已消失,且未记录退出码。",
|
|
1844
|
+
"result.invalid": "这条派发含有无效的元数据或控制文件,没有可安全显示的结果。",
|
|
1845
|
+
"result.none": "派发完成,但没有公开结果。",
|
|
1846
|
+
"result.unsafe": "无法安全读取公开结果。",
|
|
1847
|
+
"task.unsafe": "无法安全读取任务内容。",
|
|
1848
|
+
"task.none": "未记录任务内容。",
|
|
1849
|
+
"marker.truncated": "过长内容已截断为 512 KiB",
|
|
1850
|
+
"marker.taskUnsafe": "无法安全读取任务文件",
|
|
1851
|
+
"marker.resultUnsafe": "无法安全读取结果文件",
|
|
1852
|
+
"marker.failedNoCode": "派发失败,且读不到退出码",
|
|
1853
|
+
"marker.failedCode": "派发失败,退出码 {code}",
|
|
1854
|
+
"marker.dead": "工作进程消失 · 未记录退出码",
|
|
1855
|
+
"marker.invalid": "无效派发 · 仅显示安全字段",
|
|
1856
|
+
"marker.detailUnavailable": "暂时无法获取详细内容",
|
|
1857
|
+
"error.taskPreserved": "重新加载详细内容时,保留上一份任务快照。",
|
|
1858
|
+
"error.reconnecting": "正在重新连接到本地作业看板。",
|
|
1859
|
+
"jobState.starting": "启动中",
|
|
1860
|
+
"jobState.running": "运行中",
|
|
1861
|
+
"jobState.succeeded": "成功",
|
|
1862
|
+
"jobState.failed": "失败",
|
|
1863
|
+
"jobState.dead": "消失",
|
|
1864
|
+
"jobState.invalid": "无效",
|
|
1865
|
+
"noscript": "需要 JavaScript 才能查看这个本地看板。",
|
|
1866
|
+
},
|
|
1867
|
+
};
|
|
1868
|
+
|
|
1091
1869
|
function start() {
|
|
1870
|
+
initializeLanguage();
|
|
1092
1871
|
initializeHistory();
|
|
1093
1872
|
bindControls();
|
|
1094
1873
|
if (!state.token) {
|
|
@@ -1097,7 +1876,7 @@
|
|
|
1097
1876
|
}
|
|
1098
1877
|
|
|
1099
1878
|
setControlsDisabled(false);
|
|
1100
|
-
setConnection("waiting", "
|
|
1879
|
+
setConnection("waiting", "connection.connecting");
|
|
1101
1880
|
openEventStream();
|
|
1102
1881
|
loadInitialSnapshot();
|
|
1103
1882
|
}
|