dsh-cait 1.1.3 → 1.1.7
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 +186 -0
- package/README.ja.md +51 -14
- package/README.md +137 -30
- package/README.zh-CN.md +51 -14
- package/client/components.js +2033 -48
- package/client/entry.js +2 -0
- package/client/helpers.js +12 -0
- package/client/locales.js +658 -8
- package/client/styles.js +111 -4
- package/client.js +2814 -58
- package/lib/aligner.js +6 -6
- package/lib/analysis.js +88 -0
- package/lib/api.js +46 -0
- package/lib/brief.js +275 -0
- package/lib/docx.js +155 -26
- package/lib/formats.js +37 -0
- package/lib/glossary.js +20 -4
- package/lib/import-export.js +212 -13
- package/lib/libraries.js +156 -0
- package/lib/library-export.js +7 -1
- package/lib/localization.js +1098 -0
- package/lib/lock.js +30 -0
- package/lib/notes.js +144 -0
- package/lib/persistence.js +54 -7
- package/lib/pptx.js +33 -1
- package/lib/pre-translate.js +62 -45
- package/lib/propagate.js +3 -0
- package/lib/qa.js +94 -4
- package/lib/replace.js +243 -0
- package/lib/review.js +131 -0
- package/lib/segment-edit.js +199 -0
- package/lib/store-utils.js +33 -6
- package/lib/store.js +507 -47
- package/lib/subtitles.js +213 -0
- package/lib/term-extract.js +105 -3
- package/lib/terms.js +4 -2
- package/lib/tm-maintenance.js +157 -0
- package/lib/tm.js +4 -1
- package/lib/tools.js +294 -0
- package/lib/xlsx.js +176 -66
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -212,6 +212,18 @@ window.__ModuleLoader__.load({
|
|
|
212
212
|
return number - 1
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
+
// --- settings defaults ---------------------------------------------------
|
|
216
|
+
// Below ~70% a fuzzy match usually shares little more than function words,
|
|
217
|
+
// so it costs review time and saves none. The settings modal offers a reset
|
|
218
|
+
// button only while the slider sits away from this value.
|
|
219
|
+
const DEFAULT_FUZZY_THRESHOLD = 0.7
|
|
220
|
+
|
|
221
|
+
function fuzzyPercent(value) {
|
|
222
|
+
const number = Number(value)
|
|
223
|
+
if (!Number.isFinite(number)) return Math.round(DEFAULT_FUZZY_THRESHOLD * 100)
|
|
224
|
+
return Math.round(Math.min(1, Math.max(0, number)) * 100)
|
|
225
|
+
}
|
|
226
|
+
|
|
215
227
|
const CSS = `
|
|
216
228
|
.cait-root{--cait-u:1;position:relative;display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);font-family:inherit;font-size:13px;line-height:1.5;color-scheme:light}
|
|
217
229
|
.cait-root[data-cait-theme="dark"]{color-scheme:dark}
|
|
@@ -297,6 +309,90 @@ window.__ModuleLoader__.load({
|
|
|
297
309
|
.cait-dot.confirmed{background:var(--dsw-alias-state-success-primary)}
|
|
298
310
|
.cait-dot.translated{background:var(--dsw-alias-button-primary-fill)}
|
|
299
311
|
.cait-seg-warn{width:5px;height:5px;border-radius:50%;background:var(--dsw-alias-state-warn-primary);flex:0 0 auto}
|
|
312
|
+
.cait-seg-warn.high{background:var(--dsw-alias-state-error-primary)}
|
|
313
|
+
.cait-seg-warn.medium{background:var(--dsw-alias-state-warn-primary)}
|
|
314
|
+
.cait-seg-warn.low{background:var(--dsw-alias-label-secondary)}
|
|
315
|
+
.cait-seg-flags{display:flex;align-items:center;gap:5px;flex:0 0 auto}
|
|
316
|
+
.cait-seg-flags .cait-linkbtn{display:inline-flex;align-items:center;justify-content:center;gap:3px;height:21px;padding:0 5px;border-radius:6px;font-size:10px;line-height:1;white-space:nowrap;text-decoration:none}
|
|
317
|
+
.cait-seg-flags .cait-linkbtn:hover{background:var(--dsw-alias-bg-layer-1);text-decoration:none}
|
|
318
|
+
.cait-seg-flags .cait-linkbtn.active{background:color-mix(in srgb,var(--dsw-alias-brand-primary) 12%,transparent);color:var(--dsw-alias-brand-primary)}
|
|
319
|
+
.cait-seg-flags .cait-linkbtn svg{flex:0 0 auto}
|
|
320
|
+
.cait-seg-flags .cait-badge{height:21px;padding:0 9px;line-height:1;font-size:10px}
|
|
321
|
+
.cait-seg-flags .cait-riskchip{height:18px}
|
|
322
|
+
.cait-seg-flags .cait-notecount{height:15px;min-width:15px;line-height:1}
|
|
323
|
+
.cait-riskchip{display:inline-flex;align-items:center;height:17px;padding:0 6px;border:1px solid transparent;border-radius:999px;font-size:9.5px;font-weight:650;letter-spacing:.01em}
|
|
324
|
+
.cait-riskchip.high{color:var(--dsw-alias-state-error-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 35%,transparent);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 9%,transparent)}
|
|
325
|
+
.cait-riskchip.medium{color:var(--dsw-alias-state-warn-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 38%,transparent);background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 10%,transparent)}
|
|
326
|
+
.cait-riskchip.low{color:var(--dsw-alias-label-secondary);border-color:var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1)}
|
|
327
|
+
.cait-brieflink{height:20px;padding:0 4px;gap:3px;font-size:10px}
|
|
328
|
+
.cait-notelink{height:20px;padding:0 4px;gap:3px;font-size:10px}
|
|
329
|
+
.cait-notecount{display:inline-grid;place-items:center;min-width:14px;height:14px;padding:0 3px;border-radius:7px;background:color-mix(in srgb,var(--dsw-alias-brand-primary) 16%,transparent);color:var(--dsw-alias-brand-primary);font-size:9px}
|
|
330
|
+
.cait-notelist{display:flex;flex-direction:column;gap:7px;margin:10px 0 12px;font-size:11.5px;line-height:1.55;color:var(--dsw-alias-label-secondary)}
|
|
331
|
+
.cait-noteitem{padding:8px 9px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1)}
|
|
332
|
+
.cait-notehead{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:4px}
|
|
333
|
+
.cait-noteauthor{font-size:10px;font-weight:650;color:var(--dsw-alias-label-primary)}
|
|
334
|
+
.cait-notetime{font-size:9.5px;color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums}
|
|
335
|
+
.cait-notetext{font-size:11.5px;line-height:1.6;color:var(--dsw-alias-label-primary);white-space:pre-wrap;overflow-wrap:anywhere}
|
|
336
|
+
.cait-noteactions{display:flex;align-items:center;justify-content:flex-end;gap:5px;margin-top:5px}
|
|
337
|
+
.cait-noteactions .cait-minibtn{margin-left:0}
|
|
338
|
+
.cait-noteadd{display:flex;flex-direction:column;gap:7px;margin-top:8px}
|
|
339
|
+
.cait-noteadd .cait-textarea,.cait-noteitem .cait-textarea{min-height:64px;padding:7px 8px;border-radius:7px;font-size:11.5px;line-height:1.55}
|
|
340
|
+
.cait-minibtn.cait-danger:hover{color:var(--dsw-alias-state-error-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 32%,var(--dsw-alias-border-l1))}
|
|
341
|
+
.cait-briefdot{width:4px;height:4px;border-radius:50%;background:var(--dsw-alias-brand-primary)}
|
|
342
|
+
.cait-briefform{display:flex;flex-direction:column;gap:11px;margin:12px 0 4px}
|
|
343
|
+
.cait-resource-section{display:flex;flex-direction:column;gap:6px}
|
|
344
|
+
.cait-resource-head{display:flex;align-items:center;justify-content:space-between;gap:10px}
|
|
345
|
+
.cait-resource-list{max-height:132px;overflow:auto;border:1px solid var(--dsw-alias-border-secondary);border-radius:6px;padding:4px 6px}
|
|
346
|
+
.cait-resource-row{display:flex;align-items:center;gap:7px;padding:3px 0;font-size:10.5px;cursor:pointer}
|
|
347
|
+
.cait-resource-row input{flex:0 0 auto}
|
|
348
|
+
.cait-resource-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary)}
|
|
349
|
+
.cait-analysis{display:flex;flex-direction:column;gap:10px;margin:8px 0}
|
|
350
|
+
.cait-analysis-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px}
|
|
351
|
+
.cait-analysis-stat{display:flex;flex-direction:column;gap:2px;padding:7px 9px;border:1px solid var(--dsw-alias-border-secondary);border-radius:6px;font-size:10.5px;color:var(--dsw-alias-label-secondary)}
|
|
352
|
+
.cait-analysis-stat strong{font-size:15px;color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums}
|
|
353
|
+
.cait-analysis-total{display:flex;gap:14px;font-size:11px;color:var(--dsw-alias-label-secondary)}
|
|
354
|
+
.cait-replaceform{display:flex;flex-direction:column;gap:10px;margin:12px 0}
|
|
355
|
+
.cait-replaceoptions{display:flex;align-items:flex-end;flex-wrap:wrap;gap:14px}
|
|
356
|
+
.cait-replaceresults{margin-top:12px}
|
|
357
|
+
.cait-replacecounts{font-size:11px;font-weight:600;color:var(--dsw-alias-label-primary)}
|
|
358
|
+
.cait-replacelist{max-height:220px;overflow:auto;margin-top:7px;border:1px solid var(--dsw-alias-border-l1);border-radius:7px}
|
|
359
|
+
.cait-replaceitem{padding:7px 9px;border-bottom:1px solid var(--dsw-alias-border-l1);font-size:10.5px}
|
|
360
|
+
.cait-replaceitem:last-child{border-bottom:0}
|
|
361
|
+
.cait-replacehead{display:flex;align-items:center;gap:8px;margin-bottom:3px;color:var(--dsw-alias-label-secondary)}
|
|
362
|
+
.cait-replacefield{font-weight:600;color:var(--dsw-alias-label-primary)}
|
|
363
|
+
.cait-replacecount{margin-left:auto;font-variant-numeric:tabular-nums}
|
|
364
|
+
.cait-replacebefore,.cait-replaceafter{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--dsw-font-mono,monospace);line-height:1.5}
|
|
365
|
+
.cait-replacebefore{color:var(--dsw-alias-label-secondary);text-decoration:line-through}
|
|
366
|
+
.cait-replaceafter{color:var(--dsw-alias-label-primary)}
|
|
367
|
+
.cait-palette-input{margin:10px 0 7px}
|
|
368
|
+
.cait-palette-list{max-height:280px;overflow:auto;border:1px solid var(--dsw-alias-border-l1);border-radius:7px}
|
|
369
|
+
.cait-palette-item{display:flex;align-items:center;justify-content:space-between;gap:10px;width:100%;padding:8px 10px;border:0;border-bottom:1px solid var(--dsw-alias-border-l1);background:transparent;color:var(--dsw-alias-label-primary);text-align:left;font-size:11px}
|
|
370
|
+
.cait-palette-item:last-child{border-bottom:0}
|
|
371
|
+
.cait-palette-item.active{background:var(--dsw-alias-bg-layer-1)}
|
|
372
|
+
.cait-palette-label{font-weight:600}
|
|
373
|
+
.cait-palette-hint{color:var(--dsw-alias-label-secondary);font-size:10px}
|
|
374
|
+
.cait-palette-foot{margin:8px 0 0;font-size:10px;color:var(--dsw-alias-label-secondary);text-align:right}
|
|
375
|
+
.cait-riskcounts{display:flex;flex-wrap:wrap;gap:6px;margin:2px 0 9px}
|
|
376
|
+
.cait-riskcount{display:inline-flex;align-items:center;height:20px;padding:0 7px;border:1px solid var(--dsw-alias-border-l1);border-radius:999px;font-size:10px;font-weight:600;color:var(--dsw-alias-label-secondary)}
|
|
377
|
+
.cait-riskcount.high{color:var(--dsw-alias-state-error-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 35%,transparent)}
|
|
378
|
+
.cait-riskcount.medium{color:var(--dsw-alias-state-warn-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 38%,transparent)}
|
|
379
|
+
.cait-riskrow{display:flex;align-items:flex-start;gap:8px;width:100%;margin-bottom:6px;padding:8px 8px;border:1px solid var(--dsw-alias-border-l1);border-left:3px solid var(--dsw-alias-label-secondary);border-radius:7px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);text-align:left}
|
|
380
|
+
.cait-riskrow:hover{background:var(--dsw-alias-bg-layer-2)}
|
|
381
|
+
.cait-riskrow.high{border-left-color:var(--dsw-alias-state-error-primary)}
|
|
382
|
+
.cait-riskrow.medium{border-left-color:var(--dsw-alias-state-warn-primary)}
|
|
383
|
+
.cait-riskrow.low{border-left-color:var(--dsw-alias-label-secondary)}
|
|
384
|
+
.cait-riskrow-index{min-width:18px;font-size:10px;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-secondary);padding-top:2px}
|
|
385
|
+
.cait-riskrow-body{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}
|
|
386
|
+
.cait-riskrow-source{font-size:11px;line-height:1.4;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
|
|
387
|
+
.cait-riskrow-codes{font-size:9.5px;color:var(--dsw-alias-label-secondary)}
|
|
388
|
+
.cait-riskactions{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}
|
|
389
|
+
.cait-riskaction{padding:2px 6px;border:1px solid var(--dsw-alias-border-l1);border-radius:4px;background:transparent;color:var(--dsw-alias-label-secondary);font-size:9.5px;cursor:pointer}
|
|
390
|
+
.cait-riskaction:hover{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}
|
|
391
|
+
.cait-concordance-row{display:flex;flex-direction:column;gap:3px;width:100%;margin-bottom:6px;padding:8px 9px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);text-align:left}
|
|
392
|
+
.cait-concordance-row:hover{background:var(--dsw-alias-bg-layer-2)}
|
|
393
|
+
.cait-concordance-source{font-size:11px;line-height:1.45;color:var(--dsw-alias-label-secondary)}
|
|
394
|
+
.cait-concordance-target{font-size:11.5px;line-height:1.45}
|
|
395
|
+
.cait-concordance-meta{font-size:9.5px;color:var(--dsw-alias-label-secondary)}
|
|
300
396
|
.cait-qa-banner{display:flex;align-items:flex-start;gap:6px;margin-bottom:10px;padding:7px 9px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-warn-primary) 45%,transparent);border-radius:8px;background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 10%,transparent);color:var(--dsw-alias-label-primary);font-size:11px;line-height:1.5}
|
|
301
397
|
.cait-seg-text{display:-webkit-box;overflow:hidden;-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:11.5px;line-height:1.42;color:var(--dsw-alias-label-primary)}
|
|
302
398
|
.cait-segitem.active .cait-seg-text{font-weight:550}
|
|
@@ -351,6 +447,9 @@ window.__ModuleLoader__.load({
|
|
|
351
447
|
.cait-textarea::placeholder{color:var(--dsw-alias-label-secondary)}
|
|
352
448
|
.cait-meta{font-size:10.5px;color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums}
|
|
353
449
|
.cait-actionbar{display:flex;align-items:center;gap:7px;flex-wrap:wrap;margin-top:13px}
|
|
450
|
+
.cait-subbar{display:inline-flex;align-items:center;gap:6px;flex-wrap:wrap;padding:3px 6px;border:1px solid var(--dsw-alias-border-l1);border-radius:7px;background:var(--dsw-alias-bg-layer-2)}
|
|
451
|
+
.cait-subtime{font-size:10.5px;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-secondary);white-space:nowrap}
|
|
452
|
+
.cait-suboffset{width:54px;height:24px;padding:0 5px;border:1px solid var(--dsw-alias-border-l1);border-radius:5px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font-size:10.5px}
|
|
354
453
|
.cait-hint{margin-left:auto;font-size:10px;color:var(--dsw-alias-label-secondary)}
|
|
355
454
|
.cait-savebtn{width:31px;height:31px;border-color:var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1)}
|
|
356
455
|
.cait-editor-foot{display:flex;align-items:center;gap:8px;margin-top:11px;font-size:10.5px;color:var(--dsw-alias-label-secondary)}
|
|
@@ -379,8 +478,8 @@ window.__ModuleLoader__.load({
|
|
|
379
478
|
.cait-libcreate{display:flex;align-items:center;gap:5px;margin-top:8px}
|
|
380
479
|
.cait-libcreate .cait-input{height:27px;min-width:0;flex:1}
|
|
381
480
|
.cait-libcreate .cait-btn{height:27px;padding:0 8px;font-size:10px;flex:0 0 auto}
|
|
382
|
-
.cait-tabs{display:flex;gap:
|
|
383
|
-
.cait-tab{display:flex;align-items:center;justify-content:center;gap:5px;flex:
|
|
481
|
+
.cait-tabs{display:flex;flex-wrap:wrap;gap:4px;padding:7px 9px;border-bottom:1px solid var(--dsw-alias-border-l1);flex:0 0 auto}
|
|
482
|
+
.cait-tab{display:inline-flex;align-items:center;justify-content:center;gap:5px;flex:0 0 auto;white-space:nowrap;height:29px;padding:0 8px;border:1px solid transparent;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary);font-size:10.5px;font-weight:500}
|
|
384
483
|
.cait-tab:hover{background:color-mix(in srgb,var(--dsw-alias-label-primary) 5%,transparent)}
|
|
385
484
|
.cait-tab.active{background:color-mix(in srgb,var(--dsw-alias-brand-primary) 11%,var(--dsw-alias-bg-layer-1));border-color:color-mix(in srgb,var(--dsw-alias-brand-primary) 18%,var(--dsw-alias-border-l1));color:var(--dsw-alias-brand-primary);font-weight:650}
|
|
386
485
|
.cait-tab .cait-count{color:inherit;opacity:.72}
|
|
@@ -414,6 +513,16 @@ window.__ModuleLoader__.load({
|
|
|
414
513
|
.cait-panel-foot{display:flex;align-items:center;gap:5px;margin-top:10px;padding-top:9px;border-top:1px solid var(--dsw-alias-border-l1);flex-wrap:wrap}
|
|
415
514
|
.cait-panel-foot .cait-btn{height:26px;padding:0 7px;font-size:10px}
|
|
416
515
|
.cait-subhead{display:flex;align-items:center;justify-content:space-between;margin:2px 1px 7px;font-size:10px;font-weight:650;letter-spacing:.05em;text-transform:uppercase;color:var(--dsw-alias-label-secondary)}
|
|
516
|
+
.cait-subhead-actions{display:flex;align-items:center;gap:6px}
|
|
517
|
+
.cait-review{margin-top:10px;padding-top:8px;border-top:1px solid var(--dsw-alias-border-l1)}
|
|
518
|
+
.cait-reviewrow{display:flex;align-items:flex-start;gap:7px;width:100%;margin-bottom:6px;padding:7px 8px;border:1px solid var(--dsw-alias-border-l1);border-left:3px solid var(--dsw-alias-state-warn-primary);border-radius:7px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);text-align:left;cursor:pointer}
|
|
519
|
+
.cait-reviewrow:hover{background:var(--dsw-alias-bg-layer-2)}
|
|
520
|
+
.cait-reviewrow-index{min-width:18px;font-size:10px;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-secondary);padding-top:2px}
|
|
521
|
+
.cait-reviewrow-body{display:flex;flex-direction:column;align-items:flex-start;gap:4px;min-width:0;flex:1}
|
|
522
|
+
.cait-reviewrow-msg{font-size:11px;line-height:1.45;overflow-wrap:anywhere}
|
|
523
|
+
.cait-reviewrow-msg.high{color:var(--dsw-alias-state-error-primary)}
|
|
524
|
+
.cait-reviewrow-msg.medium{color:var(--dsw-alias-state-warn-primary)}
|
|
525
|
+
.cait-reviewrow-msg.low{color:var(--dsw-alias-label-secondary)}
|
|
417
526
|
.cait-term{padding:8px 9px;margin-bottom:6px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1)}
|
|
418
527
|
.cait-term-main{display:flex;align-items:baseline;gap:5px;min-width:0;font-size:11.5px}
|
|
419
528
|
.cait-term-main strong{font-weight:650;overflow-wrap:anywhere}
|
|
@@ -451,6 +560,10 @@ window.__ModuleLoader__.load({
|
|
|
451
560
|
.cait-segmented button{flex:1;height:27px;border:0;border-radius:5px;background:transparent;color:var(--dsw-alias-label-secondary);font-size:11px;font-weight:550}
|
|
452
561
|
.cait-segmented button.active{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);box-shadow:0 1px 3px color-mix(in srgb,var(--dsw-alias-label-primary) 12%,transparent)}
|
|
453
562
|
.cait-field{display:flex;flex-direction:column;gap:4px;margin-bottom:9px}
|
|
563
|
+
.cait-fieldhead{display:flex;align-items:baseline;justify-content:space-between;gap:8px}
|
|
564
|
+
.cait-fieldhead>span{font-size:10px;font-weight:600;color:var(--dsw-alias-label-secondary)}
|
|
565
|
+
.cait-fieldreset{font-size:10px;padding:1px 0}
|
|
566
|
+
.cait-fieldhint{margin:0;font-size:9.5px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
|
|
454
567
|
.cait-field>span{font-size:10px;font-weight:600;color:var(--dsw-alias-label-secondary)}
|
|
455
568
|
.cait-field .cait-input,.cait-field select{height:30px}
|
|
456
569
|
.cait-grid2{display:grid;grid-template-columns:1fr 1fr;gap:8px}
|
|
@@ -465,6 +578,7 @@ window.__ModuleLoader__.load({
|
|
|
465
578
|
.cait-formfoot .cait-btn{margin-left:auto}
|
|
466
579
|
.cait-linkbtn{border:0;background:transparent;color:var(--dsw-alias-brand-primary);font-size:11px;font-weight:600;padding:3px 0}
|
|
467
580
|
.cait-linkbtn:hover{text-decoration:underline}
|
|
581
|
+
.cait-linkbtn.active{color:var(--dsw-alias-label-primary);text-decoration:none}
|
|
468
582
|
.cait-demo-row{display:flex;align-items:center;gap:8px;margin-top:11px;font-size:10.5px;color:var(--dsw-alias-label-secondary)}
|
|
469
583
|
.cait-overlay{position:absolute;inset:0;z-index:20;display:grid;place-items:center;padding:18px;background:color-mix(in srgb,var(--dsw-alias-label-primary) 22%,transparent);backdrop-filter:blur(2px)}
|
|
470
584
|
.cait-modal{width:min(530px,100%);max-height:92%;overflow:auto;border:1px solid var(--dsw-alias-border-l1);border-radius:13px;background:var(--dsw-alias-bg-layer-2);box-shadow:0 18px 55px color-mix(in srgb,var(--dsw-alias-label-primary) 22%,transparent)}
|
|
@@ -537,7 +651,10 @@ window.__ModuleLoader__.load({
|
|
|
537
651
|
.cait-libtoolbar{display:flex;align-items:center;gap:6px;padding:9px 12px 6px;flex-wrap:wrap;flex:0 0 auto}
|
|
538
652
|
.cait-libsearch{display:flex;align-items:center;gap:6px;min-width:150px;flex:1;height:29px;padding:0 9px;border:1px solid var(--dsw-alias-border-l1);border-radius:7px;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-secondary)}
|
|
539
653
|
.cait-libsearch input{min-width:0;flex:1;border:0;outline:0;background:transparent;font-size:11.5px;color:var(--dsw-alias-label-primary)}
|
|
540
|
-
.cait-liblang{height:29px;padding:0
|
|
654
|
+
.cait-liblang{height:29px;padding:0 7px;border:1px solid var(--dsw-alias-border-l1);border-radius:7px;background:var(--dsw-alias-bg-layer-1);font-size:11px;flex:0 0 auto;min-width:132px;max-width:280px}
|
|
655
|
+
.cait-liblang:disabled{opacity:.65}
|
|
656
|
+
.cait-libwrite{display:inline-flex;align-items:center;gap:5px;flex:0 0 auto;font-size:10.5px;color:var(--dsw-alias-label-secondary);white-space:nowrap}
|
|
657
|
+
.cait-libwritelabel{font-size:10.5px;color:var(--dsw-alias-label-secondary)}
|
|
541
658
|
.cait-libscope{display:flex;align-items:center;gap:4px;padding:0 12px 8px;flex-wrap:wrap;flex:0 0 auto}
|
|
542
659
|
.cait-libtable-wrap{flex:1;min-height:0;overflow:auto;padding:0 12px}
|
|
543
660
|
.cait-libtable{width:100%;border-collapse:collapse;table-layout:fixed}
|
|
@@ -562,11 +679,13 @@ window.__ModuleLoader__.load({
|
|
|
562
679
|
.cait-libform-actions{display:flex;align-items:center;gap:6px;margin-top:11px}
|
|
563
680
|
.cait-libform-actions .cait-btn{flex:1}
|
|
564
681
|
.cait-batchbtn-plain{border-style:dashed}
|
|
682
|
+
.cait-batchbtn-danger{color:var(--dsw-alias-state-error-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 35%,var(--dsw-alias-border-l1))}
|
|
683
|
+
.cait-batchbtn-danger:hover:not(:disabled){border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}
|
|
565
684
|
.cait-alignpage{display:flex;flex-direction:column;gap:10px;flex:1;min-height:0;padding:12px 14px 14px;overflow:auto}
|
|
566
685
|
.cait-alignhead{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap}
|
|
567
686
|
.cait-alignhead h2{margin:0 0 3px;font-size:13.5px;font-weight:600;color:var(--dsw-alias-label-primary)}
|
|
568
687
|
.cait-alignactions{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
|
|
569
|
-
.cait-alignactions .cait-libinput{width:
|
|
688
|
+
.cait-alignactions .cait-libinput{width:170px;min-height:28px;height:28px;flex:0 0 auto}
|
|
570
689
|
.cait-alignfiles{display:flex;gap:8px;flex-wrap:wrap}
|
|
571
690
|
.cait-alignfile{display:flex;align-items:center;gap:8px;padding:7px 9px;border:1px dashed var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1);min-width:0;flex:1 1 300px}
|
|
572
691
|
.cait-alignlabel{font-size:10px;letter-spacing:.04em;text-transform:uppercase;color:var(--dsw-alias-label-secondary);flex:0 0 auto}
|
|
@@ -738,6 +857,10 @@ window.__ModuleLoader__.load({
|
|
|
738
857
|
check: ['M3 8.5l3.2 3.2L13 4.3'],
|
|
739
858
|
align: ['M2 5h8', 'M2 11h5', 'M12 3v10', 'M10 11l2 2 2-2'],
|
|
740
859
|
folder: ['M2 4h4l1.5 2H14v6.5H2z'],
|
|
860
|
+
chart: ['M3 13V8', 'M8 13V3', 'M13 13V6', 'M2 14h12'],
|
|
861
|
+
message: ['M2 3h12v8H8l-3 3v-3H2z'],
|
|
862
|
+
lock: ['M4 7h8v6H4z', 'M6 7V5a2 2 0 0 1 4 0v2'],
|
|
863
|
+
unlock: ['M4 7h8v6H4z', 'M6 7V5a2 2 0 0 1 3.8-1'],
|
|
741
864
|
}
|
|
742
865
|
const shape = paths[name] || paths.file
|
|
743
866
|
return h(
|
|
@@ -1003,6 +1126,7 @@ window.__ModuleLoader__.load({
|
|
|
1003
1126
|
pre,
|
|
1004
1127
|
onPreTranslate,
|
|
1005
1128
|
onCancelPreTranslate,
|
|
1129
|
+
onReplace,
|
|
1006
1130
|
view,
|
|
1007
1131
|
onView,
|
|
1008
1132
|
}) {
|
|
@@ -1108,6 +1232,19 @@ window.__ModuleLoader__.load({
|
|
|
1108
1232
|
h('span', { className: 'cait-btn-label' }, t('pretranslate.start')),
|
|
1109
1233
|
)
|
|
1110
1234
|
: null,
|
|
1235
|
+
documentsView
|
|
1236
|
+
? h(
|
|
1237
|
+
'button',
|
|
1238
|
+
{
|
|
1239
|
+
className: 'cait-btn',
|
|
1240
|
+
onClick: onReplace,
|
|
1241
|
+
disabled: Boolean(busy) || !doc,
|
|
1242
|
+
title: t('replace.open'),
|
|
1243
|
+
},
|
|
1244
|
+
h(Icon, { name: 'search', size: 14 }),
|
|
1245
|
+
h('span', { className: 'cait-btn-label' }, t('replace.open')),
|
|
1246
|
+
)
|
|
1247
|
+
: null,
|
|
1111
1248
|
documentsView
|
|
1112
1249
|
? h(
|
|
1113
1250
|
'button',
|
|
@@ -1120,7 +1257,7 @@ window.__ModuleLoader__.load({
|
|
|
1120
1257
|
? h(
|
|
1121
1258
|
'button',
|
|
1122
1259
|
{ className: 'cait-btn', onClick: onImport, disabled: Boolean(busy), title: t('nav.import') },
|
|
1123
|
-
h(Icon, { name: '
|
|
1260
|
+
h(Icon, { name: 'download', size: 14 }),
|
|
1124
1261
|
h('span', { className: 'cait-btn-label' }, t('nav.import')),
|
|
1125
1262
|
)
|
|
1126
1263
|
: null,
|
|
@@ -1128,7 +1265,7 @@ window.__ModuleLoader__.load({
|
|
|
1128
1265
|
? h(
|
|
1129
1266
|
'button',
|
|
1130
1267
|
{ className: 'cait-btn', onClick: onExport, disabled: Boolean(busy) || !doc, title: t('nav.export') },
|
|
1131
|
-
h(Icon, { name: '
|
|
1268
|
+
h(Icon, { name: 'upload', size: 14 }),
|
|
1132
1269
|
h('span', { className: 'cait-btn-label' }, t('nav.export')),
|
|
1133
1270
|
)
|
|
1134
1271
|
: null,
|
|
@@ -1137,9 +1274,9 @@ window.__ModuleLoader__.load({
|
|
|
1137
1274
|
)
|
|
1138
1275
|
}
|
|
1139
1276
|
|
|
1140
|
-
const SEGMENT_FILTERS = ['all', 'pending', 'translated', 'confirmed']
|
|
1277
|
+
const SEGMENT_FILTERS = ['all', 'pending', 'translated', 'confirmed', 'risk']
|
|
1141
1278
|
|
|
1142
|
-
function SegmentRail({ t, doc, selected, query, setQuery, filter, setFilter, onSelect, onConfirmAll, onPropagate, busy, qa }) {
|
|
1279
|
+
function SegmentRail({ t, doc, selected, query, setQuery, filter, setFilter, onSelect, onConfirmAll, onPropagate, onClearPretranslated, busy, qa }) {
|
|
1143
1280
|
const activeRef = useRef(null)
|
|
1144
1281
|
const storage = typeof localStorage !== 'undefined' ? localStorage : null
|
|
1145
1282
|
const [pageSize, setPageSize] = useState(() => readPageSize(storage))
|
|
@@ -1159,12 +1296,15 @@ window.__ModuleLoader__.load({
|
|
|
1159
1296
|
list.push(issue.code)
|
|
1160
1297
|
warns.set(issue.index, list)
|
|
1161
1298
|
}
|
|
1299
|
+
const riskByIndex = new Map()
|
|
1300
|
+
for (const item of qa?.risks || []) riskByIndex.set(item.index, item)
|
|
1162
1301
|
|
|
1163
1302
|
const counts = {
|
|
1164
1303
|
all: doc.progress.total,
|
|
1165
1304
|
pending: doc.progress.pending,
|
|
1166
1305
|
translated: doc.progress.translated,
|
|
1167
1306
|
confirmed: doc.progress.confirmed,
|
|
1307
|
+
risk: riskByIndex.size,
|
|
1168
1308
|
}
|
|
1169
1309
|
|
|
1170
1310
|
// How many empty segments could be filled from a sibling with the same
|
|
@@ -1183,10 +1323,17 @@ window.__ModuleLoader__.load({
|
|
|
1183
1323
|
if (list.length < 2 || !list.some((segment) => segment.target)) continue
|
|
1184
1324
|
duplicated += list.filter((segment) => !segment.target).length
|
|
1185
1325
|
}
|
|
1326
|
+
const pretranslated = doc.segments.filter((segment) => (
|
|
1327
|
+
segment.target &&
|
|
1328
|
+
!segment.confirmedAt &&
|
|
1329
|
+
['tm', 'mt', 'auto'].includes(segment.origin)
|
|
1330
|
+
)).length
|
|
1186
1331
|
const needle = query.trim().toLowerCase()
|
|
1187
1332
|
const filtered = doc.segments
|
|
1188
1333
|
.map((segment, index) => ({ segment, index }))
|
|
1189
|
-
.filter(({ segment }) =>
|
|
1334
|
+
.filter(({ segment, index }) =>
|
|
1335
|
+
filter === 'all' || (filter === 'risk' ? riskByIndex.has(index) : segment.status === filter),
|
|
1336
|
+
)
|
|
1190
1337
|
.filter(({ segment }) => {
|
|
1191
1338
|
if (!needle) return true
|
|
1192
1339
|
return (
|
|
@@ -1303,6 +1450,19 @@ window.__ModuleLoader__.load({
|
|
|
1303
1450
|
`${t('rail.propagate')} · ${duplicated}`,
|
|
1304
1451
|
)
|
|
1305
1452
|
: null,
|
|
1453
|
+
pretranslated > 0
|
|
1454
|
+
? h(
|
|
1455
|
+
'button',
|
|
1456
|
+
{
|
|
1457
|
+
className: 'cait-batchbtn cait-batchbtn-danger',
|
|
1458
|
+
onClick: onClearPretranslated,
|
|
1459
|
+
disabled: Boolean(busy),
|
|
1460
|
+
title: t('rail.clearPretranslatedHint'),
|
|
1461
|
+
},
|
|
1462
|
+
h(Icon, { name: 'trash', size: 11 }),
|
|
1463
|
+
`${t('rail.clearPretranslated')} · ${pretranslated}`,
|
|
1464
|
+
)
|
|
1465
|
+
: null,
|
|
1306
1466
|
h(
|
|
1307
1467
|
'div',
|
|
1308
1468
|
{ className: 'cait-rail-goto' },
|
|
@@ -1365,7 +1525,7 @@ window.__ModuleLoader__.load({
|
|
|
1365
1525
|
h('span', { className: `cait-dot ${segment.status}` }),
|
|
1366
1526
|
warns.has(index)
|
|
1367
1527
|
? h('span', {
|
|
1368
|
-
className:
|
|
1528
|
+
className: `cait-seg-warn ${riskByIndex.get(index)?.risk || 'low'}`,
|
|
1369
1529
|
title: (warns.get(index) || []).map((code) => t(`qa.code.${code}`)).join(' · '),
|
|
1370
1530
|
'aria-label': t('qa.warn'),
|
|
1371
1531
|
})
|
|
@@ -1421,9 +1581,21 @@ window.__ModuleLoader__.load({
|
|
|
1421
1581
|
)
|
|
1422
1582
|
}
|
|
1423
1583
|
|
|
1424
|
-
function EditorPane({ t, doc, index, segment, detail, draft, setDraft, dirty, busy, onConfirm, onNext, onPrev, onSave, onAdopt, onApplyTerm, textareaRef }) {
|
|
1584
|
+
function EditorPane({ t, doc, index, segment, detail, draft, setDraft, dirty, busy, onConfirm, onUnconfirm, onNext, onPrev, onSave, onUndo, onRedo, canUndo, canRedo, onToggleLock, onMerge, onSplit, onMergeCue, onSplitCue, onShiftSubtitles, onAdopt, onApplyTerm, onOpenBrief, onOpenResources, onOpenAnalysis, onOpenNotes, noteCount, qaRisk, textareaRef }) {
|
|
1425
1585
|
const hits = detail?.index === index ? detail.glossaryHits : []
|
|
1426
1586
|
const charCount = Array.from(draft || '').length
|
|
1587
|
+
const isSubtitle = doc.format === 'srt' || doc.format === 'vtt'
|
|
1588
|
+
const [shiftSeconds, setShiftSeconds] = useState('1')
|
|
1589
|
+
const cueStart = Number(segment.meta?.start)
|
|
1590
|
+
const cueEnd = Number(segment.meta?.end)
|
|
1591
|
+
const formatCue = (value) => {
|
|
1592
|
+
if (!Number.isFinite(value)) return '--:--.-'
|
|
1593
|
+
const total = Math.max(0, Math.round(value))
|
|
1594
|
+
const minutes = Math.floor(total / 60000)
|
|
1595
|
+
const seconds = Math.floor((total % 60000) / 1000)
|
|
1596
|
+
const millis = total % 1000
|
|
1597
|
+
return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}.${String(millis).padStart(3, '0')}`
|
|
1598
|
+
}
|
|
1427
1599
|
const progress = doc.progress
|
|
1428
1600
|
const remaining = progress.total - progress.confirmed
|
|
1429
1601
|
const statusLabel =
|
|
@@ -1438,9 +1610,31 @@ window.__ModuleLoader__.load({
|
|
|
1438
1610
|
// against. It is stored in `suggestion`, which adoption already preserves.
|
|
1439
1611
|
const baselineText = segment.suggestion || ''
|
|
1440
1612
|
const baselineOrigin = segment.origin || 'agent'
|
|
1613
|
+
const canMerge = !segment.locked && index < doc.segments.length - 1
|
|
1614
|
+
const canSplit = !segment.locked && String(segment.source || '').length > 1
|
|
1615
|
+
const splitAtCursor = () => {
|
|
1616
|
+
const sourceLength = String(segment.source || '').length
|
|
1617
|
+
const targetLength = String(draft || '').length
|
|
1618
|
+
const cursor = textareaRef?.current?.selectionStart
|
|
1619
|
+
const ratio = targetLength > 0 && Number.isInteger(cursor) ? cursor / targetLength : 0.5
|
|
1620
|
+
const at = Math.max(1, Math.min(sourceLength - 1, Math.round(sourceLength * ratio)))
|
|
1621
|
+
onSplit(at)
|
|
1622
|
+
}
|
|
1441
1623
|
|
|
1442
|
-
const onKeyDown = (event) => {
|
|
1443
|
-
|
|
1624
|
+
const onKeyDown = (event) => {
|
|
1625
|
+
const mod = event.metaKey || event.ctrlKey
|
|
1626
|
+
if (mod && event.key.toLowerCase() === 'z') {
|
|
1627
|
+
if (canUndo || canRedo) {
|
|
1628
|
+
event.preventDefault()
|
|
1629
|
+
if (event.shiftKey) onRedo()
|
|
1630
|
+
else onUndo()
|
|
1631
|
+
}
|
|
1632
|
+
} else if (mod && event.key.toLowerCase() === 'y') {
|
|
1633
|
+
if (canRedo) {
|
|
1634
|
+
event.preventDefault()
|
|
1635
|
+
onRedo()
|
|
1636
|
+
}
|
|
1637
|
+
} else if (mod && event.key === 'Enter') {
|
|
1444
1638
|
event.preventDefault()
|
|
1445
1639
|
onConfirm()
|
|
1446
1640
|
} else if (mod && event.key.toLowerCase() === 's') {
|
|
@@ -1472,6 +1666,37 @@ window.__ModuleLoader__.load({
|
|
|
1472
1666
|
h(Icon, { name: 'arrowRight', size: 11 }),
|
|
1473
1667
|
h('span', { className: 'cait-langchip' }, doc.targetLang),
|
|
1474
1668
|
h('span', null, `· ${doc.segments.length} ${t('doc.segments')}`),
|
|
1669
|
+
h(
|
|
1670
|
+
'button',
|
|
1671
|
+
{
|
|
1672
|
+
className: 'cait-linkbtn cait-brieflink',
|
|
1673
|
+
onClick: onOpenBrief,
|
|
1674
|
+
title: doc.brief ? t('panel.briefHint') : t('brief.empty'),
|
|
1675
|
+
},
|
|
1676
|
+
h(Icon, { name: 'file', size: 11 }),
|
|
1677
|
+
t('editor.brief'),
|
|
1678
|
+
doc.brief ? h('span', { className: 'cait-briefdot' }) : null,
|
|
1679
|
+
),
|
|
1680
|
+
h(
|
|
1681
|
+
'button',
|
|
1682
|
+
{
|
|
1683
|
+
className: 'cait-linkbtn',
|
|
1684
|
+
onClick: onOpenResources,
|
|
1685
|
+
title: t('resources.hint'),
|
|
1686
|
+
},
|
|
1687
|
+
h(Icon, { name: 'database', size: 11 }),
|
|
1688
|
+
t('editor.resources'),
|
|
1689
|
+
),
|
|
1690
|
+
h(
|
|
1691
|
+
'button',
|
|
1692
|
+
{
|
|
1693
|
+
className: 'cait-linkbtn',
|
|
1694
|
+
onClick: onOpenAnalysis,
|
|
1695
|
+
title: t('analysis.hint'),
|
|
1696
|
+
},
|
|
1697
|
+
h(Icon, { name: 'chart', size: 11 }),
|
|
1698
|
+
t('editor.analysis'),
|
|
1699
|
+
),
|
|
1475
1700
|
),
|
|
1476
1701
|
),
|
|
1477
1702
|
h(
|
|
@@ -1486,7 +1711,40 @@ window.__ModuleLoader__.load({
|
|
|
1486
1711
|
'div',
|
|
1487
1712
|
{ className: 'cait-seg-head' },
|
|
1488
1713
|
h('span', { className: 'cait-seg-label' }, `${t('editor.segment')} ${index + 1} / ${doc.segments.length}`),
|
|
1489
|
-
h(
|
|
1714
|
+
h(
|
|
1715
|
+
'span',
|
|
1716
|
+
{ className: 'cait-seg-flags' },
|
|
1717
|
+
h(
|
|
1718
|
+
'button',
|
|
1719
|
+
{
|
|
1720
|
+
className: `cait-linkbtn ${segment.locked ? 'active' : ''}`,
|
|
1721
|
+
onClick: onToggleLock,
|
|
1722
|
+
title: segment.locked ? t('editor.unlock') : t('editor.lock'),
|
|
1723
|
+
'aria-pressed': Boolean(segment.locked),
|
|
1724
|
+
},
|
|
1725
|
+
h(Icon, { name: segment.locked ? 'unlock' : 'lock', size: 11 }),
|
|
1726
|
+
segment.locked ? t('editor.locked') : t('editor.lock'),
|
|
1727
|
+
),
|
|
1728
|
+
h(
|
|
1729
|
+
'button',
|
|
1730
|
+
{
|
|
1731
|
+
className: 'cait-linkbtn cait-notelink',
|
|
1732
|
+
onClick: onOpenNotes,
|
|
1733
|
+
title: t('notes.hint'),
|
|
1734
|
+
},
|
|
1735
|
+
h(Icon, { name: 'message', size: 11 }),
|
|
1736
|
+
t('editor.notes'),
|
|
1737
|
+
noteCount ? h('span', { className: 'cait-notecount' }, String(noteCount)) : null,
|
|
1738
|
+
),
|
|
1739
|
+
qaRisk
|
|
1740
|
+
? h(
|
|
1741
|
+
'span',
|
|
1742
|
+
{ className: `cait-riskchip ${qaRisk.risk}`, title: qaRisk.codes.map((code) => t(`qa.code.${code}`)).join(' · ') },
|
|
1743
|
+
`${t('qa.risk')} · ${t(`qa.${qaRisk.risk}`)}`,
|
|
1744
|
+
)
|
|
1745
|
+
: null,
|
|
1746
|
+
h('span', { className: `cait-badge ${segment.status}` }, statusLabel),
|
|
1747
|
+
),
|
|
1490
1748
|
),
|
|
1491
1749
|
h(
|
|
1492
1750
|
'div',
|
|
@@ -1569,6 +1827,7 @@ window.__ModuleLoader__.load({
|
|
|
1569
1827
|
value: draft,
|
|
1570
1828
|
onChange: (event) => setDraft(event.target.value),
|
|
1571
1829
|
onKeyDown,
|
|
1830
|
+
readOnly: Boolean(segment.locked),
|
|
1572
1831
|
placeholder: t('editor.targetPlaceholder'),
|
|
1573
1832
|
spellCheck: false,
|
|
1574
1833
|
rows: 8,
|
|
@@ -1580,10 +1839,18 @@ window.__ModuleLoader__.load({
|
|
|
1580
1839
|
{ className: 'cait-actionbar' },
|
|
1581
1840
|
h(
|
|
1582
1841
|
'button',
|
|
1583
|
-
{ className: 'cait-btn cait-btn-primary', onClick: onConfirm, disabled: Boolean(busy) || !draft.trim() },
|
|
1842
|
+
{ className: 'cait-btn cait-btn-primary', onClick: onConfirm, disabled: Boolean(busy) || Boolean(segment.locked) || !draft.trim() },
|
|
1584
1843
|
busy === 'confirm' ? h(Spinner) : h(Icon, { name: 'check', size: 14 }),
|
|
1585
1844
|
segment.status === 'confirmed' ? t('editor.update') : t('editor.confirm'),
|
|
1586
1845
|
),
|
|
1846
|
+
segment.status === 'confirmed'
|
|
1847
|
+
? h(
|
|
1848
|
+
'button',
|
|
1849
|
+
{ className: 'cait-btn cait-btn-ghost', onClick: onUnconfirm, disabled: Boolean(busy) || Boolean(segment.locked), title: t('editor.unconfirmHint') },
|
|
1850
|
+
h(Icon, { name: 'refresh', size: 13 }),
|
|
1851
|
+
t('editor.unconfirm'),
|
|
1852
|
+
)
|
|
1853
|
+
: null,
|
|
1587
1854
|
h(
|
|
1588
1855
|
'button',
|
|
1589
1856
|
{ className: 'cait-btn', onClick: onNext, disabled: Boolean(busy) },
|
|
@@ -1596,9 +1863,60 @@ window.__ModuleLoader__.load({
|
|
|
1596
1863
|
h(Icon, { name: 'chevronLeft', size: 13 }),
|
|
1597
1864
|
t('editor.prev'),
|
|
1598
1865
|
),
|
|
1866
|
+
isSubtitle
|
|
1867
|
+
? h(
|
|
1868
|
+
'div',
|
|
1869
|
+
{ className: 'cait-subbar' },
|
|
1870
|
+
h('span', { className: 'cait-subtime' }, `${formatCue(cueStart)} → ${formatCue(cueEnd)}`),
|
|
1871
|
+
h('input', {
|
|
1872
|
+
className: 'cait-suboffset',
|
|
1873
|
+
type: 'number',
|
|
1874
|
+
step: '0.1',
|
|
1875
|
+
value: shiftSeconds,
|
|
1876
|
+
onChange: (event) => setShiftSeconds(event.target.value),
|
|
1877
|
+
title: t('editor.shiftHint'),
|
|
1878
|
+
}),
|
|
1879
|
+
h(
|
|
1880
|
+
'button',
|
|
1881
|
+
{ className: 'cait-btn cait-btn-ghost', onClick: () => onShiftSubtitles(Math.round(Number(shiftSeconds) * 1000)), disabled: Boolean(busy) || Boolean(segment.locked) || !Number.isFinite(Number(shiftSeconds)) },
|
|
1882
|
+
t('editor.shift'),
|
|
1883
|
+
),
|
|
1884
|
+
h(
|
|
1885
|
+
'button',
|
|
1886
|
+
{ className: 'cait-btn cait-btn-ghost', onClick: onMergeCue, disabled: Boolean(busy) || Boolean(segment.locked) || index >= doc.segments.length - 1 },
|
|
1887
|
+
t('editor.mergeCue'),
|
|
1888
|
+
),
|
|
1889
|
+
h(
|
|
1890
|
+
'button',
|
|
1891
|
+
{ className: 'cait-btn cait-btn-ghost', onClick: () => onSplitCue(Math.round((cueStart + cueEnd) / 2)), disabled: Boolean(busy) || Boolean(segment.locked) || !(cueEnd > cueStart) },
|
|
1892
|
+
t('editor.splitCue'),
|
|
1893
|
+
),
|
|
1894
|
+
)
|
|
1895
|
+
: [
|
|
1896
|
+
h(
|
|
1897
|
+
'button',
|
|
1898
|
+
{ key: 'merge', className: 'cait-btn cait-btn-ghost', onClick: onMerge, disabled: Boolean(busy) || !canMerge, title: t('editor.mergeHint') },
|
|
1899
|
+
t('editor.merge'),
|
|
1900
|
+
),
|
|
1901
|
+
h(
|
|
1902
|
+
'button',
|
|
1903
|
+
{ key: 'split', className: 'cait-btn cait-btn-ghost', onClick: splitAtCursor, disabled: Boolean(busy) || !canSplit, title: t('editor.splitHint') },
|
|
1904
|
+
t('editor.split'),
|
|
1905
|
+
),
|
|
1906
|
+
],
|
|
1907
|
+
h(
|
|
1908
|
+
'button',
|
|
1909
|
+
{ className: 'cait-iconbtn', onClick: onUndo, disabled: Boolean(busy) || !canUndo, title: t('editor.undo'), 'aria-label': t('editor.undo') },
|
|
1910
|
+
'↶',
|
|
1911
|
+
),
|
|
1599
1912
|
h(
|
|
1600
1913
|
'button',
|
|
1601
|
-
{ className: 'cait-iconbtn
|
|
1914
|
+
{ className: 'cait-iconbtn', onClick: onRedo, disabled: Boolean(busy) || !canRedo, title: t('editor.redo'), 'aria-label': t('editor.redo') },
|
|
1915
|
+
'↷',
|
|
1916
|
+
),
|
|
1917
|
+
h(
|
|
1918
|
+
'button',
|
|
1919
|
+
{ className: 'cait-iconbtn cait-savebtn', onClick: onSave, disabled: Boolean(busy) || Boolean(segment.locked) || !dirty, title: t('editor.save') },
|
|
1602
1920
|
h(Icon, { name: 'save', size: 14 }),
|
|
1603
1921
|
),
|
|
1604
1922
|
h('span', { className: 'cait-hint' }, t('editor.hint')),
|
|
@@ -1721,6 +2039,19 @@ window.__ModuleLoader__.load({
|
|
|
1721
2039
|
onCopy,
|
|
1722
2040
|
onOpenSettings,
|
|
1723
2041
|
onSearchLibrary,
|
|
2042
|
+
qa,
|
|
2043
|
+
onRefreshQa,
|
|
2044
|
+
onJumpQa,
|
|
2045
|
+
onFixQa,
|
|
2046
|
+
review,
|
|
2047
|
+
reviewBusy,
|
|
2048
|
+
onReview,
|
|
2049
|
+
onApplyReview,
|
|
2050
|
+
concordance,
|
|
2051
|
+
concordanceQuery,
|
|
2052
|
+
setConcordanceQuery,
|
|
2053
|
+
onConcordance,
|
|
2054
|
+
onApplyConcordance,
|
|
1724
2055
|
busy,
|
|
1725
2056
|
}) {
|
|
1726
2057
|
const [termQuery, setTermQuery] = useState('')
|
|
@@ -1800,6 +2131,19 @@ window.__ModuleLoader__.load({
|
|
|
1800
2131
|
h(Icon, { name: 'sparkles', size: 13 }),
|
|
1801
2132
|
t('panel.mt'),
|
|
1802
2133
|
),
|
|
2134
|
+
h(
|
|
2135
|
+
'button',
|
|
2136
|
+
{ className: `cait-tab${tab === 'qa' ? ' active' : ''}`, onClick: () => setTab('qa') },
|
|
2137
|
+
h(Icon, { name: 'alert', size: 13 }),
|
|
2138
|
+
t('panel.qa'),
|
|
2139
|
+
qa?.issueCount ? h('span', { className: 'cait-count' }, qa.issueCount) : null,
|
|
2140
|
+
),
|
|
2141
|
+
h(
|
|
2142
|
+
'button',
|
|
2143
|
+
{ className: `cait-tab${tab === 'concordance' ? ' active' : ''}`, onClick: () => setTab('concordance') },
|
|
2144
|
+
h(Icon, { name: 'search', size: 13 }),
|
|
2145
|
+
t('panel.concordance'),
|
|
2146
|
+
),
|
|
1803
2147
|
),
|
|
1804
2148
|
h(
|
|
1805
2149
|
'div',
|
|
@@ -1877,7 +2221,7 @@ window.__ModuleLoader__.load({
|
|
|
1877
2221
|
event.target.value = ''
|
|
1878
2222
|
},
|
|
1879
2223
|
}),
|
|
1880
|
-
h('button', { className: 'cait-btn', onClick: () => tmxInput.current?.click(), disabled: Boolean(busy) }, h(Icon, { name: '
|
|
2224
|
+
h('button', { className: 'cait-btn', onClick: () => tmxInput.current?.click(), disabled: Boolean(busy) }, h(Icon, { name: 'download', size: 12 }), t('panel.importTmx')),
|
|
1881
2225
|
h('button', { className: 'cait-btn cait-btn-danger', onClick: onClearTm, disabled: Boolean(busy) || data.tm.count === 0 }, t('panel.clearTm')),
|
|
1882
2226
|
),
|
|
1883
2227
|
)
|
|
@@ -1895,7 +2239,7 @@ window.__ModuleLoader__.load({
|
|
|
1895
2239
|
h(
|
|
1896
2240
|
'button',
|
|
1897
2241
|
{ key: hit.id, className: 'cait-chip cait-chip-action', onClick: () => onApplyTerm(hit), title: hit.note || hit.target },
|
|
1898
|
-
`${hit.term} → ${hit.target}`,
|
|
2242
|
+
`${hit.term} → ${hit.target}${hit.forbidden ? ` · ${t('panel.termForbidden')}` : hit.preferred ? ` · ${t('panel.termPreferred')}` : ''}`,
|
|
1899
2243
|
),
|
|
1900
2244
|
),
|
|
1901
2245
|
)
|
|
@@ -1928,7 +2272,7 @@ window.__ModuleLoader__.load({
|
|
|
1928
2272
|
event.target.value = ''
|
|
1929
2273
|
},
|
|
1930
2274
|
}),
|
|
1931
|
-
h('button', { className: 'cait-btn', onClick: () => glossaryInput.current?.click(), disabled: Boolean(busy) }, h(Icon, { name: '
|
|
2275
|
+
h('button', { className: 'cait-btn', onClick: () => glossaryInput.current?.click(), disabled: Boolean(busy) }, h(Icon, { name: 'download', size: 12 }), t('panel.importCsv')),
|
|
1932
2276
|
h('button', { className: 'cait-btn cait-btn-danger', onClick: onClearGlossary, disabled: Boolean(busy) || data.glossary.length === 0 }, t('panel.clearGlossary')),
|
|
1933
2277
|
),
|
|
1934
2278
|
)
|
|
@@ -1993,6 +2337,239 @@ window.__ModuleLoader__.load({
|
|
|
1993
2337
|
),
|
|
1994
2338
|
)
|
|
1995
2339
|
: null,
|
|
2340
|
+
tab === 'qa'
|
|
2341
|
+
? h(
|
|
2342
|
+
'div',
|
|
2343
|
+
null,
|
|
2344
|
+
h(
|
|
2345
|
+
'div',
|
|
2346
|
+
{ className: 'cait-subhead' },
|
|
2347
|
+
h('span', null, t('qa.title')),
|
|
2348
|
+
h(
|
|
2349
|
+
'div',
|
|
2350
|
+
{ className: 'cait-subhead-actions' },
|
|
2351
|
+
h(
|
|
2352
|
+
'button',
|
|
2353
|
+
{
|
|
2354
|
+
className: 'cait-linkbtn',
|
|
2355
|
+
onClick: onReview,
|
|
2356
|
+
disabled: Boolean(busy) || Boolean(reviewBusy),
|
|
2357
|
+
title: t('qa.review'),
|
|
2358
|
+
},
|
|
2359
|
+
h(Icon, { name: 'sparkles', size: 12 }),
|
|
2360
|
+
reviewBusy ? t('qa.reviewing') : t('qa.review'),
|
|
2361
|
+
),
|
|
2362
|
+
h(
|
|
2363
|
+
'button',
|
|
2364
|
+
{ className: 'cait-linkbtn', onClick: onRefreshQa, disabled: Boolean(busy) },
|
|
2365
|
+
h(Icon, { name: 'refresh', size: 12 }),
|
|
2366
|
+
t('qa.refresh'),
|
|
2367
|
+
),
|
|
2368
|
+
),
|
|
2369
|
+
),
|
|
2370
|
+
qa
|
|
2371
|
+
? h(
|
|
2372
|
+
'div',
|
|
2373
|
+
null,
|
|
2374
|
+
h(
|
|
2375
|
+
'div',
|
|
2376
|
+
{ className: 'cait-riskcounts' },
|
|
2377
|
+
['high', 'medium', 'low'].map((level) =>
|
|
2378
|
+
h(
|
|
2379
|
+
'span',
|
|
2380
|
+
{ key: level, className: `cait-riskcount ${level}` },
|
|
2381
|
+
`${t(`qa.${level}`)} ${qa.riskCounts?.[level] || 0}`,
|
|
2382
|
+
),
|
|
2383
|
+
),
|
|
2384
|
+
),
|
|
2385
|
+
(qa.risks || []).length
|
|
2386
|
+
? qa.risks.map((item) => {
|
|
2387
|
+
const segment = doc.segments[item.index]
|
|
2388
|
+
const codes = (item.codes || []).map((code) => t(`qa.code.${code}`)).join(' · ')
|
|
2389
|
+
return h(
|
|
2390
|
+
'div',
|
|
2391
|
+
{
|
|
2392
|
+
key: item.index,
|
|
2393
|
+
className: `cait-riskrow ${item.risk}`,
|
|
2394
|
+
role: 'button',
|
|
2395
|
+
tabIndex: 0,
|
|
2396
|
+
onClick: () => onJumpQa(item.index),
|
|
2397
|
+
onKeyDown: (event) => {
|
|
2398
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
2399
|
+
event.preventDefault()
|
|
2400
|
+
onJumpQa(item.index)
|
|
2401
|
+
}
|
|
2402
|
+
},
|
|
2403
|
+
title: codes,
|
|
2404
|
+
},
|
|
2405
|
+
h('span', { className: 'cait-riskrow-index' }, `${item.index + 1}`),
|
|
2406
|
+
h(
|
|
2407
|
+
'div',
|
|
2408
|
+
{ className: 'cait-riskrow-body' },
|
|
2409
|
+
h('span', { className: 'cait-riskrow-source' }, segment?.source || ''),
|
|
2410
|
+
h('span', { className: 'cait-riskrow-codes' }, codes),
|
|
2411
|
+
onFixQa
|
|
2412
|
+
? h(
|
|
2413
|
+
'div',
|
|
2414
|
+
{ className: 'cait-riskactions' },
|
|
2415
|
+
h(
|
|
2416
|
+
'button',
|
|
2417
|
+
{
|
|
2418
|
+
className: 'cait-riskaction',
|
|
2419
|
+
onClick: (event) => {
|
|
2420
|
+
event.stopPropagation()
|
|
2421
|
+
onFixQa(item, 'copy-source')
|
|
2422
|
+
},
|
|
2423
|
+
disabled: Boolean(busy),
|
|
2424
|
+
},
|
|
2425
|
+
t('qa.fixCopy'),
|
|
2426
|
+
),
|
|
2427
|
+
h(
|
|
2428
|
+
'button',
|
|
2429
|
+
{
|
|
2430
|
+
className: 'cait-riskaction',
|
|
2431
|
+
onClick: (event) => {
|
|
2432
|
+
event.stopPropagation()
|
|
2433
|
+
onFixQa(item, 'clear')
|
|
2434
|
+
},
|
|
2435
|
+
disabled: Boolean(busy),
|
|
2436
|
+
},
|
|
2437
|
+
t('qa.fixClear'),
|
|
2438
|
+
),
|
|
2439
|
+
item.termTarget
|
|
2440
|
+
? h(
|
|
2441
|
+
'button',
|
|
2442
|
+
{
|
|
2443
|
+
className: 'cait-riskaction',
|
|
2444
|
+
onClick: (event) => {
|
|
2445
|
+
event.stopPropagation()
|
|
2446
|
+
onFixQa(item, 'term')
|
|
2447
|
+
},
|
|
2448
|
+
disabled: Boolean(busy),
|
|
2449
|
+
},
|
|
2450
|
+
t('qa.fixTerm'),
|
|
2451
|
+
)
|
|
2452
|
+
: null,
|
|
2453
|
+
)
|
|
2454
|
+
: null,
|
|
2455
|
+
),
|
|
2456
|
+
h(Icon, { name: 'chevronRight', size: 12 }),
|
|
2457
|
+
)
|
|
2458
|
+
})
|
|
2459
|
+
: h('p', { className: 'cait-empty-note' }, t('qa.empty')),
|
|
2460
|
+
)
|
|
2461
|
+
: h('p', { className: 'cait-empty-note' }, t('qa.loading')),
|
|
2462
|
+
review
|
|
2463
|
+
? h(
|
|
2464
|
+
'div',
|
|
2465
|
+
{ className: 'cait-review' },
|
|
2466
|
+
h('div', { className: 'cait-listhead' }, t('qa.reviewTitle')),
|
|
2467
|
+
(review.issues || []).length
|
|
2468
|
+
? review.issues.map((issue, issueIndex) => {
|
|
2469
|
+
const severity = ['high', 'medium', 'low'].includes(issue.severity)
|
|
2470
|
+
? issue.severity
|
|
2471
|
+
: 'medium'
|
|
2472
|
+
return h(
|
|
2473
|
+
'div',
|
|
2474
|
+
{
|
|
2475
|
+
key: `${issue.index}-${issueIndex}`,
|
|
2476
|
+
className: 'cait-reviewrow',
|
|
2477
|
+
role: 'button',
|
|
2478
|
+
tabIndex: 0,
|
|
2479
|
+
onClick: () => onJumpQa(issue.index),
|
|
2480
|
+
onKeyDown: (event) => {
|
|
2481
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
2482
|
+
event.preventDefault()
|
|
2483
|
+
onJumpQa(issue.index)
|
|
2484
|
+
}
|
|
2485
|
+
},
|
|
2486
|
+
},
|
|
2487
|
+
h('span', { className: 'cait-reviewrow-index' }, `${issue.index + 1}`),
|
|
2488
|
+
h(
|
|
2489
|
+
'div',
|
|
2490
|
+
{ className: 'cait-reviewrow-body' },
|
|
2491
|
+
h(
|
|
2492
|
+
'span',
|
|
2493
|
+
{ className: `cait-reviewrow-msg ${severity}` },
|
|
2494
|
+
`[${t(`qa.${severity}`)}] ${issue.message}`,
|
|
2495
|
+
),
|
|
2496
|
+
issue.suggestion
|
|
2497
|
+
? h(
|
|
2498
|
+
'button',
|
|
2499
|
+
{
|
|
2500
|
+
className: 'cait-riskaction',
|
|
2501
|
+
onClick: (event) => {
|
|
2502
|
+
event.stopPropagation()
|
|
2503
|
+
onApplyReview(issue)
|
|
2504
|
+
},
|
|
2505
|
+
},
|
|
2506
|
+
t('qa.reviewApply'),
|
|
2507
|
+
)
|
|
2508
|
+
: null,
|
|
2509
|
+
),
|
|
2510
|
+
)
|
|
2511
|
+
})
|
|
2512
|
+
: h('p', { className: 'cait-empty-note' }, t('qa.reviewEmpty')),
|
|
2513
|
+
)
|
|
2514
|
+
: null,
|
|
2515
|
+
)
|
|
2516
|
+
: null,
|
|
2517
|
+
tab === 'concordance'
|
|
2518
|
+
? h(
|
|
2519
|
+
'div',
|
|
2520
|
+
null,
|
|
2521
|
+
h(
|
|
2522
|
+
'form',
|
|
2523
|
+
{
|
|
2524
|
+
className: 'cait-search',
|
|
2525
|
+
onSubmit: (event) => {
|
|
2526
|
+
event.preventDefault()
|
|
2527
|
+
onConcordance()
|
|
2528
|
+
},
|
|
2529
|
+
},
|
|
2530
|
+
h(Icon, { name: 'search', size: 13 }),
|
|
2531
|
+
h('input', {
|
|
2532
|
+
value: concordanceQuery,
|
|
2533
|
+
onChange: (event) => setConcordanceQuery(event.target.value),
|
|
2534
|
+
placeholder: t('panel.concordancePlaceholder'),
|
|
2535
|
+
'aria-label': t('panel.concordancePlaceholder'),
|
|
2536
|
+
}),
|
|
2537
|
+
h(
|
|
2538
|
+
'button',
|
|
2539
|
+
{ className: 'cait-iconbtn', type: 'submit', title: t('panel.searchConcordance') },
|
|
2540
|
+
h(Icon, { name: 'arrowRight', size: 13 }),
|
|
2541
|
+
),
|
|
2542
|
+
),
|
|
2543
|
+
h('p', { className: 'cait-mt-hint' }, t('panel.concordanceHint')),
|
|
2544
|
+
concordance
|
|
2545
|
+
? h(
|
|
2546
|
+
'div',
|
|
2547
|
+
null,
|
|
2548
|
+
h(
|
|
2549
|
+
'div',
|
|
2550
|
+
{ className: 'cait-listhead' },
|
|
2551
|
+
h('span', null, t('panel.concordance')),
|
|
2552
|
+
h('span', { className: 'cait-count' }, concordance.total ?? concordance.entries?.length ?? 0),
|
|
2553
|
+
),
|
|
2554
|
+
(concordance.entries || []).length
|
|
2555
|
+
? concordance.entries.map((entry, i) =>
|
|
2556
|
+
h(
|
|
2557
|
+
'button',
|
|
2558
|
+
{
|
|
2559
|
+
key: `${entry.tmId || entry.library || 'c'}-${i}`,
|
|
2560
|
+
className: 'cait-concordance-row',
|
|
2561
|
+
onClick: () => onApplyConcordance(entry),
|
|
2562
|
+
},
|
|
2563
|
+
h('span', { className: 'cait-concordance-source' }, entry.source),
|
|
2564
|
+
h('span', { className: 'cait-concordance-target' }, entry.target),
|
|
2565
|
+
h('span', { className: 'cait-concordance-meta' }, entry.library || ''),
|
|
2566
|
+
),
|
|
2567
|
+
)
|
|
2568
|
+
: h('p', { className: 'cait-empty-note' }, t('panel.noConcordance')),
|
|
2569
|
+
)
|
|
2570
|
+
: null,
|
|
2571
|
+
)
|
|
2572
|
+
: null,
|
|
1996
2573
|
),
|
|
1997
2574
|
)
|
|
1998
2575
|
}
|
|
@@ -2185,6 +2762,7 @@ window.__ModuleLoader__.load({
|
|
|
2185
2762
|
const sourceRef = useRef(null)
|
|
2186
2763
|
const targetRef = useRef(null)
|
|
2187
2764
|
const libraries = data?.tmLibraries || []
|
|
2765
|
+
const languageOptions = ['zh-CN', 'zh-TW', 'en', 'ja', 'ko', 'fr', 'de', 'es', 'pt-BR', 'ru', 'it', 'ar']
|
|
2188
2766
|
|
|
2189
2767
|
useEffect(() => {
|
|
2190
2768
|
if (!tmId && libraries.length) setTmId(libraries[0].id)
|
|
@@ -2285,7 +2863,12 @@ window.__ModuleLoader__.load({
|
|
|
2285
2863
|
action: async () => {
|
|
2286
2864
|
setWorking(true)
|
|
2287
2865
|
try {
|
|
2288
|
-
const written = await rpc('writeAlignment', {
|
|
2866
|
+
const written = await rpc('writeAlignment', {
|
|
2867
|
+
pairs,
|
|
2868
|
+
tmId: tmId || undefined,
|
|
2869
|
+
sourceLang: sourceLang || undefined,
|
|
2870
|
+
targetLang: targetLang || undefined,
|
|
2871
|
+
})
|
|
2289
2872
|
notify(t('align.written', { written: written.written, skipped: written.skipped }))
|
|
2290
2873
|
} catch (error) {
|
|
2291
2874
|
report(error)
|
|
@@ -2333,15 +2916,20 @@ window.__ModuleLoader__.load({
|
|
|
2333
2916
|
h(
|
|
2334
2917
|
'div',
|
|
2335
2918
|
{ className: 'cait-alignactions' },
|
|
2919
|
+
h('datalist', { id: 'cait-align-languages' }, languageOptions.map((code) => h('option', { key: code, value: code }))),
|
|
2336
2920
|
h('input', {
|
|
2337
2921
|
className: 'cait-libinput',
|
|
2338
2922
|
placeholder: t('align.sourceLang'),
|
|
2923
|
+
title: t('align.langHint'),
|
|
2924
|
+
list: 'cait-align-languages',
|
|
2339
2925
|
value: sourceLang,
|
|
2340
2926
|
onChange: (event) => setSourceLang(event.target.value),
|
|
2341
2927
|
}),
|
|
2342
2928
|
h('input', {
|
|
2343
2929
|
className: 'cait-libinput',
|
|
2344
2930
|
placeholder: t('align.targetLang'),
|
|
2931
|
+
title: t('align.langHint'),
|
|
2932
|
+
list: 'cait-align-languages',
|
|
2345
2933
|
value: targetLang,
|
|
2346
2934
|
onChange: (event) => setTargetLang(event.target.value),
|
|
2347
2935
|
}),
|
|
@@ -2351,8 +2939,15 @@ window.__ModuleLoader__.load({
|
|
|
2351
2939
|
className: 'cait-select cait-liblang',
|
|
2352
2940
|
value: tmId,
|
|
2353
2941
|
onChange: (event) => setTmId(event.target.value),
|
|
2942
|
+
title: t('align.tmHint'),
|
|
2943
|
+
'aria-label': t('align.tm'),
|
|
2944
|
+
disabled: !libraries.length,
|
|
2354
2945
|
},
|
|
2355
|
-
libraries.
|
|
2946
|
+
libraries.length
|
|
2947
|
+
? libraries.map((library) =>
|
|
2948
|
+
h('option', { key: library.id, value: library.id }, t('align.tmOption', { name: library.name })),
|
|
2949
|
+
)
|
|
2950
|
+
: h('option', { value: '' }, t('align.noTm')),
|
|
2356
2951
|
),
|
|
2357
2952
|
h(
|
|
2358
2953
|
'button',
|
|
@@ -2467,6 +3062,7 @@ window.__ModuleLoader__.load({
|
|
|
2467
3062
|
onCreateLibrary,
|
|
2468
3063
|
onRenameLibrary,
|
|
2469
3064
|
onDeleteLibrary,
|
|
3065
|
+
onSetMounts,
|
|
2470
3066
|
}) {
|
|
2471
3067
|
const storage = typeof localStorage !== 'undefined' ? localStorage : null
|
|
2472
3068
|
const [kind, setKind] = useState(seed?.kind === 'glossary' ? 'glossary' : 'tm')
|
|
@@ -2494,7 +3090,23 @@ window.__ModuleLoader__.load({
|
|
|
2494
3090
|
const idKey = kind === 'tm' ? 'tmId' : 'glossaryId'
|
|
2495
3091
|
const labelKey = 'name'
|
|
2496
3092
|
const scopeKey = scopeIds.join(',')
|
|
2497
|
-
const
|
|
3093
|
+
const defaultWriteId = kind === 'tm' ? data?.mounts?.defaultTmId : data?.mounts?.defaultGlossaryId
|
|
3094
|
+
const targetLibraryId = libraries.some((library) => library[idKey] === defaultWriteId)
|
|
3095
|
+
? defaultWriteId
|
|
3096
|
+
: libraries[0]?.[idKey] || ''
|
|
3097
|
+
|
|
3098
|
+
const changeWriteLibrary = async (value) => {
|
|
3099
|
+
if (!value || value === targetLibraryId) return
|
|
3100
|
+
// A default must also be mounted; adding it here keeps the selector
|
|
3101
|
+
// from failing when the chosen library was only in the list view.
|
|
3102
|
+
if (kind === 'tm') {
|
|
3103
|
+
const tmIds = Array.from(new Set([...(data?.mounts?.tmIds || []), value]))
|
|
3104
|
+
await onSetMounts?.({ tmIds, defaultTmId: value })
|
|
3105
|
+
} else {
|
|
3106
|
+
const glossaryIds = Array.from(new Set([...(data?.mounts?.glossaryIds || []), value]))
|
|
3107
|
+
await onSetMounts?.({ glossaryIds, defaultGlossaryId: value })
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
2498
3110
|
|
|
2499
3111
|
useEffect(() => {
|
|
2500
3112
|
const timer = setTimeout(() => setDebouncedQuery(query), 250)
|
|
@@ -2735,6 +3347,35 @@ window.__ModuleLoader__.load({
|
|
|
2735
3347
|
})
|
|
2736
3348
|
}
|
|
2737
3349
|
|
|
3350
|
+
const maintainTm = async (action) => {
|
|
3351
|
+
if (!targetLibraryId) return
|
|
3352
|
+
try {
|
|
3353
|
+
const preview = await rpc('tmMaintenancePreview', {
|
|
3354
|
+
action,
|
|
3355
|
+
tmId: targetLibraryId,
|
|
3356
|
+
emptyTarget: true,
|
|
3357
|
+
})
|
|
3358
|
+
const count = action === 'dedupe' ? preview.duplicates : preview.matched
|
|
3359
|
+
onConfirm({
|
|
3360
|
+
title: t(`tm.maintain.${action}`),
|
|
3361
|
+
message: t(`tm.maintain.${action}Hint`).replace('{count}', String(count)),
|
|
3362
|
+
confirmLabel: t(`tm.maintain.${action}`),
|
|
3363
|
+
action: async () => {
|
|
3364
|
+
const result = await rpc(action === 'dedupe' ? 'dedupeTmLibrary' : 'pruneTmLibrary', {
|
|
3365
|
+
tmId: targetLibraryId,
|
|
3366
|
+
emptyTarget: true,
|
|
3367
|
+
confirm: true,
|
|
3368
|
+
})
|
|
3369
|
+
if (result?.state) onApplyState(result.state)
|
|
3370
|
+
await loadRows()
|
|
3371
|
+
notify(t('tm.maintain.done').replace('{count}', String(count)))
|
|
3372
|
+
},
|
|
3373
|
+
})
|
|
3374
|
+
} catch (error) {
|
|
3375
|
+
report(error)
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
|
|
2738
3379
|
const exportLibrary = async () => {
|
|
2739
3380
|
if (!targetLibraryId) return
|
|
2740
3381
|
setWorking('export')
|
|
@@ -3007,6 +3648,28 @@ window.__ModuleLoader__.load({
|
|
|
3007
3648
|
['tbx', 'csv', 'json'].map((format) => h('option', { key: format, value: format }, format.toUpperCase())),
|
|
3008
3649
|
)
|
|
3009
3650
|
: null,
|
|
3651
|
+
h(
|
|
3652
|
+
'label',
|
|
3653
|
+
{ className: 'cait-libwrite', title: t(kind === 'tm' ? 'libraries.defaultTm' : 'libraries.defaultGlossary') },
|
|
3654
|
+
h(
|
|
3655
|
+
'span',
|
|
3656
|
+
{ className: 'cait-libwritelabel' },
|
|
3657
|
+
t(kind === 'tm' ? 'libraries.defaultTm' : 'libraries.defaultGlossary'),
|
|
3658
|
+
),
|
|
3659
|
+
h(
|
|
3660
|
+
'select',
|
|
3661
|
+
{
|
|
3662
|
+
className: 'cait-liblang',
|
|
3663
|
+
value: targetLibraryId,
|
|
3664
|
+
onChange: (event) => changeWriteLibrary(event.target.value),
|
|
3665
|
+
disabled: Boolean(busy) || !libraries.length,
|
|
3666
|
+
'aria-label': t(kind === 'tm' ? 'libraries.defaultTm' : 'libraries.defaultGlossary'),
|
|
3667
|
+
},
|
|
3668
|
+
libraries.map((library) =>
|
|
3669
|
+
h('option', { key: library[idKey], value: library[idKey] }, library[labelKey]),
|
|
3670
|
+
),
|
|
3671
|
+
),
|
|
3672
|
+
),
|
|
3010
3673
|
kind === 'glossary'
|
|
3011
3674
|
? h(
|
|
3012
3675
|
'button',
|
|
@@ -3036,7 +3699,7 @@ window.__ModuleLoader__.load({
|
|
|
3036
3699
|
disabled: Boolean(busy) || !targetLibraryId,
|
|
3037
3700
|
title: t('library.import'),
|
|
3038
3701
|
},
|
|
3039
|
-
h(Icon, { name: '
|
|
3702
|
+
h(Icon, { name: 'download', size: 13 }),
|
|
3040
3703
|
h('span', { className: 'cait-btn-label' }, t('library.import')),
|
|
3041
3704
|
),
|
|
3042
3705
|
h(
|
|
@@ -3048,9 +3711,37 @@ window.__ModuleLoader__.load({
|
|
|
3048
3711
|
disabled: Boolean(busy) || working === 'export' || !targetLibraryId,
|
|
3049
3712
|
title: t('library.export'),
|
|
3050
3713
|
},
|
|
3051
|
-
h(Icon, { name: '
|
|
3714
|
+
h(Icon, { name: 'upload', size: 13 }),
|
|
3052
3715
|
h('span', { className: 'cait-btn-label' }, kind === 'tm' ? 'TMX' : exportFormat.toUpperCase()),
|
|
3053
3716
|
),
|
|
3717
|
+
kind === 'tm'
|
|
3718
|
+
? h(
|
|
3719
|
+
'button',
|
|
3720
|
+
{
|
|
3721
|
+
type: 'button',
|
|
3722
|
+
className: 'cait-btn',
|
|
3723
|
+
onClick: () => maintainTm('dedupe'),
|
|
3724
|
+
disabled: Boolean(busy) || !targetLibraryId,
|
|
3725
|
+
title: t('tm.maintain.dedupe'),
|
|
3726
|
+
},
|
|
3727
|
+
h(Icon, { name: 'database', size: 13 }),
|
|
3728
|
+
h('span', { className: 'cait-btn-label' }, t('tm.maintain.dedupe')),
|
|
3729
|
+
)
|
|
3730
|
+
: null,
|
|
3731
|
+
kind === 'tm'
|
|
3732
|
+
? h(
|
|
3733
|
+
'button',
|
|
3734
|
+
{
|
|
3735
|
+
type: 'button',
|
|
3736
|
+
className: 'cait-btn',
|
|
3737
|
+
onClick: () => maintainTm('prune'),
|
|
3738
|
+
disabled: Boolean(busy) || !targetLibraryId,
|
|
3739
|
+
title: t('tm.maintain.prune'),
|
|
3740
|
+
},
|
|
3741
|
+
h(Icon, { name: 'trash', size: 13 }),
|
|
3742
|
+
h('span', { className: 'cait-btn-label' }, t('tm.maintain.prune')),
|
|
3743
|
+
)
|
|
3744
|
+
: null,
|
|
3054
3745
|
h(
|
|
3055
3746
|
'button',
|
|
3056
3747
|
{
|
|
@@ -3061,6 +3752,7 @@ window.__ModuleLoader__.load({
|
|
|
3061
3752
|
title: t('library.clear'),
|
|
3062
3753
|
},
|
|
3063
3754
|
h(Icon, { name: 'trash', size: 13 }),
|
|
3755
|
+
h('span', { className: 'cait-btn-label' }, t('library.clear')),
|
|
3064
3756
|
),
|
|
3065
3757
|
h('input', {
|
|
3066
3758
|
ref: fileRef,
|
|
@@ -3313,6 +4005,7 @@ window.__ModuleLoader__.load({
|
|
|
3313
4005
|
onChange: () => toggleTerm(at),
|
|
3314
4006
|
}),
|
|
3315
4007
|
h('span', { className: 'cait-termname' }, candidate.term),
|
|
4008
|
+
candidate.proper ? h('span', { className: 'cait-originchip' }, t('term.proper')) : null,
|
|
3316
4009
|
h('span', { className: 'cait-termcount' }, `×${candidate.count}`),
|
|
3317
4010
|
candidate.inLibrary ? h('span', { className: 'cait-originchip' }, t('term.inLibrary')) : null,
|
|
3318
4011
|
h('input', {
|
|
@@ -3330,7 +4023,7 @@ window.__ModuleLoader__.load({
|
|
|
3330
4023
|
)
|
|
3331
4024
|
}
|
|
3332
4025
|
|
|
3333
|
-
function ExportModal({ t, busy, onClose, onExport, qa }) {
|
|
4026
|
+
function ExportModal({ t, busy, onClose, onExport, onImportReview, qa }) {
|
|
3334
4027
|
const issues = qa?.issueCount || 0
|
|
3335
4028
|
const items = [
|
|
3336
4029
|
['bilingual-md', t('modal.export.bilingualMd'), t('modal.export.bilingual'), 'file'],
|
|
@@ -3339,11 +4032,20 @@ window.__ModuleLoader__.load({
|
|
|
3339
4032
|
['target-txt', t('modal.export.targetTxt'), t('modal.export.target'), 'download'],
|
|
3340
4033
|
['target-md', t('modal.export.targetMd'), t('modal.export.target'), 'download'],
|
|
3341
4034
|
['docx', t('modal.export.docx'), t('modal.export.docxGroup'), 'file'],
|
|
4035
|
+
['bilingual-docx', t('modal.export.bilingualDocx'), t('modal.export.bilingual'), 'file'],
|
|
3342
4036
|
['epub', t('modal.export.epub'), t('modal.export.epubGroup'), 'file'],
|
|
3343
4037
|
['srt', t('modal.export.srt'), t('modal.export.subtitleGroup'), 'file'],
|
|
3344
4038
|
['vtt', t('modal.export.vtt'), t('modal.export.subtitleGroup'), 'file'],
|
|
3345
4039
|
['po', t('modal.export.po'), t('modal.export.localizationGroup'), 'file'],
|
|
4040
|
+
['apple-strings', t('modal.export.appleStrings'), t('modal.export.localizationGroup'), 'file'],
|
|
4041
|
+
['apple-stringsdict', t('modal.export.appleStringsdict'), t('modal.export.localizationGroup'), 'file'],
|
|
4042
|
+
['android', t('modal.export.android'), t('modal.export.localizationGroup'), 'file'],
|
|
4043
|
+
['resx', t('modal.export.resx'), t('modal.export.localizationGroup'), 'file'],
|
|
4044
|
+
['arb', t('modal.export.arb'), t('modal.export.localizationGroup'), 'file'],
|
|
4045
|
+
['yaml', t('modal.export.yaml'), t('modal.export.localizationGroup'), 'file'],
|
|
4046
|
+
['properties', t('modal.export.properties'), t('modal.export.localizationGroup'), 'file'],
|
|
3346
4047
|
['xliff', t('modal.export.xliff'), t('modal.export.localizationGroup'), 'file'],
|
|
4048
|
+
['xliff-review', t('modal.export.xliffReview'), t('modal.export.localizationGroup'), 'file'],
|
|
3347
4049
|
['xliff2', t('modal.export.xliff2'), t('modal.export.localizationGroup'), 'file'],
|
|
3348
4050
|
['sdlxliff', t('modal.export.sdlxliff'), t('modal.export.localizationGroup'), 'file'],
|
|
3349
4051
|
['html', t('modal.export.html'), t('modal.export.webGroup'), 'file'],
|
|
@@ -3386,6 +4088,30 @@ window.__ModuleLoader__.load({
|
|
|
3386
4088
|
),
|
|
3387
4089
|
),
|
|
3388
4090
|
),
|
|
4091
|
+
onImportReview
|
|
4092
|
+
? h(
|
|
4093
|
+
'div',
|
|
4094
|
+
{ className: 'cait-review-import' },
|
|
4095
|
+
h('p', { className: 'cait-libnote' }, t('modal.export.reviewImportHint')),
|
|
4096
|
+
h(
|
|
4097
|
+
'label',
|
|
4098
|
+
{ className: 'cait-btn' },
|
|
4099
|
+
h(Icon, { name: 'upload', size: 13 }),
|
|
4100
|
+
t('modal.export.reviewImport'),
|
|
4101
|
+
h('input', {
|
|
4102
|
+
type: 'file',
|
|
4103
|
+
accept: '.xliff,.xlf,application/x-xliff+xml',
|
|
4104
|
+
style: { display: 'none' },
|
|
4105
|
+
disabled: Boolean(busy),
|
|
4106
|
+
onChange: (event) => {
|
|
4107
|
+
const file = event.target.files?.[0]
|
|
4108
|
+
if (file) onImportReview(file)
|
|
4109
|
+
event.target.value = ''
|
|
4110
|
+
},
|
|
4111
|
+
}),
|
|
4112
|
+
),
|
|
4113
|
+
)
|
|
4114
|
+
: null,
|
|
3389
4115
|
)
|
|
3390
4116
|
}
|
|
3391
4117
|
|
|
@@ -3406,13 +4132,23 @@ window.__ModuleLoader__.load({
|
|
|
3406
4132
|
model: settings.mt.model,
|
|
3407
4133
|
temperature: settings.mt.temperature,
|
|
3408
4134
|
},
|
|
4135
|
+
review: {
|
|
4136
|
+
provider: settings.review?.provider || '',
|
|
4137
|
+
model: settings.review?.model || '',
|
|
4138
|
+
temperature: settings.review?.temperature ?? 0,
|
|
4139
|
+
},
|
|
3409
4140
|
})
|
|
3410
4141
|
const routes = Array.isArray(mtRoutes?.providers) ? mtRoutes.providers : []
|
|
3411
4142
|
const route = routes.find((item) => item.id === form.mt.provider)
|
|
3412
4143
|
const models = Array.isArray(route?.models) ? route.models : []
|
|
3413
4144
|
const routeMissing = form.mt.provider !== 'mock' && !route
|
|
4145
|
+
const reviewRoute = routes.find((item) => item.id === form.review.provider)
|
|
4146
|
+
const reviewModels = Array.isArray(reviewRoute?.models) ? reviewRoute.models : []
|
|
4147
|
+
const reviewInherited = !form.review.provider
|
|
3414
4148
|
|
|
3415
4149
|
const setMt = (patch) => setForm((current) => ({ ...current, mt: { ...current.mt, ...patch } }))
|
|
4150
|
+
const setReview = (patch) =>
|
|
4151
|
+
setForm((current) => ({ ...current, review: { ...current.review, ...patch } }))
|
|
3416
4152
|
const setPre = (patch) =>
|
|
3417
4153
|
setForm((current) => ({ ...current, preprocess: { ...current.preprocess, ...patch } }))
|
|
3418
4154
|
const selectProvider = (provider) => {
|
|
@@ -3430,6 +4166,18 @@ window.__ModuleLoader__.load({
|
|
|
3430
4166
|
}))
|
|
3431
4167
|
}
|
|
3432
4168
|
|
|
4169
|
+
const selectReviewProvider = (provider) => {
|
|
4170
|
+
const nextRoute = routes.find((item) => item.id === provider)
|
|
4171
|
+
setForm((current) => ({
|
|
4172
|
+
...current,
|
|
4173
|
+
review: {
|
|
4174
|
+
...current.review,
|
|
4175
|
+
provider,
|
|
4176
|
+
model: nextRoute?.models?.[0]?.id || '',
|
|
4177
|
+
},
|
|
4178
|
+
}))
|
|
4179
|
+
}
|
|
4180
|
+
|
|
3433
4181
|
const submit = (event) => {
|
|
3434
4182
|
event.preventDefault()
|
|
3435
4183
|
const patch = {
|
|
@@ -3448,6 +4196,11 @@ window.__ModuleLoader__.load({
|
|
|
3448
4196
|
model: form.mt.model,
|
|
3449
4197
|
temperature: Number(form.mt.temperature),
|
|
3450
4198
|
},
|
|
4199
|
+
review: {
|
|
4200
|
+
provider: form.review.provider,
|
|
4201
|
+
model: form.review.model,
|
|
4202
|
+
temperature: Number(form.review.temperature),
|
|
4203
|
+
},
|
|
3451
4204
|
}
|
|
3452
4205
|
onSave(patch)
|
|
3453
4206
|
}
|
|
@@ -3532,9 +4285,26 @@ window.__ModuleLoader__.load({
|
|
|
3532
4285
|
{ className: 'cait-settings-section' },
|
|
3533
4286
|
h('h4', { className: 'cait-settings-title' }, t('modal.settings.tm')),
|
|
3534
4287
|
h(
|
|
3535
|
-
'
|
|
4288
|
+
'div',
|
|
3536
4289
|
{ className: 'cait-field' },
|
|
3537
|
-
h(
|
|
4290
|
+
h(
|
|
4291
|
+
'div',
|
|
4292
|
+
{ className: 'cait-fieldhead' },
|
|
4293
|
+
h('span', null, `${t('modal.settings.fuzzy')} · ${fuzzyPercent(form.fuzzyThreshold)}%`),
|
|
4294
|
+
// Offered only while the slider is away from the default, so the
|
|
4295
|
+
// control never takes space in the usual case.
|
|
4296
|
+
fuzzyPercent(form.fuzzyThreshold) === fuzzyPercent(DEFAULT_FUZZY_THRESHOLD)
|
|
4297
|
+
? null
|
|
4298
|
+
: h(
|
|
4299
|
+
'button',
|
|
4300
|
+
{
|
|
4301
|
+
className: 'cait-linkbtn cait-fieldreset',
|
|
4302
|
+
type: 'button',
|
|
4303
|
+
onClick: () => setForm({ ...form, fuzzyThreshold: DEFAULT_FUZZY_THRESHOLD }),
|
|
4304
|
+
},
|
|
4305
|
+
t('modal.settings.resetDefault'),
|
|
4306
|
+
),
|
|
4307
|
+
),
|
|
3538
4308
|
h('input', {
|
|
3539
4309
|
className: 'cait-range',
|
|
3540
4310
|
type: 'range',
|
|
@@ -3544,6 +4314,7 @@ window.__ModuleLoader__.load({
|
|
|
3544
4314
|
value: form.fuzzyThreshold,
|
|
3545
4315
|
onChange: (event) => setForm({ ...form, fuzzyThreshold: event.target.value }),
|
|
3546
4316
|
}),
|
|
4317
|
+
h('p', { className: 'cait-fieldhint' }, t('modal.settings.fuzzyHint')),
|
|
3547
4318
|
),
|
|
3548
4319
|
h(
|
|
3549
4320
|
'label',
|
|
@@ -3639,20 +4410,112 @@ window.__ModuleLoader__.load({
|
|
|
3639
4410
|
),
|
|
3640
4411
|
h(
|
|
3641
4412
|
'div',
|
|
3642
|
-
{ className: 'cait-
|
|
3643
|
-
h('
|
|
3644
|
-
h(
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
4413
|
+
{ className: 'cait-settings-section' },
|
|
4414
|
+
h('h4', { className: 'cait-settings-title' }, t('modal.settings.review')),
|
|
4415
|
+
h(
|
|
4416
|
+
'label',
|
|
4417
|
+
{ className: 'cait-checkline' },
|
|
4418
|
+
h('input', {
|
|
4419
|
+
type: 'checkbox',
|
|
4420
|
+
checked: reviewInherited,
|
|
4421
|
+
onChange: (event) => {
|
|
4422
|
+
if (event.target.checked) {
|
|
4423
|
+
setForm((current) => ({
|
|
4424
|
+
...current,
|
|
4425
|
+
review: { ...current.review, provider: '', model: '' },
|
|
4426
|
+
}))
|
|
4427
|
+
return
|
|
4428
|
+
}
|
|
4429
|
+
const first = routes.find((item) => item.id !== 'mock' && item.hasModels !== false) || routes[0]
|
|
4430
|
+
setForm((current) => ({
|
|
4431
|
+
...current,
|
|
4432
|
+
review: {
|
|
4433
|
+
...current.review,
|
|
4434
|
+
provider: first?.id || '',
|
|
4435
|
+
model: first?.models?.[0]?.id || '',
|
|
4436
|
+
},
|
|
4437
|
+
}))
|
|
4438
|
+
},
|
|
4439
|
+
}),
|
|
4440
|
+
t('modal.settings.reviewInherit'),
|
|
4441
|
+
),
|
|
4442
|
+
h('p', { className: 'cait-mt-hint', style: { marginTop: 0 } }, t('modal.settings.reviewHint')),
|
|
4443
|
+
reviewInherited
|
|
4444
|
+
? null
|
|
4445
|
+
: h(
|
|
4446
|
+
'div',
|
|
4447
|
+
{ className: 'cait-grid2' },
|
|
4448
|
+
h(
|
|
4449
|
+
'label',
|
|
4450
|
+
{ className: 'cait-field' },
|
|
4451
|
+
h('span', null, t('modal.settings.reviewProvider')),
|
|
4452
|
+
h(
|
|
4453
|
+
'select',
|
|
4454
|
+
{
|
|
4455
|
+
className: 'cait-input',
|
|
4456
|
+
value: form.review.provider,
|
|
4457
|
+
onChange: (event) => selectReviewProvider(event.target.value),
|
|
4458
|
+
},
|
|
4459
|
+
routes.map((item) =>
|
|
4460
|
+
h(
|
|
4461
|
+
'option',
|
|
4462
|
+
{ key: item.id, value: item.id, disabled: item.hasModels === false },
|
|
4463
|
+
item.hasModels === false
|
|
4464
|
+
? `${item.name} (${item.id}) - ${t('modal.settings.noModels')}`
|
|
4465
|
+
: `${item.name} (${item.id})`,
|
|
4466
|
+
),
|
|
4467
|
+
),
|
|
4468
|
+
form.review.provider && !reviewRoute
|
|
4469
|
+
? h('option', { value: form.review.provider }, t('modal.settings.routeMissing'))
|
|
4470
|
+
: null,
|
|
4471
|
+
),
|
|
4472
|
+
),
|
|
4473
|
+
h(
|
|
4474
|
+
'label',
|
|
4475
|
+
{ className: 'cait-field' },
|
|
4476
|
+
h('span', null, t('modal.settings.model')),
|
|
4477
|
+
reviewModels.length
|
|
4478
|
+
? h(
|
|
4479
|
+
'select',
|
|
4480
|
+
{
|
|
4481
|
+
className: 'cait-input',
|
|
4482
|
+
value: form.review.model,
|
|
4483
|
+
onChange: (event) => setReview({ model: event.target.value }),
|
|
4484
|
+
},
|
|
4485
|
+
reviewModels.map((model) =>
|
|
4486
|
+
h('option', { key: model.id, value: model.id }, `${model.name} (${model.id})`),
|
|
4487
|
+
),
|
|
4488
|
+
)
|
|
4489
|
+
: h('input', {
|
|
4490
|
+
className: 'cait-input',
|
|
4491
|
+
value: form.review.model,
|
|
4492
|
+
readOnly: true,
|
|
4493
|
+
placeholder: t('modal.settings.noModels'),
|
|
4494
|
+
}),
|
|
4495
|
+
),
|
|
4496
|
+
),
|
|
4497
|
+
h(
|
|
4498
|
+
'p',
|
|
4499
|
+
{ className: 'cait-mt-hint', style: { marginTop: 0 } },
|
|
4500
|
+
reviewInherited ? t('modal.settings.reviewInheritedHint') : t('modal.settings.reviewCustomHint'),
|
|
4501
|
+
),
|
|
4502
|
+
),
|
|
4503
|
+
h(
|
|
4504
|
+
'div',
|
|
4505
|
+
{ className: 'cait-modal-actions' },
|
|
4506
|
+
h('button', { type: 'button', className: 'cait-btn cait-btn-ghost', onClick: onClose }, t('modal.settings.cancel')),
|
|
4507
|
+
h('button', { type: 'submit', className: 'cait-btn cait-btn-primary', disabled: Boolean(busy) }, busy === 'settings' ? h(Spinner) : null, t('modal.settings.save')),
|
|
4508
|
+
),
|
|
4509
|
+
),
|
|
4510
|
+
)
|
|
4511
|
+
}
|
|
4512
|
+
|
|
4513
|
+
function ConfirmModal({ t, state, busy, onClose, onConfirm }) {
|
|
4514
|
+
return h(
|
|
4515
|
+
Modal,
|
|
4516
|
+
{ title: state.title || t('modal.confirm.title'), onClose },
|
|
4517
|
+
h('p', { className: 'cait-confirm-text' }, state.message),
|
|
4518
|
+
h(
|
|
3656
4519
|
'div',
|
|
3657
4520
|
{ className: 'cait-modal-actions' },
|
|
3658
4521
|
h('button', { className: 'cait-btn cait-btn-ghost', onClick: onClose, disabled: Boolean(busy) }, t('modal.confirm.cancel')),
|
|
@@ -3689,6 +4552,614 @@ window.__ModuleLoader__.load({
|
|
|
3689
4552
|
)
|
|
3690
4553
|
}
|
|
3691
4554
|
|
|
4555
|
+
function BriefModal({ t, doc, busy, onClose, onSave }) {
|
|
4556
|
+
const initial = doc.brief || {}
|
|
4557
|
+
const [form, setForm] = useState({
|
|
4558
|
+
domain: initial.domain || '',
|
|
4559
|
+
audience: initial.audience || '',
|
|
4560
|
+
tone: initial.tone || '',
|
|
4561
|
+
notes: initial.notes || '',
|
|
4562
|
+
doNotTranslate: initial.doNotTranslate || '',
|
|
4563
|
+
styleSample: initial.styleSample || '',
|
|
4564
|
+
})
|
|
4565
|
+
const field = (key, label, placeholder, rows) =>
|
|
4566
|
+
h(
|
|
4567
|
+
'label',
|
|
4568
|
+
{ className: 'cait-field' },
|
|
4569
|
+
h('span', null, t(label)),
|
|
4570
|
+
rows
|
|
4571
|
+
? h('textarea', {
|
|
4572
|
+
className: 'cait-textarea',
|
|
4573
|
+
rows,
|
|
4574
|
+
value: form[key],
|
|
4575
|
+
placeholder: t(placeholder),
|
|
4576
|
+
onChange: (event) => setForm({ ...form, [key]: event.target.value }),
|
|
4577
|
+
})
|
|
4578
|
+
: h('input', {
|
|
4579
|
+
className: 'cait-input',
|
|
4580
|
+
value: form[key],
|
|
4581
|
+
placeholder: t(placeholder),
|
|
4582
|
+
onChange: (event) => setForm({ ...form, [key]: event.target.value }),
|
|
4583
|
+
}),
|
|
4584
|
+
)
|
|
4585
|
+
return h(
|
|
4586
|
+
Modal,
|
|
4587
|
+
{ title: t('brief.title'), onClose, wide: true },
|
|
4588
|
+
h('p', { className: 'cait-mt-hint', style: { marginTop: 0 } }, t('brief.hint')),
|
|
4589
|
+
h(
|
|
4590
|
+
'div',
|
|
4591
|
+
{ className: 'cait-briefform' },
|
|
4592
|
+
h('div', { className: 'cait-grid2' }, field('domain', 'brief.domain', 'brief.placeholder.domain'), field('audience', 'brief.audience', 'brief.placeholder.audience')),
|
|
4593
|
+
field('tone', 'brief.tone', 'brief.placeholder.tone'),
|
|
4594
|
+
field('notes', 'brief.notes', 'brief.placeholder.notes', 3),
|
|
4595
|
+
field('doNotTranslate', 'brief.doNotTranslate', 'brief.placeholder.doNotTranslate', 3),
|
|
4596
|
+
field('styleSample', 'brief.styleSample', 'brief.placeholder.styleSample', 4),
|
|
4597
|
+
),
|
|
4598
|
+
h(
|
|
4599
|
+
'div',
|
|
4600
|
+
{ className: 'cait-modal-actions' },
|
|
4601
|
+
h('button', { className: 'cait-btn cait-btn-ghost', onClick: onClose, disabled: Boolean(busy) }, t('modal.confirm.cancel')),
|
|
4602
|
+
h(
|
|
4603
|
+
'button',
|
|
4604
|
+
{ className: 'cait-btn cait-btn-primary', onClick: () => onSave(form), disabled: Boolean(busy) },
|
|
4605
|
+
busy ? h(Spinner) : null,
|
|
4606
|
+
t('brief.save'),
|
|
4607
|
+
),
|
|
4608
|
+
),
|
|
4609
|
+
)
|
|
4610
|
+
}
|
|
4611
|
+
|
|
4612
|
+
function NoteModal({ t, doc, index, segment, notes, busy, onAdd, onUpdate, onDelete, onClose }) {
|
|
4613
|
+
const [text, setText] = useState('')
|
|
4614
|
+
const [editingId, setEditingId] = useState('')
|
|
4615
|
+
const [editingText, setEditingText] = useState('')
|
|
4616
|
+
const list = Array.isArray(notes) ? notes : []
|
|
4617
|
+
const submit = () => {
|
|
4618
|
+
const value = text.trim()
|
|
4619
|
+
if (!value || busy) return
|
|
4620
|
+
onAdd(value)
|
|
4621
|
+
setText('')
|
|
4622
|
+
}
|
|
4623
|
+
return h(
|
|
4624
|
+
Modal,
|
|
4625
|
+
{ title: `${t('notes.title')} · #${index + 1}`, onClose, wide: true },
|
|
4626
|
+
h('p', { className: 'cait-mt-hint', style: { marginTop: 0 } }, t('notes.hint')),
|
|
4627
|
+
h('div', { className: 'cait-notelist' }, segment?.source || ''),
|
|
4628
|
+
list.length
|
|
4629
|
+
? h(
|
|
4630
|
+
'div',
|
|
4631
|
+
{ className: 'cait-notelist' },
|
|
4632
|
+
list.map((note) =>
|
|
4633
|
+
h(
|
|
4634
|
+
'div',
|
|
4635
|
+
{ className: 'cait-noteitem', key: note.id },
|
|
4636
|
+
h(
|
|
4637
|
+
'div',
|
|
4638
|
+
{ className: 'cait-notehead' },
|
|
4639
|
+
h('span', { className: 'cait-noteauthor' }, note.author === 'agent' ? 'Agent' : t('notes.user')),
|
|
4640
|
+
h('span', { className: 'cait-notetime' }, String(note.createdAt || '').replace('T', ' ').slice(0, 16)),
|
|
4641
|
+
),
|
|
4642
|
+
editingId === note.id
|
|
4643
|
+
? h(
|
|
4644
|
+
'div',
|
|
4645
|
+
null,
|
|
4646
|
+
h('textarea', {
|
|
4647
|
+
className: 'cait-input cait-textarea',
|
|
4648
|
+
value: editingText,
|
|
4649
|
+
rows: 3,
|
|
4650
|
+
onChange: (event) => setEditingText(event.target.value),
|
|
4651
|
+
}),
|
|
4652
|
+
h(
|
|
4653
|
+
'div',
|
|
4654
|
+
{ className: 'cait-noteactions' },
|
|
4655
|
+
h('button', { className: 'cait-btn cait-btn-ghost', onClick: () => setEditingId('') }, t('notes.cancel')),
|
|
4656
|
+
h(
|
|
4657
|
+
'button',
|
|
4658
|
+
{
|
|
4659
|
+
className: 'cait-btn cait-btn-primary',
|
|
4660
|
+
disabled: Boolean(busy) || !editingText.trim(),
|
|
4661
|
+
onClick: () => {
|
|
4662
|
+
onUpdate(note.id, editingText)
|
|
4663
|
+
setEditingId('')
|
|
4664
|
+
},
|
|
4665
|
+
},
|
|
4666
|
+
t('notes.save'),
|
|
4667
|
+
),
|
|
4668
|
+
),
|
|
4669
|
+
)
|
|
4670
|
+
: h(
|
|
4671
|
+
'div',
|
|
4672
|
+
null,
|
|
4673
|
+
h('div', { className: 'cait-notetext' }, note.text),
|
|
4674
|
+
h(
|
|
4675
|
+
'div',
|
|
4676
|
+
{ className: 'cait-noteactions' },
|
|
4677
|
+
h(
|
|
4678
|
+
'button',
|
|
4679
|
+
{
|
|
4680
|
+
className: 'cait-minibtn',
|
|
4681
|
+
onClick: () => {
|
|
4682
|
+
setEditingId(note.id)
|
|
4683
|
+
setEditingText(note.text)
|
|
4684
|
+
},
|
|
4685
|
+
},
|
|
4686
|
+
t('notes.edit'),
|
|
4687
|
+
),
|
|
4688
|
+
h(
|
|
4689
|
+
'button',
|
|
4690
|
+
{ className: 'cait-minibtn cait-danger', onClick: () => onDelete(note.id) },
|
|
4691
|
+
t('notes.delete'),
|
|
4692
|
+
),
|
|
4693
|
+
),
|
|
4694
|
+
),
|
|
4695
|
+
),
|
|
4696
|
+
),
|
|
4697
|
+
)
|
|
4698
|
+
: h('p', { className: 'cait-mt-hint' }, t('notes.empty')),
|
|
4699
|
+
h(
|
|
4700
|
+
'div',
|
|
4701
|
+
{ className: 'cait-noteadd' },
|
|
4702
|
+
h('textarea', {
|
|
4703
|
+
className: 'cait-input cait-textarea',
|
|
4704
|
+
value: text,
|
|
4705
|
+
rows: 3,
|
|
4706
|
+
placeholder: t('notes.placeholder'),
|
|
4707
|
+
onChange: (event) => setText(event.target.value),
|
|
4708
|
+
}),
|
|
4709
|
+
h(
|
|
4710
|
+
'div',
|
|
4711
|
+
{ className: 'cait-noteactions' },
|
|
4712
|
+
h('button', { className: 'cait-btn cait-btn-primary', disabled: Boolean(busy) || !text.trim(), onClick: submit }, busy ? h(Spinner) : null, t('notes.add')),
|
|
4713
|
+
),
|
|
4714
|
+
),
|
|
4715
|
+
)
|
|
4716
|
+
}
|
|
4717
|
+
|
|
4718
|
+
function ReplaceModal({ t, doc, busy, preview, undoCount, onPreview, onApply, onUndo, onClose }) {
|
|
4719
|
+
const queryRef = useRef(null)
|
|
4720
|
+
const [form, setForm] = useState({
|
|
4721
|
+
query: '',
|
|
4722
|
+
replacement: '',
|
|
4723
|
+
scope: 'target',
|
|
4724
|
+
matchCase: false,
|
|
4725
|
+
regex: false,
|
|
4726
|
+
})
|
|
4727
|
+
useEffect(() => {
|
|
4728
|
+
if (queryRef.current) queryRef.current.focus()
|
|
4729
|
+
}, [])
|
|
4730
|
+
const set = (key, value) => setForm((current) => ({ ...current, [key]: value }))
|
|
4731
|
+
return h(
|
|
4732
|
+
Modal,
|
|
4733
|
+
{ title: t('replace.title'), onClose, wide: true },
|
|
4734
|
+
h('p', { className: 'cait-mt-hint', style: { marginTop: 0 } }, t('replace.hint')),
|
|
4735
|
+
h(
|
|
4736
|
+
'div',
|
|
4737
|
+
{ className: 'cait-replaceform' },
|
|
4738
|
+
h(
|
|
4739
|
+
'label',
|
|
4740
|
+
{ className: 'cait-field' },
|
|
4741
|
+
h('span', null, t('replace.query')),
|
|
4742
|
+
h('input', {
|
|
4743
|
+
ref: queryRef,
|
|
4744
|
+
className: 'cait-input',
|
|
4745
|
+
value: form.query,
|
|
4746
|
+
placeholder: t('replace.queryPlaceholder'),
|
|
4747
|
+
onChange: (event) => set('query', event.target.value),
|
|
4748
|
+
}),
|
|
4749
|
+
),
|
|
4750
|
+
h(
|
|
4751
|
+
'label',
|
|
4752
|
+
{ className: 'cait-field' },
|
|
4753
|
+
h('span', null, t('replace.replacement')),
|
|
4754
|
+
h('input', {
|
|
4755
|
+
className: 'cait-input',
|
|
4756
|
+
value: form.replacement,
|
|
4757
|
+
placeholder: t('replace.replacementPlaceholder'),
|
|
4758
|
+
onChange: (event) => set('replacement', event.target.value),
|
|
4759
|
+
}),
|
|
4760
|
+
),
|
|
4761
|
+
h(
|
|
4762
|
+
'div',
|
|
4763
|
+
{ className: 'cait-replaceoptions' },
|
|
4764
|
+
h(
|
|
4765
|
+
'label',
|
|
4766
|
+
{ className: 'cait-field' },
|
|
4767
|
+
h('span', null, t('replace.scope')),
|
|
4768
|
+
h(
|
|
4769
|
+
'select',
|
|
4770
|
+
{
|
|
4771
|
+
className: 'cait-input',
|
|
4772
|
+
value: form.scope,
|
|
4773
|
+
onChange: (event) => set('scope', event.target.value),
|
|
4774
|
+
},
|
|
4775
|
+
h('option', { value: 'target' }, t('replace.scope.target')),
|
|
4776
|
+
h('option', { value: 'source' }, t('replace.scope.source')),
|
|
4777
|
+
h('option', { value: 'both' }, t('replace.scope.both')),
|
|
4778
|
+
),
|
|
4779
|
+
),
|
|
4780
|
+
h(
|
|
4781
|
+
'label',
|
|
4782
|
+
{ className: 'cait-check' },
|
|
4783
|
+
h('input', {
|
|
4784
|
+
type: 'checkbox',
|
|
4785
|
+
checked: form.matchCase,
|
|
4786
|
+
onChange: (event) => set('matchCase', event.target.checked),
|
|
4787
|
+
}),
|
|
4788
|
+
t('replace.matchCase'),
|
|
4789
|
+
),
|
|
4790
|
+
h(
|
|
4791
|
+
'label',
|
|
4792
|
+
{ className: 'cait-check' },
|
|
4793
|
+
h('input', {
|
|
4794
|
+
type: 'checkbox',
|
|
4795
|
+
checked: form.regex,
|
|
4796
|
+
onChange: (event) => set('regex', event.target.checked),
|
|
4797
|
+
}),
|
|
4798
|
+
t('replace.regex'),
|
|
4799
|
+
),
|
|
4800
|
+
),
|
|
4801
|
+
),
|
|
4802
|
+
preview
|
|
4803
|
+
? h(
|
|
4804
|
+
'div',
|
|
4805
|
+
{ className: 'cait-replaceresults' },
|
|
4806
|
+
h(
|
|
4807
|
+
'div',
|
|
4808
|
+
{ className: 'cait-replacecounts' },
|
|
4809
|
+
t('replace.results')
|
|
4810
|
+
.replace('{count}', String(preview.total))
|
|
4811
|
+
.replace('{segments}', String(preview.matchedSegments || 0)),
|
|
4812
|
+
),
|
|
4813
|
+
preview.truncated ? h('p', { className: 'cait-mt-hint' }, t('replace.truncated')) : null,
|
|
4814
|
+
preview.matches?.length
|
|
4815
|
+
? h(
|
|
4816
|
+
'div',
|
|
4817
|
+
{ className: 'cait-replacelist' },
|
|
4818
|
+
preview.matches.slice(0, 50).map((match, index) =>
|
|
4819
|
+
h(
|
|
4820
|
+
'div',
|
|
4821
|
+
{ className: 'cait-replaceitem', key: `${match.uid}-${match.field}-${index}` },
|
|
4822
|
+
h(
|
|
4823
|
+
'div',
|
|
4824
|
+
{ className: 'cait-replacehead' },
|
|
4825
|
+
h('span', null, `#${match.index + 1}`),
|
|
4826
|
+
h('span', { className: 'cait-replacefield' }, t(`replace.field.${match.field}`)),
|
|
4827
|
+
h('span', { className: 'cait-replacecount' }, `×${match.count}`),
|
|
4828
|
+
),
|
|
4829
|
+
h('div', { className: 'cait-replacebefore' }, match.before),
|
|
4830
|
+
h('div', { className: 'cait-replaceafter' }, match.after),
|
|
4831
|
+
),
|
|
4832
|
+
),
|
|
4833
|
+
)
|
|
4834
|
+
: h('p', { className: 'cait-mt-hint' }, t('replace.noResults')),
|
|
4835
|
+
)
|
|
4836
|
+
: null,
|
|
4837
|
+
h(
|
|
4838
|
+
'div',
|
|
4839
|
+
{ className: 'cait-modal-actions' },
|
|
4840
|
+
h('button', { className: 'cait-btn cait-btn-ghost', onClick: onClose, disabled: Boolean(busy) }, t('modal.confirm.cancel')),
|
|
4841
|
+
undoCount
|
|
4842
|
+
? h('button', { className: 'cait-btn', onClick: onUndo, disabled: Boolean(busy) }, t('replace.undo'))
|
|
4843
|
+
: null,
|
|
4844
|
+
h('button', { className: 'cait-btn', onClick: () => onPreview(form), disabled: Boolean(busy) || !form.query }, t('replace.preview')),
|
|
4845
|
+
h(
|
|
4846
|
+
'button',
|
|
4847
|
+
{ className: 'cait-btn cait-btn-primary', onClick: () => onApply(form), disabled: Boolean(busy) || !form.query || !preview?.total },
|
|
4848
|
+
busy ? h(Spinner) : null,
|
|
4849
|
+
t('replace.apply'),
|
|
4850
|
+
),
|
|
4851
|
+
),
|
|
4852
|
+
)
|
|
4853
|
+
}
|
|
4854
|
+
|
|
4855
|
+
function CommandPalette({ t, items, onClose, onRun }) {
|
|
4856
|
+
const [query, setQuery] = useState('')
|
|
4857
|
+
const [active, setActive] = useState(0)
|
|
4858
|
+
const inputRef = useRef(null)
|
|
4859
|
+
useEffect(() => {
|
|
4860
|
+
if (inputRef.current) inputRef.current.focus()
|
|
4861
|
+
}, [])
|
|
4862
|
+
const needle = query.trim().toLowerCase()
|
|
4863
|
+
const filtered = items.filter((item) => {
|
|
4864
|
+
if (!needle) return true
|
|
4865
|
+
return `${item.title} ${item.hint || ''}`.toLowerCase().includes(needle)
|
|
4866
|
+
})
|
|
4867
|
+
useEffect(() => {
|
|
4868
|
+
setActive(0)
|
|
4869
|
+
}, [query])
|
|
4870
|
+
const choose = (item) => {
|
|
4871
|
+
if (item) onRun(item)
|
|
4872
|
+
}
|
|
4873
|
+
return h(
|
|
4874
|
+
Modal,
|
|
4875
|
+
{ title: t('palette.title'), onClose, wide: true },
|
|
4876
|
+
h('input', {
|
|
4877
|
+
ref: inputRef,
|
|
4878
|
+
className: 'cait-input cait-palette-input',
|
|
4879
|
+
value: query,
|
|
4880
|
+
placeholder: t('palette.placeholder'),
|
|
4881
|
+
onChange: (event) => setQuery(event.target.value),
|
|
4882
|
+
onKeyDown: (event) => {
|
|
4883
|
+
if (event.key === 'ArrowDown') {
|
|
4884
|
+
event.preventDefault()
|
|
4885
|
+
setActive((value) => Math.min(value + 1, Math.max(0, filtered.length - 1)))
|
|
4886
|
+
} else if (event.key === 'ArrowUp') {
|
|
4887
|
+
event.preventDefault()
|
|
4888
|
+
setActive((value) => Math.max(value - 1, 0))
|
|
4889
|
+
} else if (event.key === 'Enter') {
|
|
4890
|
+
event.preventDefault()
|
|
4891
|
+
choose(filtered[active])
|
|
4892
|
+
}
|
|
4893
|
+
},
|
|
4894
|
+
}),
|
|
4895
|
+
h(
|
|
4896
|
+
'div',
|
|
4897
|
+
{ className: 'cait-palette-list' },
|
|
4898
|
+
filtered.length
|
|
4899
|
+
? filtered.map((item, index) =>
|
|
4900
|
+
h(
|
|
4901
|
+
'button',
|
|
4902
|
+
{
|
|
4903
|
+
type: 'button',
|
|
4904
|
+
key: item.id,
|
|
4905
|
+
className: `cait-palette-item${index === active ? ' active' : ''}`,
|
|
4906
|
+
onMouseEnter: () => setActive(index),
|
|
4907
|
+
onClick: () => choose(item),
|
|
4908
|
+
},
|
|
4909
|
+
h('span', { className: 'cait-palette-label' }, item.title),
|
|
4910
|
+
item.hint ? h('span', { className: 'cait-palette-hint' }, item.hint) : null,
|
|
4911
|
+
),
|
|
4912
|
+
)
|
|
4913
|
+
: h('p', { className: 'cait-mt-hint' }, t('palette.empty')),
|
|
4914
|
+
),
|
|
4915
|
+
h('p', { className: 'cait-palette-foot' }, t('palette.footer')),
|
|
4916
|
+
)
|
|
4917
|
+
}
|
|
4918
|
+
|
|
4919
|
+
function ResourceModal({ t, doc, libraries, mounts, busy, onClose, onSave }) {
|
|
4920
|
+
const resources = doc.resources || {}
|
|
4921
|
+
const [inheritTm, setInheritTm] = useState(resources.inheritsTm !== false)
|
|
4922
|
+
const [inheritGlossary, setInheritGlossary] = useState(resources.inheritsGlossary !== false)
|
|
4923
|
+
const [tmIds, setTmIds] = useState(
|
|
4924
|
+
() => new Set(resources.tmIds ?? resources.effectiveTmIds ?? []),
|
|
4925
|
+
)
|
|
4926
|
+
const [glossaryIds, setGlossaryIds] = useState(
|
|
4927
|
+
() => new Set(resources.glossaryIds ?? resources.effectiveGlossaryIds ?? []),
|
|
4928
|
+
)
|
|
4929
|
+
const [defaultWriteTmId, setDefaultWriteTmId] = useState(resources.defaultWriteTmId || '')
|
|
4930
|
+
|
|
4931
|
+
const allTm = libraries?.tm || []
|
|
4932
|
+
const allGlossary = libraries?.glossary || []
|
|
4933
|
+
const selectedTm = inheritTm ? mounts?.tmIds || [] : Array.from(tmIds)
|
|
4934
|
+
const selectedGlossary = inheritGlossary ? mounts?.glossaryIds || [] : Array.from(glossaryIds)
|
|
4935
|
+
const tmChoices = allTm.filter((library) => selectedTm.includes(library.tmId))
|
|
4936
|
+
const checkedTmIds = new Set(selectedTm)
|
|
4937
|
+
const checkedGlossaryIds = new Set(selectedGlossary)
|
|
4938
|
+
const defaultTm = tmChoices.some((library) => library.tmId === defaultWriteTmId)
|
|
4939
|
+
? defaultWriteTmId
|
|
4940
|
+
: ''
|
|
4941
|
+
|
|
4942
|
+
const toggleId = (setter, id) => {
|
|
4943
|
+
setter((current) => {
|
|
4944
|
+
const next = new Set(current)
|
|
4945
|
+
if (next.has(id)) next.delete(id)
|
|
4946
|
+
else next.add(id)
|
|
4947
|
+
return next
|
|
4948
|
+
})
|
|
4949
|
+
}
|
|
4950
|
+
|
|
4951
|
+
const librarySection = (label, all, idKey, checkedIds, inherit, setInherit, toggle, inheritLabel) =>
|
|
4952
|
+
h(
|
|
4953
|
+
'div',
|
|
4954
|
+
{ className: 'cait-resource-section' },
|
|
4955
|
+
h(
|
|
4956
|
+
'div',
|
|
4957
|
+
{ className: 'cait-resource-head' },
|
|
4958
|
+
h('strong', null, t(label)),
|
|
4959
|
+
h(
|
|
4960
|
+
'label',
|
|
4961
|
+
{ className: 'cait-checkline' },
|
|
4962
|
+
h('input', {
|
|
4963
|
+
type: 'checkbox',
|
|
4964
|
+
checked: inherit,
|
|
4965
|
+
onChange: (event) => setInherit(event.target.checked),
|
|
4966
|
+
}),
|
|
4967
|
+
t(inheritLabel),
|
|
4968
|
+
),
|
|
4969
|
+
),
|
|
4970
|
+
h(
|
|
4971
|
+
'div',
|
|
4972
|
+
{ className: 'cait-resource-list' },
|
|
4973
|
+
all.length === 0
|
|
4974
|
+
? h('p', { className: 'cait-meta' }, t('resources.empty'))
|
|
4975
|
+
: all.map((library) =>
|
|
4976
|
+
h(
|
|
4977
|
+
'label',
|
|
4978
|
+
{ key: library[idKey], className: 'cait-resource-row' },
|
|
4979
|
+
h('input', {
|
|
4980
|
+
type: 'checkbox',
|
|
4981
|
+
checked: checkedIds.has(library[idKey]),
|
|
4982
|
+
disabled: inherit,
|
|
4983
|
+
onChange: () => toggle(library[idKey]),
|
|
4984
|
+
}),
|
|
4985
|
+
h('span', { className: 'cait-resource-name' }, library.name),
|
|
4986
|
+
h('span', { className: 'cait-meta' }, library[idKey]),
|
|
4987
|
+
),
|
|
4988
|
+
),
|
|
4989
|
+
),
|
|
4990
|
+
)
|
|
4991
|
+
|
|
4992
|
+
return h(
|
|
4993
|
+
Modal,
|
|
4994
|
+
{ title: t('resources.title'), onClose, wide: true },
|
|
4995
|
+
h('p', { className: 'cait-mt-hint', style: { marginTop: 0 } }, t('resources.hint')),
|
|
4996
|
+
h(
|
|
4997
|
+
'div',
|
|
4998
|
+
{ className: 'cait-briefform' },
|
|
4999
|
+
librarySection(
|
|
5000
|
+
'resources.tm',
|
|
5001
|
+
allTm,
|
|
5002
|
+
'tmId',
|
|
5003
|
+
checkedTmIds,
|
|
5004
|
+
inheritTm,
|
|
5005
|
+
setInheritTm,
|
|
5006
|
+
(id) => toggleId(setTmIds, id),
|
|
5007
|
+
'resources.inheritTm',
|
|
5008
|
+
),
|
|
5009
|
+
librarySection(
|
|
5010
|
+
'resources.glossary',
|
|
5011
|
+
allGlossary,
|
|
5012
|
+
'glossaryId',
|
|
5013
|
+
checkedGlossaryIds,
|
|
5014
|
+
inheritGlossary,
|
|
5015
|
+
setInheritGlossary,
|
|
5016
|
+
(id) => toggleId(setGlossaryIds, id),
|
|
5017
|
+
'resources.inheritGlossary',
|
|
5018
|
+
),
|
|
5019
|
+
h(
|
|
5020
|
+
'label',
|
|
5021
|
+
{ className: 'cait-field' },
|
|
5022
|
+
h('span', null, t('resources.defaultWrite')),
|
|
5023
|
+
h(
|
|
5024
|
+
'select',
|
|
5025
|
+
{
|
|
5026
|
+
className: 'cait-input',
|
|
5027
|
+
value: defaultTm,
|
|
5028
|
+
onChange: (event) => setDefaultWriteTmId(event.target.value),
|
|
5029
|
+
},
|
|
5030
|
+
h('option', { value: '' }, t(inheritTm ? 'resources.followDefault' : 'resources.firstBound')),
|
|
5031
|
+
tmChoices.map((library) => h('option', { key: library.tmId, value: library.tmId }, library.name)),
|
|
5032
|
+
),
|
|
5033
|
+
),
|
|
5034
|
+
),
|
|
5035
|
+
h(
|
|
5036
|
+
'div',
|
|
5037
|
+
{ className: 'cait-modal-actions' },
|
|
5038
|
+
h('button', { className: 'cait-btn cait-btn-ghost', onClick: onClose, disabled: Boolean(busy) }, t('modal.confirm.cancel')),
|
|
5039
|
+
h(
|
|
5040
|
+
'button',
|
|
5041
|
+
{
|
|
5042
|
+
className: 'cait-btn cait-btn-primary',
|
|
5043
|
+
disabled: Boolean(busy),
|
|
5044
|
+
onClick: () =>
|
|
5045
|
+
onSave({
|
|
5046
|
+
tmIds: inheritTm ? null : Array.from(tmIds),
|
|
5047
|
+
glossaryIds: inheritGlossary ? null : Array.from(glossaryIds),
|
|
5048
|
+
defaultWriteTmId: defaultTm || null,
|
|
5049
|
+
}),
|
|
5050
|
+
},
|
|
5051
|
+
busy ? h(Spinner) : null,
|
|
5052
|
+
t('resources.save'),
|
|
5053
|
+
),
|
|
5054
|
+
),
|
|
5055
|
+
)
|
|
5056
|
+
}
|
|
5057
|
+
|
|
5058
|
+
function AnalysisModal({ t, doc, analysis, busy, onClose }) {
|
|
5059
|
+
const download = (format) => {
|
|
5060
|
+
if (!analysis) return
|
|
5061
|
+
const pairs = [
|
|
5062
|
+
['segments', analysis.segments],
|
|
5063
|
+
['words', analysis.words],
|
|
5064
|
+
['characters', analysis.characters],
|
|
5065
|
+
['duplicates.groups', analysis.duplicates.groups],
|
|
5066
|
+
['duplicates.segments', analysis.duplicates.segments],
|
|
5067
|
+
['translated', analysis.translated],
|
|
5068
|
+
['confirmed', analysis.confirmed],
|
|
5069
|
+
['ice', analysis.buckets.ice],
|
|
5070
|
+
['exact', analysis.buckets.exact],
|
|
5071
|
+
['fuzzy85', analysis.buckets.fuzzy85],
|
|
5072
|
+
['fuzzy70', analysis.buckets.fuzzy70],
|
|
5073
|
+
['new', analysis.buckets.new],
|
|
5074
|
+
['matched.segments', analysis.matched.segments],
|
|
5075
|
+
['matched.percent', analysis.matched.percent],
|
|
5076
|
+
['leverage.percent', analysis.leverage.percent],
|
|
5077
|
+
['generatedAt', analysis.generatedAt],
|
|
5078
|
+
]
|
|
5079
|
+
const content =
|
|
5080
|
+
format === 'json'
|
|
5081
|
+
? JSON.stringify(analysis, null, 2)
|
|
5082
|
+
: ['metric,value', ...pairs.map(([key, value]) => `${key},${value}`)].join('\n')
|
|
5083
|
+
const type = format === 'json' ? 'application/json' : 'text/csv'
|
|
5084
|
+
const blob = new Blob([content], { type })
|
|
5085
|
+
const url = URL.createObjectURL(blob)
|
|
5086
|
+
const anchor = document.createElement('a')
|
|
5087
|
+
anchor.href = url
|
|
5088
|
+
anchor.download = `${analysis.name || 'document'}-analysis.${format}`
|
|
5089
|
+
anchor.click()
|
|
5090
|
+
URL.revokeObjectURL(url)
|
|
5091
|
+
}
|
|
5092
|
+
|
|
5093
|
+
const rows = analysis
|
|
5094
|
+
? [
|
|
5095
|
+
['analysis.segments', analysis.segments],
|
|
5096
|
+
['analysis.words', analysis.words],
|
|
5097
|
+
['analysis.characters', analysis.characters],
|
|
5098
|
+
['analysis.duplicates', `${analysis.duplicates.segments} / ${analysis.duplicates.groups}`],
|
|
5099
|
+
['analysis.translated', analysis.translated],
|
|
5100
|
+
['analysis.confirmed', analysis.confirmed],
|
|
5101
|
+
]
|
|
5102
|
+
: []
|
|
5103
|
+
const buckets = analysis
|
|
5104
|
+
? [
|
|
5105
|
+
['analysis.ice', analysis.buckets.ice],
|
|
5106
|
+
['analysis.exact', analysis.buckets.exact],
|
|
5107
|
+
['analysis.fuzzy85', analysis.buckets.fuzzy85],
|
|
5108
|
+
['analysis.fuzzy70', analysis.buckets.fuzzy70],
|
|
5109
|
+
['analysis.new', analysis.buckets.new],
|
|
5110
|
+
]
|
|
5111
|
+
: []
|
|
5112
|
+
|
|
5113
|
+
return h(
|
|
5114
|
+
Modal,
|
|
5115
|
+
{ title: t('analysis.title'), onClose, wide: true },
|
|
5116
|
+
h('p', { className: 'cait-mt-hint', style: { marginTop: 0 } }, t('analysis.hint')),
|
|
5117
|
+
busy || !analysis
|
|
5118
|
+
? h('div', { className: 'cait-empty' }, h(Spinner))
|
|
5119
|
+
: h(
|
|
5120
|
+
'div',
|
|
5121
|
+
{ className: 'cait-analysis' },
|
|
5122
|
+
h(
|
|
5123
|
+
'div',
|
|
5124
|
+
{ className: 'cait-analysis-grid' },
|
|
5125
|
+
rows.map(([label, value]) =>
|
|
5126
|
+
h(
|
|
5127
|
+
'div',
|
|
5128
|
+
{ key: label, className: 'cait-analysis-stat' },
|
|
5129
|
+
h('span', null, t(label)),
|
|
5130
|
+
h('strong', null, String(value)),
|
|
5131
|
+
),
|
|
5132
|
+
),
|
|
5133
|
+
),
|
|
5134
|
+
h(
|
|
5135
|
+
'div',
|
|
5136
|
+
{ className: 'cait-analysis-grid' },
|
|
5137
|
+
buckets.map(([label, value]) =>
|
|
5138
|
+
h(
|
|
5139
|
+
'div',
|
|
5140
|
+
{ key: label, className: 'cait-analysis-stat' },
|
|
5141
|
+
h('span', null, t(label)),
|
|
5142
|
+
h('strong', null, String(value)),
|
|
5143
|
+
),
|
|
5144
|
+
),
|
|
5145
|
+
),
|
|
5146
|
+
h(
|
|
5147
|
+
'div',
|
|
5148
|
+
{ className: 'cait-analysis-total' },
|
|
5149
|
+
h('span', null, `${t('analysis.matchRate')} ${analysis.matched.percent}%`),
|
|
5150
|
+
h('span', null, `${t('analysis.leverage')} ${analysis.leverage.percent}%`),
|
|
5151
|
+
),
|
|
5152
|
+
),
|
|
5153
|
+
h(
|
|
5154
|
+
'div',
|
|
5155
|
+
{ className: 'cait-modal-actions' },
|
|
5156
|
+
h('button', { className: 'cait-btn cait-btn-ghost', onClick: onClose, disabled: Boolean(busy) }, t('analysis.close')),
|
|
5157
|
+
h('button', { className: 'cait-btn cait-btn-ghost', onClick: () => download('csv'), disabled: Boolean(busy) || !analysis }, t('analysis.exportCsv')),
|
|
5158
|
+
h('button', { className: 'cait-btn cait-btn-primary', onClick: () => download('json'), disabled: Boolean(busy) || !analysis }, t('analysis.exportJson')),
|
|
5159
|
+
),
|
|
5160
|
+
)
|
|
5161
|
+
}
|
|
5162
|
+
|
|
3692
5163
|
function Workbench({ i18n, translate }) {
|
|
3693
5164
|
const [data, setData] = useState(null)
|
|
3694
5165
|
const [loading, setLoading] = useState(true)
|
|
@@ -3712,6 +5183,18 @@ window.__ModuleLoader__.load({
|
|
|
3712
5183
|
}
|
|
3713
5184
|
const [pre, setPre] = useState({ running: false, done: 0, total: 0, filled: 0, failed: 0 })
|
|
3714
5185
|
const [qa, setQa] = useState(null)
|
|
5186
|
+
const [review, setReview] = useState(null)
|
|
5187
|
+
const [reviewBusy, setReviewBusy] = useState(false)
|
|
5188
|
+
const [analysis, setAnalysis] = useState(null)
|
|
5189
|
+
const [analysisBusy, setAnalysisBusy] = useState(false)
|
|
5190
|
+
const [replacePreview, setReplacePreview] = useState(null)
|
|
5191
|
+
const [replaceUndo, setReplaceUndo] = useState(null)
|
|
5192
|
+
const [replaceBusy, setReplaceBusy] = useState(false)
|
|
5193
|
+
const [notes, setNotes] = useState(null)
|
|
5194
|
+
const [notesBusy, setNotesBusy] = useState(false)
|
|
5195
|
+
const [concordance, setConcordance] = useState(null)
|
|
5196
|
+
const [concordanceQuery, setConcordanceQuery] = useState('')
|
|
5197
|
+
const [concordanceBusy, setConcordanceBusy] = useState(false)
|
|
3715
5198
|
const [tmQuery, setTmQuery] = useState('')
|
|
3716
5199
|
const [tmResults, setTmResults] = useState(null)
|
|
3717
5200
|
const [tmRefresh, setTmRefresh] = useState(0)
|
|
@@ -3728,16 +5211,20 @@ window.__ModuleLoader__.load({
|
|
|
3728
5211
|
const busyRef = useRef(false)
|
|
3729
5212
|
const rootRef = useRef(null)
|
|
3730
5213
|
const unitRef = useRef(0)
|
|
5214
|
+
const draftHistoryRef = useRef({ index: -1, stack: [], cursor: 0, lastAt: 0 })
|
|
3731
5215
|
|
|
3732
5216
|
const doc = data?.activeDocument || null
|
|
3733
5217
|
const segments = doc?.segments || []
|
|
3734
5218
|
const index = doc ? Math.min(selected, Math.max(0, segments.length - 1)) : 0
|
|
3735
5219
|
const segment = segments[index] || null
|
|
3736
5220
|
const detail = doc?.current?.index === index ? doc.current : null
|
|
5221
|
+
useEffect(() => {
|
|
5222
|
+
draftHistoryRef.current = { index, stack: [draft], cursor: 0, lastAt: 0 }
|
|
5223
|
+
}, [doc?.id, index])
|
|
3737
5224
|
const settings = data?.settings || {
|
|
3738
5225
|
sourceLang: 'en',
|
|
3739
5226
|
targetLang: 'zh-CN',
|
|
3740
|
-
fuzzyThreshold:
|
|
5227
|
+
fuzzyThreshold: DEFAULT_FUZZY_THRESHOLD,
|
|
3741
5228
|
tmLimit: 5,
|
|
3742
5229
|
uiLang: 'auto',
|
|
3743
5230
|
mt: { provider: 'mock', model: 'cait-mock', temperature: 0.2 },
|
|
@@ -3960,6 +5447,10 @@ window.__ModuleLoader__.load({
|
|
|
3960
5447
|
}
|
|
3961
5448
|
}, [data?.activeDocument?.id, data?.revision])
|
|
3962
5449
|
|
|
5450
|
+
useEffect(() => {
|
|
5451
|
+
setReview(null)
|
|
5452
|
+
}, [data?.activeDocument?.id])
|
|
5453
|
+
|
|
3963
5454
|
useEffect(() => {
|
|
3964
5455
|
let alive = true
|
|
3965
5456
|
rpc('listMtRoutes')
|
|
@@ -3985,6 +5476,11 @@ window.__ModuleLoader__.load({
|
|
|
3985
5476
|
setTmQuery('')
|
|
3986
5477
|
setTmResults(null)
|
|
3987
5478
|
setTmError('')
|
|
5479
|
+
setConcordance(null)
|
|
5480
|
+
setConcordanceQuery('')
|
|
5481
|
+
setReplacePreview(null)
|
|
5482
|
+
setReplaceUndo(null)
|
|
5483
|
+
setNotes(null)
|
|
3988
5484
|
}, [doc?.id])
|
|
3989
5485
|
|
|
3990
5486
|
useEffect(() => {
|
|
@@ -4022,7 +5518,8 @@ window.__ModuleLoader__.load({
|
|
|
4022
5518
|
const saveDraft = async (silent = true) => {
|
|
4023
5519
|
if (!doc || !dirty) return true
|
|
4024
5520
|
try {
|
|
4025
|
-
await rpc('saveDraft', { documentId: doc.id, index: selected, target: draft })
|
|
5521
|
+
const result = await rpc('saveDraft', { documentId: doc.id, index: selected, target: draft })
|
|
5522
|
+
if (result.state) adoptState(result.state)
|
|
4026
5523
|
setDirty(false)
|
|
4027
5524
|
if (!silent) notify(t('toast.draftSaved'))
|
|
4028
5525
|
return true
|
|
@@ -4049,6 +5546,49 @@ window.__ModuleLoader__.load({
|
|
|
4049
5546
|
}
|
|
4050
5547
|
}
|
|
4051
5548
|
|
|
5549
|
+
const changeDraft = (value) => {
|
|
5550
|
+
const history = draftHistoryRef.current
|
|
5551
|
+
if (history.index !== index) {
|
|
5552
|
+
history.index = index
|
|
5553
|
+
history.stack = [draft]
|
|
5554
|
+
history.cursor = 0
|
|
5555
|
+
}
|
|
5556
|
+
if (history.stack[history.cursor] !== value) {
|
|
5557
|
+
const now = Date.now()
|
|
5558
|
+
if (now - history.lastAt < 650 && history.cursor > 0) {
|
|
5559
|
+
history.stack[history.cursor] = value
|
|
5560
|
+
} else {
|
|
5561
|
+
history.stack = history.stack.slice(0, history.cursor + 1)
|
|
5562
|
+
history.stack.push(value)
|
|
5563
|
+
if (history.stack.length > 200) history.stack.shift()
|
|
5564
|
+
history.cursor = history.stack.length - 1
|
|
5565
|
+
}
|
|
5566
|
+
history.lastAt = now
|
|
5567
|
+
}
|
|
5568
|
+
setDraft(value)
|
|
5569
|
+
setDirty(true)
|
|
5570
|
+
}
|
|
5571
|
+
|
|
5572
|
+
const undoDraft = () => {
|
|
5573
|
+
const history = draftHistoryRef.current
|
|
5574
|
+
if (history.cursor <= 0) return
|
|
5575
|
+
history.cursor -= 1
|
|
5576
|
+
history.lastAt = 0
|
|
5577
|
+
const value = history.stack[history.cursor]
|
|
5578
|
+
setDraft(value)
|
|
5579
|
+
setDirty(value !== (segment?.target || ''))
|
|
5580
|
+
}
|
|
5581
|
+
|
|
5582
|
+
const redoDraft = () => {
|
|
5583
|
+
const history = draftHistoryRef.current
|
|
5584
|
+
if (history.cursor >= history.stack.length - 1) return
|
|
5585
|
+
history.cursor += 1
|
|
5586
|
+
history.lastAt = 0
|
|
5587
|
+
const value = history.stack[history.cursor]
|
|
5588
|
+
setDraft(value)
|
|
5589
|
+
setDirty(value !== (segment?.target || ''))
|
|
5590
|
+
}
|
|
5591
|
+
|
|
4052
5592
|
const confirmSegment = async () => {
|
|
4053
5593
|
if (!doc || busy) return
|
|
4054
5594
|
if (!draft.trim()) {
|
|
@@ -4203,6 +5743,10 @@ window.__ModuleLoader__.load({
|
|
|
4203
5743
|
}
|
|
4204
5744
|
|
|
4205
5745
|
const applyTerm = (term) => {
|
|
5746
|
+
if (term?.forbidden) {
|
|
5747
|
+
notify(t('toast.termForbidden'), 'error')
|
|
5748
|
+
return
|
|
5749
|
+
}
|
|
4206
5750
|
const separator = /^(zh|ja|ko)/i.test(doc?.targetLang || '') ? '' : ' '
|
|
4207
5751
|
setDraft((current) => {
|
|
4208
5752
|
const value = current.replace(/\s+$/, '')
|
|
@@ -4212,6 +5756,368 @@ window.__ModuleLoader__.load({
|
|
|
4212
5756
|
setTimeout(() => textareaRef.current?.focus(), 0)
|
|
4213
5757
|
}
|
|
4214
5758
|
|
|
5759
|
+
const unconfirmCurrent = async () => {
|
|
5760
|
+
if (!doc || busy) return
|
|
5761
|
+
setBusy('unconfirm')
|
|
5762
|
+
try {
|
|
5763
|
+
if (dirty) {
|
|
5764
|
+
const saved = await saveDraft(true)
|
|
5765
|
+
if (!saved) return
|
|
5766
|
+
}
|
|
5767
|
+
const result = await rpc('unconfirmSegment', { documentId: doc.id, index: selected })
|
|
5768
|
+
adoptState(result.state)
|
|
5769
|
+
notify(t('toast.unconfirmed'))
|
|
5770
|
+
} catch (error) {
|
|
5771
|
+
// Saving a dirty confirmed segment already revokes the confirmation.
|
|
5772
|
+
// Treat the second call as idempotent instead of showing an error.
|
|
5773
|
+
if (error?.code === 'NOT_CONFIRMED') notify(t('toast.unconfirmed'))
|
|
5774
|
+
else report(error)
|
|
5775
|
+
} finally {
|
|
5776
|
+
setBusy('')
|
|
5777
|
+
}
|
|
5778
|
+
}
|
|
5779
|
+
|
|
5780
|
+
const toggleLock = async () => {
|
|
5781
|
+
if (!doc || busy || !segment) return
|
|
5782
|
+
setBusy('lock')
|
|
5783
|
+
try {
|
|
5784
|
+
await rpc('setSegmentLocked', { documentId: doc.id, index: selected, locked: !segment.locked })
|
|
5785
|
+
const next = await rpc('bootstrap')
|
|
5786
|
+
adoptState(next, selected)
|
|
5787
|
+
notify(t(segment.locked ? 'toast.unlocked' : 'toast.locked'))
|
|
5788
|
+
} catch (error) {
|
|
5789
|
+
report(error)
|
|
5790
|
+
} finally {
|
|
5791
|
+
setBusy('')
|
|
5792
|
+
}
|
|
5793
|
+
}
|
|
5794
|
+
|
|
5795
|
+
const mergeCurrent = () => {
|
|
5796
|
+
if (!doc || !segment || busy) return
|
|
5797
|
+
openConfirm({
|
|
5798
|
+
title: t('modal.confirm.mergeTitle'),
|
|
5799
|
+
message: t('modal.confirm.merge'),
|
|
5800
|
+
confirmLabel: t('editor.merge'),
|
|
5801
|
+
action: async () => {
|
|
5802
|
+
const result = await rpc('mergeSegments', { documentId: doc.id, index: selected, count: 2 })
|
|
5803
|
+
adoptState(result.state, result.index)
|
|
5804
|
+
notify(t('toast.merged'))
|
|
5805
|
+
},
|
|
5806
|
+
})
|
|
5807
|
+
}
|
|
5808
|
+
|
|
5809
|
+
const splitCurrent = (at) => {
|
|
5810
|
+
if (!doc || !segment || busy) return
|
|
5811
|
+
openConfirm({
|
|
5812
|
+
title: t('modal.confirm.splitTitle'),
|
|
5813
|
+
message: t('modal.confirm.split'),
|
|
5814
|
+
confirmLabel: t('editor.split'),
|
|
5815
|
+
action: async () => {
|
|
5816
|
+
const result = await rpc('splitSegment', { documentId: doc.id, index: selected, at })
|
|
5817
|
+
adoptState(result.state, result.index)
|
|
5818
|
+
notify(t('toast.split'))
|
|
5819
|
+
},
|
|
5820
|
+
})
|
|
5821
|
+
}
|
|
5822
|
+
|
|
5823
|
+
const mergeCue = () => {
|
|
5824
|
+
if (!doc || !segment || busy) return
|
|
5825
|
+
openConfirm({
|
|
5826
|
+
title: t('modal.confirm.mergeTitle'),
|
|
5827
|
+
message: t('modal.confirm.mergeCue'),
|
|
5828
|
+
confirmLabel: t('editor.mergeCue'),
|
|
5829
|
+
action: async () => {
|
|
5830
|
+
const result = await rpc('mergeCues', { documentId: doc.id, index: selected, count: 2 })
|
|
5831
|
+
adoptState(result.state, result.index)
|
|
5832
|
+
notify(t('toast.cuesMerged'))
|
|
5833
|
+
},
|
|
5834
|
+
})
|
|
5835
|
+
}
|
|
5836
|
+
|
|
5837
|
+
const splitCueAt = (at) => {
|
|
5838
|
+
if (!doc || !segment || busy) return
|
|
5839
|
+
openConfirm({
|
|
5840
|
+
title: t('modal.confirm.splitTitle'),
|
|
5841
|
+
message: t('modal.confirm.splitCue'),
|
|
5842
|
+
confirmLabel: t('editor.splitCue'),
|
|
5843
|
+
action: async () => {
|
|
5844
|
+
const result = await rpc('splitCue', { documentId: doc.id, index: selected, at })
|
|
5845
|
+
adoptState(result.state, result.index)
|
|
5846
|
+
notify(t('toast.cueSplit'))
|
|
5847
|
+
},
|
|
5848
|
+
})
|
|
5849
|
+
}
|
|
5850
|
+
|
|
5851
|
+
const shiftSubtitles = (offsetMs) => {
|
|
5852
|
+
if (!doc || !offsetMs || busy) return
|
|
5853
|
+
openConfirm({
|
|
5854
|
+
title: t('modal.confirm.shiftTitle'),
|
|
5855
|
+
message: t('modal.confirm.shiftCue'),
|
|
5856
|
+
confirmLabel: t('editor.shift'),
|
|
5857
|
+
action: async () => {
|
|
5858
|
+
const result = await rpc('shiftSubtitles', { documentId: doc.id, offsetMs })
|
|
5859
|
+
adoptState(result.state)
|
|
5860
|
+
notify(t('toast.subtitlesShifted'))
|
|
5861
|
+
},
|
|
5862
|
+
})
|
|
5863
|
+
}
|
|
5864
|
+
|
|
5865
|
+
const saveBrief = async (brief) => {
|
|
5866
|
+
if (!doc || busy) return false
|
|
5867
|
+
setBusy('brief')
|
|
5868
|
+
try {
|
|
5869
|
+
const result = await rpc('setBrief', { documentId: doc.id, brief })
|
|
5870
|
+
adoptState(result.state)
|
|
5871
|
+
setModal(null)
|
|
5872
|
+
notify(t('toast.briefSaved'))
|
|
5873
|
+
return true
|
|
5874
|
+
} catch (error) {
|
|
5875
|
+
report(error)
|
|
5876
|
+
return false
|
|
5877
|
+
} finally {
|
|
5878
|
+
setBusy('')
|
|
5879
|
+
}
|
|
5880
|
+
}
|
|
5881
|
+
|
|
5882
|
+
const saveResources = async (resources) => {
|
|
5883
|
+
if (!doc || busy) return false
|
|
5884
|
+
setBusy('resources')
|
|
5885
|
+
try {
|
|
5886
|
+
const result = await rpc('setDocumentResources', { documentId: doc.id, ...resources })
|
|
5887
|
+
adoptState(result.state)
|
|
5888
|
+
setModal(null)
|
|
5889
|
+
notify(t('toast.resourcesSaved'))
|
|
5890
|
+
return true
|
|
5891
|
+
} catch (error) {
|
|
5892
|
+
report(error)
|
|
5893
|
+
return false
|
|
5894
|
+
} finally {
|
|
5895
|
+
setBusy('')
|
|
5896
|
+
}
|
|
5897
|
+
}
|
|
5898
|
+
|
|
5899
|
+
const openAnalysis = async () => {
|
|
5900
|
+
if (!doc) return
|
|
5901
|
+
setAnalysis(null)
|
|
5902
|
+
setModal({ type: 'analysis' })
|
|
5903
|
+
setAnalysisBusy(true)
|
|
5904
|
+
try {
|
|
5905
|
+
setAnalysis(await rpc('analyzeDocument', { documentId: doc.id }))
|
|
5906
|
+
} catch (error) {
|
|
5907
|
+
report(error)
|
|
5908
|
+
} finally {
|
|
5909
|
+
setAnalysisBusy(false)
|
|
5910
|
+
}
|
|
5911
|
+
}
|
|
5912
|
+
|
|
5913
|
+
const openReplace = () => {
|
|
5914
|
+
if (!doc) return
|
|
5915
|
+
setReplacePreview(null)
|
|
5916
|
+
setModal({ type: 'replace' })
|
|
5917
|
+
}
|
|
5918
|
+
|
|
5919
|
+
const previewReplace = async (form) => {
|
|
5920
|
+
if (!doc || replaceBusy) return
|
|
5921
|
+
setReplaceBusy(true)
|
|
5922
|
+
try {
|
|
5923
|
+
setReplacePreview(await rpc('replaceInDocument', { documentId: doc.id, ...form }))
|
|
5924
|
+
} catch (error) {
|
|
5925
|
+
report(error)
|
|
5926
|
+
} finally {
|
|
5927
|
+
setReplaceBusy(false)
|
|
5928
|
+
}
|
|
5929
|
+
}
|
|
5930
|
+
|
|
5931
|
+
const applyReplace = async (form) => {
|
|
5932
|
+
if (!doc || replaceBusy) return
|
|
5933
|
+
setReplaceBusy(true)
|
|
5934
|
+
try {
|
|
5935
|
+
const result = await rpc('replaceInDocument', { documentId: doc.id, ...form, apply: true })
|
|
5936
|
+
setReplaceUndo(Array.isArray(result.undo) && result.undo.length ? result.undo : null)
|
|
5937
|
+
setReplacePreview(null)
|
|
5938
|
+
adoptState(result.state, result.undo?.[0]?.index)
|
|
5939
|
+
notify(t('toast.replaced').replace('{count}', String(result.applied ?? 0)))
|
|
5940
|
+
} catch (error) {
|
|
5941
|
+
report(error)
|
|
5942
|
+
} finally {
|
|
5943
|
+
setReplaceBusy(false)
|
|
5944
|
+
}
|
|
5945
|
+
}
|
|
5946
|
+
|
|
5947
|
+
const undoReplace = async () => {
|
|
5948
|
+
if (!doc || !replaceUndo || replaceBusy) return
|
|
5949
|
+
setReplaceBusy(true)
|
|
5950
|
+
try {
|
|
5951
|
+
const result = await rpc('undoReplace', { documentId: doc.id, undo: replaceUndo })
|
|
5952
|
+
adoptState(result.state, replaceUndo[0]?.index)
|
|
5953
|
+
setReplaceUndo(null)
|
|
5954
|
+
setReplacePreview(null)
|
|
5955
|
+
notify(t('toast.replaceUndone'))
|
|
5956
|
+
} catch (error) {
|
|
5957
|
+
report(error)
|
|
5958
|
+
} finally {
|
|
5959
|
+
setReplaceBusy(false)
|
|
5960
|
+
}
|
|
5961
|
+
}
|
|
5962
|
+
|
|
5963
|
+
const openNotes = async () => {
|
|
5964
|
+
if (!doc) return
|
|
5965
|
+
setNotes(null)
|
|
5966
|
+
setNotesBusy(true)
|
|
5967
|
+
setModal({ type: 'notes' })
|
|
5968
|
+
try {
|
|
5969
|
+
const result = await rpc('notesForSegment', { documentId: doc.id, index })
|
|
5970
|
+
setNotes(result.notes)
|
|
5971
|
+
} catch (error) {
|
|
5972
|
+
report(error)
|
|
5973
|
+
} finally {
|
|
5974
|
+
setNotesBusy(false)
|
|
5975
|
+
}
|
|
5976
|
+
}
|
|
5977
|
+
|
|
5978
|
+
const addNote = async (text) => {
|
|
5979
|
+
if (!doc || notesBusy) return
|
|
5980
|
+
setNotesBusy(true)
|
|
5981
|
+
try {
|
|
5982
|
+
const result = await rpc('addSegmentNote', { documentId: doc.id, index, text })
|
|
5983
|
+
setNotes(result.notes)
|
|
5984
|
+
adoptState(result.state, index)
|
|
5985
|
+
notify(t('toast.noteSaved'))
|
|
5986
|
+
} catch (error) {
|
|
5987
|
+
report(error)
|
|
5988
|
+
} finally {
|
|
5989
|
+
setNotesBusy(false)
|
|
5990
|
+
}
|
|
5991
|
+
}
|
|
5992
|
+
|
|
5993
|
+
const updateNote = async (noteId, text) => {
|
|
5994
|
+
if (!doc || notesBusy) return
|
|
5995
|
+
setNotesBusy(true)
|
|
5996
|
+
try {
|
|
5997
|
+
const result = await rpc('updateSegmentNote', { documentId: doc.id, index, noteId, text })
|
|
5998
|
+
setNotes(result.notes)
|
|
5999
|
+
adoptState(result.state, index)
|
|
6000
|
+
notify(t('toast.noteSaved'))
|
|
6001
|
+
} catch (error) {
|
|
6002
|
+
report(error)
|
|
6003
|
+
} finally {
|
|
6004
|
+
setNotesBusy(false)
|
|
6005
|
+
}
|
|
6006
|
+
}
|
|
6007
|
+
|
|
6008
|
+
const deleteNote = async (noteId) => {
|
|
6009
|
+
if (!doc || notesBusy) return
|
|
6010
|
+
setNotesBusy(true)
|
|
6011
|
+
try {
|
|
6012
|
+
const result = await rpc('deleteSegmentNote', { documentId: doc.id, index, noteId })
|
|
6013
|
+
setNotes(result.notes)
|
|
6014
|
+
adoptState(result.state, index)
|
|
6015
|
+
notify(t('toast.noteDeleted'))
|
|
6016
|
+
} catch (error) {
|
|
6017
|
+
report(error)
|
|
6018
|
+
} finally {
|
|
6019
|
+
setNotesBusy(false)
|
|
6020
|
+
}
|
|
6021
|
+
}
|
|
6022
|
+
|
|
6023
|
+
const refreshQa = async () => { if (!doc) return
|
|
6024
|
+
setQa(null)
|
|
6025
|
+
try {
|
|
6026
|
+
setQa(await rpc('getQaReport', { documentId: doc.id }))
|
|
6027
|
+
} catch {
|
|
6028
|
+
// QA is advisory; a failure must not disturb the editor.
|
|
6029
|
+
}
|
|
6030
|
+
}
|
|
6031
|
+
|
|
6032
|
+
const jumpQa = (nextIndex) => {
|
|
6033
|
+
if (Number.isInteger(nextIndex)) chooseSegment(nextIndex)
|
|
6034
|
+
}
|
|
6035
|
+
|
|
6036
|
+
const runReview = async () => {
|
|
6037
|
+
if (!doc || reviewBusy) return
|
|
6038
|
+
setReviewBusy(true)
|
|
6039
|
+
try {
|
|
6040
|
+
const result = await rpc('reviewDocument', {
|
|
6041
|
+
documentId: doc.id,
|
|
6042
|
+
from: 0,
|
|
6043
|
+
count: 20,
|
|
6044
|
+
})
|
|
6045
|
+
setReview(result || { issues: [] })
|
|
6046
|
+
} catch (error) {
|
|
6047
|
+
setReview(null)
|
|
6048
|
+
report(error)
|
|
6049
|
+
} finally {
|
|
6050
|
+
setReviewBusy(false)
|
|
6051
|
+
}
|
|
6052
|
+
}
|
|
6053
|
+
|
|
6054
|
+
const applyReview = (issue) => {
|
|
6055
|
+
if (!doc || !issue || !Number.isInteger(issue.index)) return
|
|
6056
|
+
chooseSegment(issue.index)
|
|
6057
|
+
if (issue.suggestion) {
|
|
6058
|
+
setDraft(issue.suggestion)
|
|
6059
|
+
setDirty(true)
|
|
6060
|
+
}
|
|
6061
|
+
}
|
|
6062
|
+
|
|
6063
|
+
const applyQaFix = async (item, action) => {
|
|
6064
|
+
if (!doc || busy || !item) return
|
|
6065
|
+
const segment = doc.segments[item.index]
|
|
6066
|
+
if (!segment) return
|
|
6067
|
+
let target = segment.target || ''
|
|
6068
|
+
if (action === 'copy-source') {
|
|
6069
|
+
target = segment.source
|
|
6070
|
+
} else if (action === 'clear') {
|
|
6071
|
+
target = ''
|
|
6072
|
+
} else if (action === 'term') {
|
|
6073
|
+
const term = String(item.termTarget || '')
|
|
6074
|
+
if (!term || target.includes(term)) return
|
|
6075
|
+
const separator = /[A-Za-z0-9]$/.test(target) && /^[A-Za-z0-9]/.test(term) ? ' ' : ''
|
|
6076
|
+
target = `${target}${separator}${term}`
|
|
6077
|
+
} else {
|
|
6078
|
+
return
|
|
6079
|
+
}
|
|
6080
|
+
setBusy('qa-fix')
|
|
6081
|
+
try {
|
|
6082
|
+
const result = await rpc('saveDraft', {
|
|
6083
|
+
documentId: doc.id,
|
|
6084
|
+
index: item.index,
|
|
6085
|
+
target,
|
|
6086
|
+
})
|
|
6087
|
+
if (result.state) adoptState(result.state, item.index)
|
|
6088
|
+
setQa(await rpc('getQaReport', { documentId: doc.id }))
|
|
6089
|
+
notify(t('toast.qaFixed'))
|
|
6090
|
+
} catch (error) {
|
|
6091
|
+
report(error)
|
|
6092
|
+
} finally {
|
|
6093
|
+
setBusy('')
|
|
6094
|
+
}
|
|
6095
|
+
}
|
|
6096
|
+
|
|
6097
|
+
const runConcordance = async () => {
|
|
6098
|
+
const query = concordanceQuery.trim()
|
|
6099
|
+
if (!doc || !query) {
|
|
6100
|
+
setConcordance(null)
|
|
6101
|
+
return
|
|
6102
|
+
}
|
|
6103
|
+
setConcordanceBusy(true)
|
|
6104
|
+
try {
|
|
6105
|
+
setConcordance(await rpc('concordance', { documentId: doc.id, query, limit: 50 }))
|
|
6106
|
+
} catch (error) {
|
|
6107
|
+
report(error)
|
|
6108
|
+
} finally {
|
|
6109
|
+
setConcordanceBusy(false)
|
|
6110
|
+
}
|
|
6111
|
+
}
|
|
6112
|
+
|
|
6113
|
+
const applyConcordance = (entry) => {
|
|
6114
|
+
if (!entry?.target) return
|
|
6115
|
+
setDraft(entry.target)
|
|
6116
|
+
setDirty(true)
|
|
6117
|
+
setMt(null)
|
|
6118
|
+
setTimeout(() => textareaRef.current?.focus(), 0)
|
|
6119
|
+
}
|
|
6120
|
+
|
|
4215
6121
|
const editTm = async ({ id, target }) => {
|
|
4216
6122
|
setBusy('tm-edit')
|
|
4217
6123
|
try {
|
|
@@ -4277,6 +6183,40 @@ window.__ModuleLoader__.load({
|
|
|
4277
6183
|
})
|
|
4278
6184
|
}
|
|
4279
6185
|
|
|
6186
|
+
const clearPretranslated = async () => {
|
|
6187
|
+
const active = data?.activeDocument
|
|
6188
|
+
if (!active) return
|
|
6189
|
+
try {
|
|
6190
|
+
const preview = await rpc('getClearUnconfirmedPreview', { documentId: active.id })
|
|
6191
|
+
const counts = preview?.counts || {}
|
|
6192
|
+
const total = counts.total || 0
|
|
6193
|
+
if (!total) {
|
|
6194
|
+
notify(t('toast.nothingToClear'), 'info')
|
|
6195
|
+
return
|
|
6196
|
+
}
|
|
6197
|
+
setModal({
|
|
6198
|
+
type: 'confirm',
|
|
6199
|
+
title: t('modal.confirm.clearPretranslatedTitle'),
|
|
6200
|
+
message: t('modal.confirm.clearPretranslatedMessage')
|
|
6201
|
+
.replace('{count}', String(total))
|
|
6202
|
+
.replace('{mt}', String(counts.mt || 0))
|
|
6203
|
+
.replace('{tm}', String(counts.tm || 0))
|
|
6204
|
+
.replace('{auto}', String(counts.auto || 0)),
|
|
6205
|
+
confirmLabel: t('modal.confirm.clearPretranslatedConfirm').replace('{count}', String(total)),
|
|
6206
|
+
action: async () => {
|
|
6207
|
+
const result = await rpc('clearUnconfirmedTranslations', {
|
|
6208
|
+
documentId: active.id,
|
|
6209
|
+
confirm: true,
|
|
6210
|
+
})
|
|
6211
|
+
adoptState(result.state, result.cursor)
|
|
6212
|
+
notify(t('toast.pretranslatedCleared').replace('{count}', String(result.cleared)))
|
|
6213
|
+
},
|
|
6214
|
+
})
|
|
6215
|
+
} catch (error) {
|
|
6216
|
+
report(error)
|
|
6217
|
+
}
|
|
6218
|
+
}
|
|
6219
|
+
|
|
4280
6220
|
const addTerm = async (form) => { setBusy('term')
|
|
4281
6221
|
try {
|
|
4282
6222
|
const result = await rpc('addGlossary', {
|
|
@@ -4466,6 +6406,32 @@ window.__ModuleLoader__.load({
|
|
|
4466
6406
|
}
|
|
4467
6407
|
}
|
|
4468
6408
|
|
|
6409
|
+
const importReview = (file) => {
|
|
6410
|
+
if (!doc || !file) return
|
|
6411
|
+
const reader = new FileReader()
|
|
6412
|
+
reader.onload = async () => {
|
|
6413
|
+
setBusy('review')
|
|
6414
|
+
try {
|
|
6415
|
+
const result = await rpc('applyXliffReview', {
|
|
6416
|
+
documentId: doc.id,
|
|
6417
|
+
text: String(reader.result || ''),
|
|
6418
|
+
})
|
|
6419
|
+
adoptState(result.state)
|
|
6420
|
+
notify(
|
|
6421
|
+
t('toast.reviewApplied')
|
|
6422
|
+
.replace('{applied}', String(result.applied))
|
|
6423
|
+
.replace('{unmatched}', String(result.unmatched + result.sourceMismatch)),
|
|
6424
|
+
)
|
|
6425
|
+
setModal(null)
|
|
6426
|
+
} catch (error) {
|
|
6427
|
+
report(error)
|
|
6428
|
+
} finally {
|
|
6429
|
+
setBusy('')
|
|
6430
|
+
}
|
|
6431
|
+
}
|
|
6432
|
+
reader.readAsText(file, 'utf-8')
|
|
6433
|
+
}
|
|
6434
|
+
|
|
4469
6435
|
const openConfirm = (state) => setModal({ type: 'confirm', ...state })
|
|
4470
6436
|
|
|
4471
6437
|
const clearTm = () =>
|
|
@@ -4520,6 +6486,7 @@ window.__ModuleLoader__.load({
|
|
|
4520
6486
|
|
|
4521
6487
|
const openIds = new Set((data?.openDocuments || []).map((item) => item.id))
|
|
4522
6488
|
const closedDocuments = (data?.documents || []).filter((item) => !openIds.has(item.id))
|
|
6489
|
+
const qaRisk = (qa?.risks || []).find((item) => item.index === index) || null
|
|
4523
6490
|
|
|
4524
6491
|
const deleteClosedDocument = (document) =>
|
|
4525
6492
|
openConfirm({
|
|
@@ -4599,6 +6566,23 @@ window.__ModuleLoader__.load({
|
|
|
4599
6566
|
}
|
|
4600
6567
|
}, [])
|
|
4601
6568
|
|
|
6569
|
+
useEffect(() => {
|
|
6570
|
+
const onKey = (event) => {
|
|
6571
|
+
const mod = event.metaKey || event.ctrlKey
|
|
6572
|
+
if (!mod) return
|
|
6573
|
+
const key = event.key.toLowerCase()
|
|
6574
|
+
if (key === 'k') {
|
|
6575
|
+
event.preventDefault()
|
|
6576
|
+
setModal((current) => (current?.type === 'palette' ? null : { type: 'palette' }))
|
|
6577
|
+
} else if (key === 'f' && doc && !modal) {
|
|
6578
|
+
event.preventDefault()
|
|
6579
|
+
openReplace()
|
|
6580
|
+
}
|
|
6581
|
+
}
|
|
6582
|
+
window.addEventListener('keydown', onKey)
|
|
6583
|
+
return () => window.removeEventListener('keydown', onKey)
|
|
6584
|
+
}, [doc?.id, modal])
|
|
6585
|
+
|
|
4602
6586
|
if (loading) {
|
|
4603
6587
|
return h(
|
|
4604
6588
|
'div',
|
|
@@ -4626,6 +6610,44 @@ window.__ModuleLoader__.load({
|
|
|
4626
6610
|
)
|
|
4627
6611
|
}
|
|
4628
6612
|
|
|
6613
|
+
const commands = [
|
|
6614
|
+
{ id: 'new', title: t('cmd.new'), hint: t('cmd.newHint'), run: () => setModal({ type: 'new', defaultMode: 'text' }) },
|
|
6615
|
+
{ id: 'import', title: t('cmd.import'), hint: '', run: () => setModal({ type: 'new', defaultMode: 'file' }) },
|
|
6616
|
+
{ id: 'export', title: t('cmd.export'), hint: '', docOnly: true, run: () => setModal({ type: 'export' }) },
|
|
6617
|
+
{ id: 'pretranslate', title: t('cmd.pretranslate'), hint: '', docOnly: true, run: () => runPreTranslate() },
|
|
6618
|
+
{ id: 'replace', title: t('cmd.replace'), hint: 'Ctrl+F', docOnly: true, run: () => openReplace() },
|
|
6619
|
+
{ id: 'brief', title: t('cmd.brief'), hint: '', docOnly: true, run: () => setModal({ type: 'brief' }) },
|
|
6620
|
+
{ id: 'resources', title: t('cmd.resources'), hint: '', docOnly: true, run: () => setModal({ type: 'resources' }) },
|
|
6621
|
+
{ id: 'analysis', title: t('cmd.analysis'), hint: '', docOnly: true, run: () => openAnalysis() },
|
|
6622
|
+
{ id: 'notes', title: t('cmd.notes'), hint: '', docOnly: true, run: () => openNotes() },
|
|
6623
|
+
{
|
|
6624
|
+
id: 'review',
|
|
6625
|
+
title: t('cmd.review'),
|
|
6626
|
+
hint: '',
|
|
6627
|
+
docOnly: true,
|
|
6628
|
+
run: () => {
|
|
6629
|
+
setTab('qa')
|
|
6630
|
+
runReview()
|
|
6631
|
+
},
|
|
6632
|
+
},
|
|
6633
|
+
{
|
|
6634
|
+
id: 'clear-pretranslated',
|
|
6635
|
+
title: t('cmd.clearPretranslated'),
|
|
6636
|
+
hint: '',
|
|
6637
|
+
docOnly: true,
|
|
6638
|
+
run: () => clearPretranslated(),
|
|
6639
|
+
},
|
|
6640
|
+
{ id: 'documents', title: t('cmd.documents'), hint: '', run: () => setView('documents') },
|
|
6641
|
+
{ id: 'libraries', title: t('cmd.libraries'), hint: '', run: () => setView('libraries') },
|
|
6642
|
+
{ id: 'align', title: t('cmd.align'), hint: '', run: () => setView('align') },
|
|
6643
|
+
{ id: 'settings', title: t('cmd.settings'), hint: '', run: () => setModal({ type: 'settings' }) },
|
|
6644
|
+
].filter((item) => !item.docOnly || doc)
|
|
6645
|
+
|
|
6646
|
+
const runCommand = (item) => {
|
|
6647
|
+
setModal(null)
|
|
6648
|
+
setTimeout(() => item.run(), 0)
|
|
6649
|
+
}
|
|
6650
|
+
|
|
4629
6651
|
return h(
|
|
4630
6652
|
'div',
|
|
4631
6653
|
{ className: 'cait-root', ref: rootRef },
|
|
@@ -4646,6 +6668,7 @@ window.__ModuleLoader__.load({
|
|
|
4646
6668
|
pre,
|
|
4647
6669
|
onPreTranslate: runPreTranslate,
|
|
4648
6670
|
onCancelPreTranslate: cancelPreTranslate,
|
|
6671
|
+
onReplace: openReplace,
|
|
4649
6672
|
view,
|
|
4650
6673
|
onView: setView,
|
|
4651
6674
|
}),
|
|
@@ -4664,6 +6687,7 @@ window.__ModuleLoader__.load({
|
|
|
4664
6687
|
onCreateLibrary: createLibrary,
|
|
4665
6688
|
onRenameLibrary: renameLibrary,
|
|
4666
6689
|
onDeleteLibrary: deleteLibrary,
|
|
6690
|
+
onSetMounts: setMounts,
|
|
4667
6691
|
})
|
|
4668
6692
|
: null,
|
|
4669
6693
|
view === 'align'
|
|
@@ -4681,7 +6705,7 @@ window.__ModuleLoader__.load({
|
|
|
4681
6705
|
? h(
|
|
4682
6706
|
'div',
|
|
4683
6707
|
{ className: `cait-body${doc ? '' : ' cait-no-doc'}` },
|
|
4684
|
-
doc ? h(SegmentRail, { t, doc, selected: index, query: railQuery, setQuery: setRailQuery, filter: railFilter, setFilter: setRailFilter, onSelect: chooseSegment, onConfirmAll: confirmAllTranslated, onPropagate: propagateRepeats, busy, qa }) : null,
|
|
6708
|
+
doc ? h(SegmentRail, { t, doc, selected: index, query: railQuery, setQuery: setRailQuery, filter: railFilter, setFilter: setRailFilter, onSelect: chooseSegment, onConfirmAll: confirmAllTranslated, onPropagate: propagateRepeats, onClearPretranslated: clearPretranslated, busy, qa }) : null,
|
|
4685
6709
|
h(
|
|
4686
6710
|
'div',
|
|
4687
6711
|
{ className: `cait-main${doc ? '' : ' cait-main-empty'}` },
|
|
@@ -4693,18 +6717,32 @@ window.__ModuleLoader__.load({
|
|
|
4693
6717
|
segment,
|
|
4694
6718
|
detail,
|
|
4695
6719
|
draft,
|
|
4696
|
-
setDraft:
|
|
4697
|
-
setDraft(value)
|
|
4698
|
-
setDirty(true)
|
|
4699
|
-
},
|
|
6720
|
+
setDraft: changeDraft,
|
|
4700
6721
|
dirty,
|
|
4701
6722
|
busy,
|
|
6723
|
+
canUndo: draftHistoryRef.current.cursor > 0,
|
|
6724
|
+
canRedo: draftHistoryRef.current.cursor < draftHistoryRef.current.stack.length - 1,
|
|
6725
|
+
onUndo: undoDraft,
|
|
6726
|
+
onRedo: redoDraft,
|
|
4702
6727
|
onConfirm: confirmSegment,
|
|
6728
|
+
onUnconfirm: unconfirmCurrent,
|
|
6729
|
+
onToggleLock: toggleLock,
|
|
6730
|
+
onMerge: mergeCurrent,
|
|
6731
|
+
onSplit: splitCurrent,
|
|
6732
|
+
onMergeCue: mergeCue,
|
|
6733
|
+
onSplitCue: splitCueAt,
|
|
6734
|
+
onShiftSubtitles: shiftSubtitles,
|
|
4703
6735
|
onNext: () => moveSegment(1),
|
|
4704
6736
|
onPrev: () => moveSegment(-1),
|
|
4705
6737
|
onSave: () => saveDraft(false),
|
|
4706
6738
|
onAdopt: adoptSuggestion,
|
|
4707
6739
|
onApplyTerm: applyTerm,
|
|
6740
|
+
onOpenBrief: () => setModal({ type: 'brief' }),
|
|
6741
|
+
onOpenResources: () => setModal({ type: 'resources' }),
|
|
6742
|
+
onOpenAnalysis: openAnalysis,
|
|
6743
|
+
onOpenNotes: openNotes,
|
|
6744
|
+
noteCount: segment?.notes?.length || 0,
|
|
6745
|
+
qaRisk,
|
|
4708
6746
|
textareaRef,
|
|
4709
6747
|
})
|
|
4710
6748
|
: h(EmptyState, { t, settings, busy, onCreate: createFromForm, onDemo: loadDemo }),
|
|
@@ -4745,7 +6783,20 @@ window.__ModuleLoader__.load({
|
|
|
4745
6783
|
onCopy: copy,
|
|
4746
6784
|
onOpenSettings: () => setModal({ type: 'settings' }),
|
|
4747
6785
|
onSearchLibrary: openLibrary,
|
|
4748
|
-
|
|
6786
|
+
qa,
|
|
6787
|
+
onRefreshQa: refreshQa,
|
|
6788
|
+
onJumpQa: jumpQa,
|
|
6789
|
+
onFixQa: applyQaFix,
|
|
6790
|
+
review,
|
|
6791
|
+
reviewBusy,
|
|
6792
|
+
onReview: runReview,
|
|
6793
|
+
onApplyReview: applyReview,
|
|
6794
|
+
concordance,
|
|
6795
|
+
concordanceQuery,
|
|
6796
|
+
setConcordanceQuery,
|
|
6797
|
+
onConcordance: runConcordance,
|
|
6798
|
+
onApplyConcordance: applyConcordance,
|
|
6799
|
+
busy: busy || (concordanceBusy ? 'concordance' : ''),
|
|
4749
6800
|
})
|
|
4750
6801
|
: null,
|
|
4751
6802
|
),
|
|
@@ -4762,7 +6813,60 @@ window.__ModuleLoader__.load({
|
|
|
4762
6813
|
})
|
|
4763
6814
|
: null,
|
|
4764
6815
|
modal?.type === 'export'
|
|
4765
|
-
? h(ExportModal, { t, busy, qa, onClose: () => setModal(null), onExport: exportAs })
|
|
6816
|
+
? h(ExportModal, { t, busy, qa, onClose: () => setModal(null), onExport: exportAs, onImportReview: importReview })
|
|
6817
|
+
: null,
|
|
6818
|
+
modal?.type === 'brief'
|
|
6819
|
+
? h(BriefModal, { t, doc, busy, onClose: () => setModal(null), onSave: saveBrief })
|
|
6820
|
+
: null,
|
|
6821
|
+
modal?.type === 'resources'
|
|
6822
|
+
? h(ResourceModal, {
|
|
6823
|
+
t,
|
|
6824
|
+
doc,
|
|
6825
|
+
libraries: data?.libraries,
|
|
6826
|
+
mounts: data?.mounts,
|
|
6827
|
+
busy,
|
|
6828
|
+
onClose: () => setModal(null),
|
|
6829
|
+
onSave: saveResources,
|
|
6830
|
+
})
|
|
6831
|
+
: null,
|
|
6832
|
+
modal?.type === 'analysis'
|
|
6833
|
+
? h(AnalysisModal, {
|
|
6834
|
+
t,
|
|
6835
|
+
doc,
|
|
6836
|
+
analysis,
|
|
6837
|
+
busy: analysisBusy,
|
|
6838
|
+
onClose: () => setModal(null),
|
|
6839
|
+
})
|
|
6840
|
+
: null,
|
|
6841
|
+
modal?.type === 'replace'
|
|
6842
|
+
? h(ReplaceModal, {
|
|
6843
|
+
t,
|
|
6844
|
+
doc,
|
|
6845
|
+
busy: replaceBusy,
|
|
6846
|
+
preview: replacePreview,
|
|
6847
|
+
undoCount: replaceUndo ? replaceUndo.length : 0,
|
|
6848
|
+
onPreview: previewReplace,
|
|
6849
|
+
onApply: applyReplace,
|
|
6850
|
+
onUndo: undoReplace,
|
|
6851
|
+
onClose: () => setModal(null),
|
|
6852
|
+
})
|
|
6853
|
+
: null,
|
|
6854
|
+
modal?.type === 'notes'
|
|
6855
|
+
? h(NoteModal, {
|
|
6856
|
+
t,
|
|
6857
|
+
doc,
|
|
6858
|
+
index,
|
|
6859
|
+
segment,
|
|
6860
|
+
notes,
|
|
6861
|
+
busy: notesBusy,
|
|
6862
|
+
onAdd: addNote,
|
|
6863
|
+
onUpdate: updateNote,
|
|
6864
|
+
onDelete: deleteNote,
|
|
6865
|
+
onClose: () => setModal(null),
|
|
6866
|
+
})
|
|
6867
|
+
: null,
|
|
6868
|
+
modal?.type === 'palette'
|
|
6869
|
+
? h(CommandPalette, { t, items: commands, onClose: () => setModal(null), onRun: runCommand })
|
|
4766
6870
|
: null,
|
|
4767
6871
|
modal?.type === 'settings'
|
|
4768
6872
|
? h(SettingsModal, { t, settings, mtRoutes, busy, onClose: () => setModal(null), onSave: saveSettings })
|
|
@@ -4846,6 +6950,14 @@ window.__ModuleLoader__.load({
|
|
|
4846
6950
|
'rail.filter.pending': '待译',
|
|
4847
6951
|
'rail.filter.translated': '待审',
|
|
4848
6952
|
'rail.filter.confirmed': '已确认',
|
|
6953
|
+
'rail.filter.risk': '重点审',
|
|
6954
|
+
'rail.clearPretranslated': '清理预翻译',
|
|
6955
|
+
'rail.clearPretranslatedHint': '清空未确认的预翻译与自动填充译文,保留人工草稿和已确认句段',
|
|
6956
|
+
'modal.confirm.clearPretranslatedTitle': '清理预翻译译文?',
|
|
6957
|
+
'modal.confirm.clearPretranslatedMessage': '将清空当前文档中 {count} 句未确认的预翻译译文(MT {mt}、TM {tm}、自动传播 {auto})。已确认句段和人工编辑的译文不会受影响;TM 中已保存的内容也不会删除。清理后这些句段回到未翻译状态,且无法撤销。',
|
|
6958
|
+
'modal.confirm.clearPretranslatedConfirm': '清理 {count} 句',
|
|
6959
|
+
'toast.pretranslatedCleared': '已清理 {count} 句预翻译译文',
|
|
6960
|
+
'toast.nothingToClear': '没有可清理的未确认预翻译',
|
|
4849
6961
|
'rail.confirmAll': '全部确认',
|
|
4850
6962
|
'rail.confirmAllHint': '把当前所有「待审」句段一次性确认并写入翻译记忆',
|
|
4851
6963
|
'rail.confirmAllMessage': '将确认 {count} 个待审句段并写入翻译记忆,句段译文不会经过逐句复核。确定继续?',
|
|
@@ -4987,6 +7099,7 @@ window.__ModuleLoader__.load({
|
|
|
4987
7099
|
'modal.export.targetTxt': '纯文本',
|
|
4988
7100
|
'modal.export.targetMd': 'Markdown',
|
|
4989
7101
|
'modal.export.docx': 'Word DOCX',
|
|
7102
|
+
'modal.export.bilingualDocx': '双语审校稿 DOCX',
|
|
4990
7103
|
'modal.export.docxGroup': '保留源格式',
|
|
4991
7104
|
'modal.export.epub': 'EPUB 电子书',
|
|
4992
7105
|
'modal.export.epubGroup': '保留源排版',
|
|
@@ -4996,7 +7109,18 @@ window.__ModuleLoader__.load({
|
|
|
4996
7109
|
'modal.export.srt': 'SRT 字幕',
|
|
4997
7110
|
'modal.export.vtt': 'WebVTT 字幕',
|
|
4998
7111
|
'modal.export.po': 'PO 本地化',
|
|
7112
|
+
'modal.export.appleStrings': '.strings 字符串',
|
|
7113
|
+
'modal.export.appleStringsdict': '.stringsdict 复数',
|
|
7114
|
+
'modal.export.android': 'Android strings.xml',
|
|
7115
|
+
'modal.export.resx': '.resx 资源',
|
|
7116
|
+
'modal.export.arb': 'ARB 应用资源',
|
|
7117
|
+
'modal.export.yaml': 'YAML 本地化',
|
|
7118
|
+
'modal.export.properties': '.properties 属性',
|
|
4999
7119
|
'modal.export.xliff': 'XLIFF 1.2',
|
|
7120
|
+
'modal.export.xliffReview': 'XLIFF 审校包',
|
|
7121
|
+
'modal.export.reviewImport': '回填审校包',
|
|
7122
|
+
'modal.export.reviewImportHint': '选择外部返回的 XLIFF 审校包,只更新能按 id 或源文对上的句段。',
|
|
7123
|
+
'toast.reviewApplied': '审校包已回填 {applied} 段,{unmatched} 段未匹配',
|
|
5000
7124
|
'modal.export.xliff2': 'XLIFF 2.0',
|
|
5001
7125
|
'modal.export.sdlxliff': 'SDL XLIFF(基础)',
|
|
5002
7126
|
'modal.export.webGroup': '网页与结构化文本',
|
|
@@ -5019,8 +7143,16 @@ window.__ModuleLoader__.load({
|
|
|
5019
7143
|
'modal.settings.pre.collapseBlankLines': '合并连续空行',
|
|
5020
7144
|
'modal.settings.tm': '翻译记忆',
|
|
5021
7145
|
'modal.settings.fuzzy': '模糊匹配阈值',
|
|
7146
|
+
'modal.settings.fuzzyHint': '低于 70% 的匹配通常只共享虚词,改起来比重译更慢;70% 是主流 CAT 工具的默认值。',
|
|
7147
|
+
'modal.settings.resetDefault': '恢复默认',
|
|
5022
7148
|
'modal.settings.limit': '返回匹配数',
|
|
5023
7149
|
'modal.settings.mt': '机器翻译',
|
|
7150
|
+
'modal.settings.review': 'AI 审校模型',
|
|
7151
|
+
'modal.settings.reviewInherit': '跟随翻译模型(默认)',
|
|
7152
|
+
'modal.settings.reviewHint': '取消勾选后,AI 审校使用独立路由;预翻译仍使用上面的机器翻译路由。',
|
|
7153
|
+
'modal.settings.reviewProvider': '审校 provider 路由',
|
|
7154
|
+
'modal.settings.reviewInheritedHint': '当前与预翻译共用同一个 provider 和模型。',
|
|
7155
|
+
'modal.settings.reviewCustomHint': 'AI 审校使用独立路由,预翻译不受影响。',
|
|
5024
7156
|
'modal.settings.provider': 'MT provider 路由',
|
|
5025
7157
|
'modal.settings.routeHint': 'CAIT 只保存路由名和模型;API Key 由 DSH 凭据服务解析,CAIT 不接触。',
|
|
5026
7158
|
'modal.settings.routeMissing': '已失效的路由',
|
|
@@ -5081,6 +7213,11 @@ window.__ModuleLoader__.load({
|
|
|
5081
7213
|
'library.import': '导入',
|
|
5082
7214
|
'library.export': '导出',
|
|
5083
7215
|
'library.clear': '清空',
|
|
7216
|
+
'tm.maintain.dedupe': '去重',
|
|
7217
|
+
'tm.maintain.prune': '清理空译文',
|
|
7218
|
+
'tm.maintain.dedupeHint': '将删除 {count} 条重复条目,保留最新一条。',
|
|
7219
|
+
'tm.maintain.pruneHint': '将删除 {count} 条空译文条目。',
|
|
7220
|
+
'tm.maintain.done': 'TM 维护完成:{count} 条',
|
|
5084
7221
|
'library.newLibrary': '新建库',
|
|
5085
7222
|
'library.rename': '重命名',
|
|
5086
7223
|
'library.deleteLibrary': '删除库',
|
|
@@ -5124,6 +7261,11 @@ window.__ModuleLoader__.load({
|
|
|
5124
7261
|
'align.readError': '读取文件失败。',
|
|
5125
7262
|
'align.sourceLang': '源语言(可选)',
|
|
5126
7263
|
'align.targetLang': '目标语言(可选)',
|
|
7264
|
+
'align.langHint': '可留空;例如 zh-CN、en',
|
|
7265
|
+
'align.tm': '写入记忆库',
|
|
7266
|
+
'align.tmHint': '对齐结果写入这个记忆库',
|
|
7267
|
+
'align.tmOption': '记忆库:{name}',
|
|
7268
|
+
'align.noTm': '暂无已挂载的记忆库',
|
|
5127
7269
|
'align.stats': '高置信 {high} · 待确认 {medium} · 可疑 {low}',
|
|
5128
7270
|
'align.unpairedSource': '未配对原文',
|
|
5129
7271
|
'align.unpairedTarget': '未配对译文',
|
|
@@ -5142,13 +7284,14 @@ window.__ModuleLoader__.load({
|
|
|
5142
7284
|
'rail.propagateMessage': '把已有译文填到本文档中其它相同原文的句段(已确认的不会动)。',
|
|
5143
7285
|
'toast.propagated': '已铺满 {count} 个句段。',
|
|
5144
7286
|
'term.title': '术语候选',
|
|
5145
|
-
'term.hint': '
|
|
7287
|
+
'term.hint': '候选包含重复出现的术语和只出现一次的专名;专名需人工确认后才入库。',
|
|
5146
7288
|
'term.extract': '从文档提取术语',
|
|
5147
|
-
'term.empty': '
|
|
7289
|
+
'term.empty': '没有找到候选术语。',
|
|
5148
7290
|
'term.add': '加入术语库',
|
|
5149
7291
|
'term.added': '已加入 {added} 条术语。',
|
|
5150
7292
|
'term.noDoc': '先打开一个文档再提取术语。',
|
|
5151
7293
|
'term.inLibrary': '已有',
|
|
7294
|
+
'term.proper': '专名',
|
|
5152
7295
|
}
|
|
5153
7296
|
|
|
5154
7297
|
const EN = {
|
|
@@ -5213,6 +7356,14 @@ window.__ModuleLoader__.load({
|
|
|
5213
7356
|
'rail.filter.pending': 'To translate',
|
|
5214
7357
|
'rail.filter.translated': 'To review',
|
|
5215
7358
|
'rail.filter.confirmed': 'Confirmed',
|
|
7359
|
+
'rail.filter.risk': 'Needs review',
|
|
7360
|
+
'rail.clearPretranslated': 'Clear pretranslated',
|
|
7361
|
+
'rail.clearPretranslatedHint': 'Clear unconfirmed pretranslated and auto-filled targets; manual drafts and confirmed segments are kept',
|
|
7362
|
+
'modal.confirm.clearPretranslatedTitle': 'Clear pretranslated targets?',
|
|
7363
|
+
'modal.confirm.clearPretranslatedMessage': 'This will clear {count} unconfirmed pretranslated target(s) in the current document (MT {mt}, TM {tm}, auto-propagated {auto}). Confirmed segments and manually edited targets are not affected, and saved TM entries are not deleted. The cleared segments return to untranslated and this cannot be undone.',
|
|
7364
|
+
'modal.confirm.clearPretranslatedConfirm': 'Clear {count} segment(s)',
|
|
7365
|
+
'toast.pretranslatedCleared': 'Cleared {count} pretranslated target(s)',
|
|
7366
|
+
'toast.nothingToClear': 'No unconfirmed pretranslated targets to clear',
|
|
5216
7367
|
'rail.confirmAll': 'Confirm all',
|
|
5217
7368
|
'rail.confirmAllHint': 'Confirm every "To review" segment at once and write them to translation memory',
|
|
5218
7369
|
'rail.confirmAllMessage': 'This confirms {count} segment(s) awaiting review and writes them to translation memory, without reviewing them one by one. Continue?',
|
|
@@ -5354,6 +7505,7 @@ window.__ModuleLoader__.load({
|
|
|
5354
7505
|
'modal.export.targetTxt': 'Plain text',
|
|
5355
7506
|
'modal.export.targetMd': 'Markdown',
|
|
5356
7507
|
'modal.export.docx': 'Word DOCX',
|
|
7508
|
+
'modal.export.bilingualDocx': 'Bilingual review DOCX',
|
|
5357
7509
|
'modal.export.docxGroup': 'Preserve source formatting',
|
|
5358
7510
|
'modal.export.epub': 'EPUB ebook',
|
|
5359
7511
|
'modal.export.epubGroup': 'Preserve source layout',
|
|
@@ -5363,7 +7515,18 @@ window.__ModuleLoader__.load({
|
|
|
5363
7515
|
'modal.export.srt': 'SRT subtitles',
|
|
5364
7516
|
'modal.export.vtt': 'WebVTT subtitles',
|
|
5365
7517
|
'modal.export.po': 'PO localization',
|
|
7518
|
+
'modal.export.appleStrings': '.strings strings',
|
|
7519
|
+
'modal.export.appleStringsdict': '.stringsdict plurals',
|
|
7520
|
+
'modal.export.android': 'Android strings.xml',
|
|
7521
|
+
'modal.export.resx': '.resx resources',
|
|
7522
|
+
'modal.export.arb': 'ARB app resources',
|
|
7523
|
+
'modal.export.yaml': 'YAML localization',
|
|
7524
|
+
'modal.export.properties': '.properties',
|
|
5366
7525
|
'modal.export.xliff': 'XLIFF 1.2',
|
|
7526
|
+
'modal.export.xliffReview': 'XLIFF review package',
|
|
7527
|
+
'modal.export.reviewImport': 'Apply review package',
|
|
7528
|
+
'modal.export.reviewImportHint': 'Choose an XLIFF review package. Only segments matched by id or source are updated.',
|
|
7529
|
+
'toast.reviewApplied': 'Review applied: {applied} segments, {unmatched} unmatched',
|
|
5367
7530
|
'modal.export.xliff2': 'XLIFF 2.0',
|
|
5368
7531
|
'modal.export.sdlxliff': 'SDL XLIFF (basic)',
|
|
5369
7532
|
'modal.export.webGroup': 'Web and structured text',
|
|
@@ -5386,8 +7549,17 @@ window.__ModuleLoader__.load({
|
|
|
5386
7549
|
'modal.settings.pre.collapseBlankLines': 'Merge consecutive blank lines',
|
|
5387
7550
|
'modal.settings.tm': 'Translation memory',
|
|
5388
7551
|
'modal.settings.fuzzy': 'Fuzzy-match threshold',
|
|
7552
|
+
'modal.settings.fuzzyHint':
|
|
7553
|
+
'Below 70% a match usually shares only function words, so editing it takes longer than retranslating. 70% is what mainstream CAT tools default to.',
|
|
7554
|
+
'modal.settings.resetDefault': 'Reset',
|
|
5389
7555
|
'modal.settings.limit': 'Matches returned',
|
|
5390
7556
|
'modal.settings.mt': 'Machine translation',
|
|
7557
|
+
'modal.settings.review': 'AI review model',
|
|
7558
|
+
'modal.settings.reviewInherit': 'Follow the translation model (default)',
|
|
7559
|
+
'modal.settings.reviewHint': 'Uncheck to give AI review its own route. Pre-translation keeps using the MT route above.',
|
|
7560
|
+
'modal.settings.reviewProvider': 'Review provider route',
|
|
7561
|
+
'modal.settings.reviewInheritedHint': 'Review currently shares the same provider and model as pre-translation.',
|
|
7562
|
+
'modal.settings.reviewCustomHint': 'AI review uses a separate route; pre-translation is unaffected.',
|
|
5391
7563
|
'modal.settings.provider': 'MT provider route',
|
|
5392
7564
|
'modal.settings.routeHint': 'CAIT stores only the route and model name; DSH resolves the API key, which CAIT never sees.',
|
|
5393
7565
|
'modal.settings.routeMissing': 'Route no longer available',
|
|
@@ -5448,6 +7620,11 @@ window.__ModuleLoader__.load({
|
|
|
5448
7620
|
'library.import': 'Import',
|
|
5449
7621
|
'library.export': 'Export',
|
|
5450
7622
|
'library.clear': 'Clear',
|
|
7623
|
+
'tm.maintain.dedupe': 'Deduplicate',
|
|
7624
|
+
'tm.maintain.prune': 'Prune empty targets',
|
|
7625
|
+
'tm.maintain.dedupeHint': 'Delete {count} duplicate entries, keeping the newest.',
|
|
7626
|
+
'tm.maintain.pruneHint': 'Delete {count} entries with an empty target.',
|
|
7627
|
+
'tm.maintain.done': 'TM maintenance done: {count} entries',
|
|
5451
7628
|
'library.newLibrary': 'New library',
|
|
5452
7629
|
'library.rename': 'Rename',
|
|
5453
7630
|
'library.deleteLibrary': 'Delete library',
|
|
@@ -5489,8 +7666,13 @@ window.__ModuleLoader__.load({
|
|
|
5489
7666
|
'align.running': 'Aligning…',
|
|
5490
7667
|
'align.needFiles': 'Choose both files first.',
|
|
5491
7668
|
'align.readError': 'Could not read that file.',
|
|
5492
|
-
'align.sourceLang': 'Source language
|
|
5493
|
-
'align.targetLang': 'Target language
|
|
7669
|
+
'align.sourceLang': 'Source language',
|
|
7670
|
+
'align.targetLang': 'Target language',
|
|
7671
|
+
'align.langHint': 'Optional; for example zh-CN, en',
|
|
7672
|
+
'align.tm': 'Write to memory',
|
|
7673
|
+
'align.tmHint': 'Aligned pairs are written to this memory',
|
|
7674
|
+
'align.tmOption': 'Memory: {name}',
|
|
7675
|
+
'align.noTm': 'No mounted memory yet',
|
|
5494
7676
|
'align.stats': 'high {high} · to review {medium} · doubtful {low}',
|
|
5495
7677
|
'align.unpairedSource': 'Unpaired source',
|
|
5496
7678
|
'align.unpairedTarget': 'Unpaired target',
|
|
@@ -5509,13 +7691,14 @@ window.__ModuleLoader__.load({
|
|
|
5509
7691
|
'rail.propagateMessage': 'Copy the existing translation to other segments with the same source. Confirmed segments are left alone.',
|
|
5510
7692
|
'toast.propagated': 'Filled {count} segment(s).',
|
|
5511
7693
|
'term.title': 'Terminology candidates',
|
|
5512
|
-
'term.hint': '
|
|
7694
|
+
'term.hint': 'Candidates include repeated terms and one-off proper names; names need a human decision before they enter the glossary.',
|
|
5513
7695
|
'term.extract': 'Mine terms from the document',
|
|
5514
|
-
'term.empty': 'No
|
|
7696
|
+
'term.empty': 'No candidate found.',
|
|
5515
7697
|
'term.add': 'Add to glossary',
|
|
5516
7698
|
'term.added': 'Added {added} term(s).',
|
|
5517
7699
|
'term.noDoc': 'Open a document before mining terms.',
|
|
5518
7700
|
'term.inLibrary': 'in library',
|
|
7701
|
+
'term.proper': 'proper name',
|
|
5519
7702
|
}
|
|
5520
7703
|
|
|
5521
7704
|
const JA = {
|
|
@@ -5580,6 +7763,14 @@ window.__ModuleLoader__.load({
|
|
|
5580
7763
|
'rail.filter.pending': '未翻訳',
|
|
5581
7764
|
'rail.filter.translated': '要確認',
|
|
5582
7765
|
'rail.filter.confirmed': '確認済み',
|
|
7766
|
+
'rail.filter.risk': '重点確認',
|
|
7767
|
+
'rail.clearPretranslated': '事前翻訳をクリア',
|
|
7768
|
+
'rail.clearPretranslatedHint': '未確認の事前翻訳・自動反映の訳文を消去します。手動の下書きと確認済みセグメントは残します',
|
|
7769
|
+
'modal.confirm.clearPretranslatedTitle': '事前翻訳をクリアしますか?',
|
|
7770
|
+
'modal.confirm.clearPretranslatedMessage': '現在の文書で未確認の事前翻訳訳文 {count} 件(MT {mt}、TM {tm}、自動反映 {auto})を消去します。確認済みセグメントと手動編集の訳文は影響を受けず、TM に保存済みの内容も削除されません。消去後は未翻訳に戻り、元に戻せません。',
|
|
7771
|
+
'modal.confirm.clearPretranslatedConfirm': '{count} 件をクリア',
|
|
7772
|
+
'toast.pretranslatedCleared': '事前翻訳訳文を {count} 件クリアしました',
|
|
7773
|
+
'toast.nothingToClear': 'クリアできる未確認の事前翻訳はありません',
|
|
5583
7774
|
'rail.confirmAll': 'まとめて確認',
|
|
5584
7775
|
'rail.confirmAllHint': '「要確認」の文をまとめて確認し、翻訳メモリに書き込みます',
|
|
5585
7776
|
'rail.confirmAllMessage': '要確認の {count} 文をまとめて確認し、翻訳メモリに書き込みます。1 文ずつ見直すことはありません。続けますか?',
|
|
@@ -5721,6 +7912,7 @@ window.__ModuleLoader__.load({
|
|
|
5721
7912
|
'modal.export.targetTxt': 'プレーンテキスト',
|
|
5722
7913
|
'modal.export.targetMd': 'Markdown',
|
|
5723
7914
|
'modal.export.docx': 'Word DOCX',
|
|
7915
|
+
'modal.export.bilingualDocx': 'バイリンガル校閲 DOCX',
|
|
5724
7916
|
'modal.export.docxGroup': '元の書式を保持',
|
|
5725
7917
|
'modal.export.epub': 'EPUB 電子書',
|
|
5726
7918
|
'modal.export.epubGroup': '元のレイアウトを保持',
|
|
@@ -5730,7 +7922,18 @@ window.__ModuleLoader__.load({
|
|
|
5730
7922
|
'modal.export.srt': 'SRT 字幕',
|
|
5731
7923
|
'modal.export.vtt': 'WebVTT 字幕',
|
|
5732
7924
|
'modal.export.po': 'PO ローカライズ',
|
|
7925
|
+
'modal.export.appleStrings': '.strings 文字列',
|
|
7926
|
+
'modal.export.appleStringsdict': '.stringsdict 複数形',
|
|
7927
|
+
'modal.export.android': 'Android strings.xml',
|
|
7928
|
+
'modal.export.resx': '.resx リソース',
|
|
7929
|
+
'modal.export.arb': 'ARB アプリリソース',
|
|
7930
|
+
'modal.export.yaml': 'YAML ローカライズ',
|
|
7931
|
+
'modal.export.properties': '.properties',
|
|
5733
7932
|
'modal.export.xliff': 'XLIFF 1.2',
|
|
7933
|
+
'modal.export.xliffReview': 'XLIFF 校閲パッケージ',
|
|
7934
|
+
'modal.export.reviewImport': '校閲パッケージを反映',
|
|
7935
|
+
'modal.export.reviewImportHint': '外部から返却された XLIFF を選択。id または原文が一致するセグメントだけ更新します。',
|
|
7936
|
+
'toast.reviewApplied': '校閲を反映: {applied} セグメント、未一致 {unmatched}',
|
|
5734
7937
|
'modal.export.xliff2': 'XLIFF 2.0',
|
|
5735
7938
|
'modal.export.sdlxliff': 'SDL XLIFF(基本)',
|
|
5736
7939
|
'modal.export.webGroup': 'Web と構造化テキスト',
|
|
@@ -5753,8 +7956,17 @@ window.__ModuleLoader__.load({
|
|
|
5753
7956
|
'modal.settings.pre.collapseBlankLines': '連続する空行をまとめる',
|
|
5754
7957
|
'modal.settings.tm': '翻訳メモリ',
|
|
5755
7958
|
'modal.settings.fuzzy': 'あいまい一致のしきい値',
|
|
7959
|
+
'modal.settings.fuzzyHint':
|
|
7960
|
+
'70% を下回る一致は機能語だけが重なることが多く、訳し直すより遅くなります。主流の CAT ツールも 70% を既定にしています。',
|
|
7961
|
+
'modal.settings.resetDefault': '既定に戻す',
|
|
5756
7962
|
'modal.settings.limit': '返す一致件数',
|
|
5757
7963
|
'modal.settings.mt': '機械翻訳',
|
|
7964
|
+
'modal.settings.review': 'AI レビューモデル',
|
|
7965
|
+
'modal.settings.reviewInherit': '翻訳モデルに従う(デフォルト)',
|
|
7966
|
+
'modal.settings.reviewHint': 'チェックを外すと AI レビューが独立したルートを使います。事前翻訳は上の MT ルートのままです。',
|
|
7967
|
+
'modal.settings.reviewProvider': 'レビュー用プロバイダーのルート',
|
|
7968
|
+
'modal.settings.reviewInheritedHint': '現在、レビューは事前翻訳と同じプロバイダーとモデルを共有しています。',
|
|
7969
|
+
'modal.settings.reviewCustomHint': 'AI レビューは独立したルートを使います。事前翻訳には影響しません。',
|
|
5758
7970
|
'modal.settings.provider': 'MT プロバイダーのルート',
|
|
5759
7971
|
'modal.settings.routeHint': 'CAIT はルート名とモデル名だけを保存します。API キーは DSH の認証情報サービスが解決し、CAIT は触れません。',
|
|
5760
7972
|
'modal.settings.routeMissing': '利用できないルート',
|
|
@@ -5815,6 +8027,11 @@ window.__ModuleLoader__.load({
|
|
|
5815
8027
|
'library.import': 'インポート',
|
|
5816
8028
|
'library.export': 'エクスポート',
|
|
5817
8029
|
'library.clear': 'クリア',
|
|
8030
|
+
'tm.maintain.dedupe': '重複排除',
|
|
8031
|
+
'tm.maintain.prune': '空訳を整理',
|
|
8032
|
+
'tm.maintain.dedupeHint': '{count} 件の重複を削除し、最新のみ残します。',
|
|
8033
|
+
'tm.maintain.pruneHint': '空の訳文を持つ {count} 件を削除します。',
|
|
8034
|
+
'tm.maintain.done': 'TM メンテナンス完了: {count} 件',
|
|
5818
8035
|
'library.newLibrary': '新しいライブラリ',
|
|
5819
8036
|
'library.rename': '名前を変更',
|
|
5820
8037
|
'library.deleteLibrary': 'ライブラリを削除',
|
|
@@ -5858,6 +8075,11 @@ window.__ModuleLoader__.load({
|
|
|
5858
8075
|
'align.readError': 'ファイルを読み込めませんでした。',
|
|
5859
8076
|
'align.sourceLang': '原文言語(任意)',
|
|
5860
8077
|
'align.targetLang': '訳文言語(任意)',
|
|
8078
|
+
'align.langHint': '任意;例: zh-CN、en',
|
|
8079
|
+
'align.tm': '保存先メモリ',
|
|
8080
|
+
'align.tmHint': '整列結果をこのメモリに保存します',
|
|
8081
|
+
'align.tmOption': 'メモリ: {name}',
|
|
8082
|
+
'align.noTm': 'メモリ未設定',
|
|
5861
8083
|
'align.stats': '高 {high} · 要確認 {medium} · 要注意 {low}',
|
|
5862
8084
|
'align.unpairedSource': '未対応の原文',
|
|
5863
8085
|
'align.unpairedTarget': '未対応の訳文',
|
|
@@ -5876,18 +8098,550 @@ window.__ModuleLoader__.load({
|
|
|
5876
8098
|
'rail.propagateMessage': '同じ原文の他のセグメントに既存の訳文をコピーします。確定済みは変更しません。',
|
|
5877
8099
|
'toast.propagated': '{count} 件のセグメントを埋めました。',
|
|
5878
8100
|
'term.title': '用語候補',
|
|
5879
|
-
'term.hint': '
|
|
8101
|
+
'term.hint': '繰り返し用語と一度だけ現れる固有名詞を候補にします。固有名詞は確認してから登録してください。',
|
|
5880
8102
|
'term.extract': '文書から用語を抽出',
|
|
5881
|
-
'term.empty': '
|
|
8103
|
+
'term.empty': '候補が見つかりませんでした。',
|
|
5882
8104
|
'term.add': '用語集に追加',
|
|
5883
8105
|
'term.added': '{added} 件の用語を追加しました。',
|
|
5884
8106
|
'term.noDoc': '先に文書を開いてください。',
|
|
5885
8107
|
'term.inLibrary': '登録済み',
|
|
8108
|
+
'term.proper': '固有名詞',
|
|
5886
8109
|
}
|
|
5887
8110
|
|
|
8111
|
+
// 1.1.4: brief, risk-aware QA, unconfirm and concordance. Kept in one place
|
|
8112
|
+
// so the three dictionaries cannot drift apart.
|
|
8113
|
+
Object.assign(ZH, {
|
|
8114
|
+
'panel.qa': '检查',
|
|
8115
|
+
'panel.concordance': '检索',
|
|
8116
|
+
'panel.concordancePlaceholder': '在翻译记忆中检索…',
|
|
8117
|
+
'panel.concordanceHint': '在已挂载的记忆库中检索源文或译文;点击结果写入当前句段的译文。',
|
|
8118
|
+
'panel.noConcordance': '没有检索结果。',
|
|
8119
|
+
'panel.searchConcordance': '检索',
|
|
8120
|
+
'panel.brief': '文档 brief',
|
|
8121
|
+
'panel.briefHint': '给译者和 agent 的领域、读者、语气与术语约束。',
|
|
8122
|
+
'panel.termForbidden': '禁用',
|
|
8123
|
+
'panel.termPreferred': '必用',
|
|
8124
|
+
'brief.title': '文档 brief',
|
|
8125
|
+
'brief.hint': '这些信息会注入预翻译和 agent 上下文;留空表示不限制。',
|
|
8126
|
+
'brief.domain': '领域',
|
|
8127
|
+
'brief.audience': '读者',
|
|
8128
|
+
'brief.tone': '语气',
|
|
8129
|
+
'brief.notes': '补充说明',
|
|
8130
|
+
'brief.doNotTranslate': '不要翻译',
|
|
8131
|
+
'brief.styleSample': '风格示例',
|
|
8132
|
+
'brief.save': '保存 brief',
|
|
8133
|
+
'brief.saved': 'brief 已保存',
|
|
8134
|
+
'brief.empty': '尚未填写 brief',
|
|
8135
|
+
'brief.placeholder.domain': '例如:法律、医疗、游戏',
|
|
8136
|
+
'brief.placeholder.audience': '例如:终端用户、开发者',
|
|
8137
|
+
'brief.placeholder.tone': '例如:正式、简洁、口语',
|
|
8138
|
+
'brief.placeholder.notes': '术语偏好、格式要求、背景…',
|
|
8139
|
+
'brief.placeholder.doNotTranslate': '每行一个词或短语',
|
|
8140
|
+
'brief.placeholder.styleSample': '粘贴一段期望风格的译文…',
|
|
8141
|
+
'editor.unconfirm': '撤销确认',
|
|
8142
|
+
'editor.unconfirmHint': '回到待审状态,不改动翻译记忆',
|
|
8143
|
+
'editor.undo': '撤销编辑',
|
|
8144
|
+
'editor.redo': '重做编辑',
|
|
8145
|
+
'editor.lock': '锁定',
|
|
8146
|
+
'editor.unlock': '解锁',
|
|
8147
|
+
'editor.locked': '已锁定',
|
|
8148
|
+
'toast.locked': '已锁定句段',
|
|
8149
|
+
'toast.unlocked': '已解锁句段',
|
|
8150
|
+
'editor.brief': 'Brief',
|
|
8151
|
+
'editor.risk': '风险',
|
|
8152
|
+
'qa.title': '检查',
|
|
8153
|
+
'qa.refresh': '重新检查',
|
|
8154
|
+
'qa.empty': '没有发现问题。',
|
|
8155
|
+
'qa.loading': '正在检查…',
|
|
8156
|
+
'qa.review': 'AI 审校',
|
|
8157
|
+
'qa.reviewing': '审校中…',
|
|
8158
|
+
'qa.reviewTitle': 'AI 审校建议',
|
|
8159
|
+
'qa.reviewEmpty': 'AI 没有发现需要改的句子。',
|
|
8160
|
+
'qa.reviewApply': '填入建议',
|
|
8161
|
+
'qa.high': '高',
|
|
8162
|
+
'qa.medium': '中',
|
|
8163
|
+
'qa.low': '低',
|
|
8164
|
+
'qa.jump': '跳到句段',
|
|
8165
|
+
'qa.risk': '风险',
|
|
8166
|
+
'qa.code.TERM_FORBIDDEN': '禁用术语',
|
|
8167
|
+
'qa.code.LENGTH_DRIFT': '长度异常',
|
|
8168
|
+
'qa.code.SUBTITLE_OVERLAP': '字幕重叠',
|
|
8169
|
+
'qa.fixCopy': '复制源文',
|
|
8170
|
+
'qa.fixClear': '清空译文',
|
|
8171
|
+
'qa.fixTerm': '插入术语',
|
|
8172
|
+
'toast.qaFixed': '已修复该句,可继续检查',
|
|
8173
|
+
'toast.unconfirmed': '已撤销确认,可继续编辑',
|
|
8174
|
+
'toast.briefSaved': 'brief 已保存',
|
|
8175
|
+
'toast.termForbidden': '这是禁用术语,不能插入',
|
|
8176
|
+
'editor.resources': '资源',
|
|
8177
|
+
'resources.title': '文档资源',
|
|
8178
|
+
'resources.hint': '限定这篇文档读写哪些 TM 和术语库;不勾选“继承全局”时只使用下面选中的库。',
|
|
8179
|
+
'resources.inheritTm': '继承全局 TM 挂载',
|
|
8180
|
+
'resources.inheritGlossary': '继承全局术语库挂载',
|
|
8181
|
+
'resources.tm': '翻译记忆',
|
|
8182
|
+
'resources.glossary': '术语库',
|
|
8183
|
+
'resources.defaultWrite': '默认写入',
|
|
8184
|
+
'resources.followDefault': '跟随全局默认',
|
|
8185
|
+
'resources.firstBound': '首个绑定库',
|
|
8186
|
+
'resources.none': '未绑定(不读不写)',
|
|
8187
|
+
'resources.empty': '没有可用的库。',
|
|
8188
|
+
'resources.save': '保存资源',
|
|
8189
|
+
'toast.resourcesSaved': '文档资源已保存',
|
|
8190
|
+
'editor.analysis': '分析',
|
|
8191
|
+
'analysis.title': '文档分析',
|
|
8192
|
+
'analysis.hint': '按当前文档绑定的 TM 统计;匹配率用于报价参考。',
|
|
8193
|
+
'analysis.words': '字数',
|
|
8194
|
+
'analysis.characters': '字符',
|
|
8195
|
+
'analysis.segments': '句段',
|
|
8196
|
+
'analysis.duplicates': '重复句段',
|
|
8197
|
+
'analysis.translated': '已有译文',
|
|
8198
|
+
'analysis.confirmed': '已确认',
|
|
8199
|
+
'analysis.ice': 'ICE / 上下文',
|
|
8200
|
+
'analysis.exact': '完全匹配',
|
|
8201
|
+
'analysis.fuzzy85': '85–99%',
|
|
8202
|
+
'analysis.fuzzy70': '70–84%',
|
|
8203
|
+
'analysis.new': '新句段',
|
|
8204
|
+
'analysis.matchRate': '匹配率',
|
|
8205
|
+
'analysis.leverage': '加权 leverage',
|
|
8206
|
+
'analysis.exportCsv': '导出 CSV',
|
|
8207
|
+
'analysis.exportJson': '导出 JSON',
|
|
8208
|
+
'analysis.close': '关闭',
|
|
8209
|
+
'replace.open': '查找替换',
|
|
8210
|
+
'replace.title': '查找替换',
|
|
8211
|
+
'replace.hint': '在原文或译文中查找并替换;应用前先预览影响范围。',
|
|
8212
|
+
'replace.query': '查找',
|
|
8213
|
+
'replace.queryPlaceholder': '要查找的文本',
|
|
8214
|
+
'replace.replacement': '替换为',
|
|
8215
|
+
'replace.replacementPlaceholder': '留空表示删除',
|
|
8216
|
+
'replace.scope': '范围',
|
|
8217
|
+
'replace.scope.target': '仅译文',
|
|
8218
|
+
'replace.scope.source': '仅原文',
|
|
8219
|
+
'replace.scope.both': '原文和译文',
|
|
8220
|
+
'replace.matchCase': '区分大小写',
|
|
8221
|
+
'replace.regex': '正则表达式',
|
|
8222
|
+
'replace.preview': '预览',
|
|
8223
|
+
'replace.apply': '应用替换',
|
|
8224
|
+
'replace.undo': '撤销上次替换',
|
|
8225
|
+
'replace.results': '命中 {count} 处,涉及 {segments} 个句段',
|
|
8226
|
+
'replace.truncated': '结果较多,仅显示前 200 条。',
|
|
8227
|
+
'replace.noResults': '没有命中。',
|
|
8228
|
+
'replace.field.source': '原文',
|
|
8229
|
+
'replace.field.target': '译文',
|
|
8230
|
+
'toast.replaced': '已替换 {count} 处',
|
|
8231
|
+
'toast.replaceUndone': '已撤销替换',
|
|
8232
|
+
'palette.title': '命令面板',
|
|
8233
|
+
'palette.placeholder': '输入命令…',
|
|
8234
|
+
'palette.empty': '没有匹配的命令。',
|
|
8235
|
+
'palette.footer': '↑↓ 选择 · Enter 执行 · Esc 关闭 · Ctrl+K 打开',
|
|
8236
|
+
'cmd.new': '新建文档',
|
|
8237
|
+
'cmd.newHint': '从文本或文件开始',
|
|
8238
|
+
'cmd.import': '导入文件',
|
|
8239
|
+
'cmd.export': '导出',
|
|
8240
|
+
'cmd.pretranslate': '预翻译',
|
|
8241
|
+
'cmd.replace': '查找替换',
|
|
8242
|
+
'cmd.brief': '文档 brief',
|
|
8243
|
+
'cmd.resources': '文档资源',
|
|
8244
|
+
'cmd.analysis': '文档分析',
|
|
8245
|
+
'cmd.clearPretranslated': '清理预翻译',
|
|
8246
|
+
'cmd.notes': '句段备注',
|
|
8247
|
+
'cmd.review': 'AI 审校当前文档',
|
|
8248
|
+
'editor.notes': '备注',
|
|
8249
|
+
'notes.title': '句段备注',
|
|
8250
|
+
'notes.hint': '备注只保存在本文档,不会写入 TM。',
|
|
8251
|
+
'notes.empty': '还没有备注。',
|
|
8252
|
+
'notes.placeholder': '写下审校说明、术语疑问或给 agent 的提示…',
|
|
8253
|
+
'notes.add': '添加备注',
|
|
8254
|
+
'notes.save': '保存',
|
|
8255
|
+
'notes.cancel': '取消',
|
|
8256
|
+
'notes.edit': '编辑',
|
|
8257
|
+
'notes.delete': '删除',
|
|
8258
|
+
'notes.user': '你',
|
|
8259
|
+
'toast.noteSaved': '备注已保存',
|
|
8260
|
+
'toast.noteDeleted': '备注已删除',
|
|
8261
|
+
'cmd.documents': '文档工作区',
|
|
8262
|
+
'cmd.libraries': '库管理',
|
|
8263
|
+
'cmd.align': '对齐',
|
|
8264
|
+
'cmd.settings': '设置',
|
|
8265
|
+
})
|
|
8266
|
+
Object.assign(EN, {
|
|
8267
|
+
'panel.qa': 'Checks',
|
|
8268
|
+
'panel.concordance': 'Concordance',
|
|
8269
|
+
'panel.concordancePlaceholder': 'Search translation memory…',
|
|
8270
|
+
'panel.concordanceHint': 'Search source or target across mounted memories; click a result to use its target in the current segment.',
|
|
8271
|
+
'panel.noConcordance': 'No matches.',
|
|
8272
|
+
'panel.searchConcordance': 'Search',
|
|
8273
|
+
'panel.brief': 'Document brief',
|
|
8274
|
+
'panel.briefHint': 'Domain, audience, tone and terminology guidance for translators and agents.',
|
|
8275
|
+
'panel.termForbidden': 'Forbidden',
|
|
8276
|
+
'panel.termPreferred': 'Required',
|
|
8277
|
+
'brief.title': 'Document brief',
|
|
8278
|
+
'brief.hint': 'Injected into pre-translation and agent context. Empty fields mean no constraint.',
|
|
8279
|
+
'brief.domain': 'Domain',
|
|
8280
|
+
'brief.audience': 'Audience',
|
|
8281
|
+
'brief.tone': 'Tone',
|
|
8282
|
+
'brief.notes': 'Notes',
|
|
8283
|
+
'brief.doNotTranslate': 'Do not translate',
|
|
8284
|
+
'brief.styleSample': 'Style sample',
|
|
8285
|
+
'brief.save': 'Save brief',
|
|
8286
|
+
'brief.saved': 'Brief saved',
|
|
8287
|
+
'brief.empty': 'No brief yet',
|
|
8288
|
+
'brief.placeholder.domain': 'e.g. legal, medical, games',
|
|
8289
|
+
'brief.placeholder.audience': 'e.g. end users, developers',
|
|
8290
|
+
'brief.placeholder.tone': 'e.g. formal, concise, conversational',
|
|
8291
|
+
'brief.placeholder.notes': 'Terminology preferences, formatting requirements, background…',
|
|
8292
|
+
'brief.placeholder.doNotTranslate': 'One term or phrase per line',
|
|
8293
|
+
'brief.placeholder.styleSample': 'Paste a sample of the desired style…',
|
|
8294
|
+
'editor.unconfirm': 'Unconfirm',
|
|
8295
|
+
'editor.unconfirmHint': 'Return to review without changing translation memory',
|
|
8296
|
+
'editor.undo': 'Undo edit',
|
|
8297
|
+
'editor.redo': 'Redo edit',
|
|
8298
|
+
'editor.lock': 'Lock',
|
|
8299
|
+
'editor.unlock': 'Unlock',
|
|
8300
|
+
'editor.locked': 'Locked',
|
|
8301
|
+
'toast.locked': 'Segment locked',
|
|
8302
|
+
'toast.unlocked': 'Segment unlocked',
|
|
8303
|
+
'editor.brief': 'Brief',
|
|
8304
|
+
'editor.risk': 'Risk',
|
|
8305
|
+
'qa.title': 'Checks',
|
|
8306
|
+
'qa.refresh': 'Re-run checks',
|
|
8307
|
+
'qa.empty': 'No issues found.',
|
|
8308
|
+
'qa.loading': 'Running checks…',
|
|
8309
|
+
'qa.review': 'AI review',
|
|
8310
|
+
'qa.reviewing': 'Reviewing…',
|
|
8311
|
+
'qa.reviewTitle': 'AI review suggestions',
|
|
8312
|
+
'qa.reviewEmpty': 'The model found no sentence worth changing.',
|
|
8313
|
+
'qa.reviewApply': 'Use suggestion',
|
|
8314
|
+
'qa.high': 'High',
|
|
8315
|
+
'qa.medium': 'Medium',
|
|
8316
|
+
'qa.low': 'Low',
|
|
8317
|
+
'qa.jump': 'Jump to segment',
|
|
8318
|
+
'qa.risk': 'Risk',
|
|
8319
|
+
'qa.code.TERM_FORBIDDEN': 'Forbidden term',
|
|
8320
|
+
'qa.code.LENGTH_DRIFT': 'Length drift',
|
|
8321
|
+
'qa.code.SUBTITLE_OVERLAP': 'Subtitle overlap',
|
|
8322
|
+
'qa.fixCopy': 'Copy source',
|
|
8323
|
+
'qa.fixClear': 'Clear target',
|
|
8324
|
+
'qa.fixTerm': 'Insert term',
|
|
8325
|
+
'toast.qaFixed': 'Segment fixed; checks refreshed',
|
|
8326
|
+
'toast.unconfirmed': 'Confirmation revoked; editing is enabled again',
|
|
8327
|
+
'toast.briefSaved': 'Brief saved',
|
|
8328
|
+
'toast.termForbidden': 'This is a forbidden term and cannot be inserted',
|
|
8329
|
+
'editor.resources': 'Resources',
|
|
8330
|
+
'resources.title': 'Document resources',
|
|
8331
|
+
'resources.hint': 'Limit which translation memories and glossaries this document reads and writes. Without inheritance, only the selected libraries are used.',
|
|
8332
|
+
'resources.inheritTm': 'Inherit global TM mounts',
|
|
8333
|
+
'resources.inheritGlossary': 'Inherit global glossary mounts',
|
|
8334
|
+
'resources.tm': 'Translation memories',
|
|
8335
|
+
'resources.glossary': 'Glossaries',
|
|
8336
|
+
'resources.defaultWrite': 'Default write',
|
|
8337
|
+
'resources.followDefault': 'Follow global default',
|
|
8338
|
+
'resources.firstBound': 'First bound library',
|
|
8339
|
+
'resources.none': 'None (no read, no write)',
|
|
8340
|
+
'resources.empty': 'No libraries available.',
|
|
8341
|
+
'resources.save': 'Save resources',
|
|
8342
|
+
'toast.resourcesSaved': 'Document resources saved',
|
|
8343
|
+
'editor.analysis': 'Analysis',
|
|
8344
|
+
'analysis.title': 'Document analysis',
|
|
8345
|
+
'analysis.hint': 'Counted against the memory bound to this document; match rate is a quoting aid.',
|
|
8346
|
+
'analysis.words': 'Words',
|
|
8347
|
+
'analysis.characters': 'Characters',
|
|
8348
|
+
'analysis.segments': 'Segments',
|
|
8349
|
+
'analysis.duplicates': 'Duplicate segments',
|
|
8350
|
+
'analysis.translated': 'With target',
|
|
8351
|
+
'analysis.confirmed': 'Confirmed',
|
|
8352
|
+
'analysis.ice': 'ICE / context',
|
|
8353
|
+
'analysis.exact': 'Exact',
|
|
8354
|
+
'analysis.fuzzy85': '85–99%',
|
|
8355
|
+
'analysis.fuzzy70': '70–84%',
|
|
8356
|
+
'analysis.new': 'New',
|
|
8357
|
+
'analysis.matchRate': 'Match rate',
|
|
8358
|
+
'analysis.leverage': 'Weighted leverage',
|
|
8359
|
+
'analysis.exportCsv': 'Export CSV',
|
|
8360
|
+
'analysis.exportJson': 'Export JSON',
|
|
8361
|
+
'analysis.close': 'Close',
|
|
8362
|
+
'replace.open': 'Find & replace',
|
|
8363
|
+
'replace.title': 'Find & replace',
|
|
8364
|
+
'replace.hint': 'Find and replace in source or target. Preview the impact before applying.',
|
|
8365
|
+
'replace.query': 'Find',
|
|
8366
|
+
'replace.queryPlaceholder': 'Text to find',
|
|
8367
|
+
'replace.replacement': 'Replace with',
|
|
8368
|
+
'replace.replacementPlaceholder': 'Leave empty to delete',
|
|
8369
|
+
'replace.scope': 'Scope',
|
|
8370
|
+
'replace.scope.target': 'Target only',
|
|
8371
|
+
'replace.scope.source': 'Source only',
|
|
8372
|
+
'replace.scope.both': 'Source and target',
|
|
8373
|
+
'replace.matchCase': 'Match case',
|
|
8374
|
+
'replace.regex': 'Regular expression',
|
|
8375
|
+
'replace.preview': 'Preview',
|
|
8376
|
+
'replace.apply': 'Apply replacement',
|
|
8377
|
+
'replace.undo': 'Undo last replacement',
|
|
8378
|
+
'replace.results': '{count} matches across {segments} segments',
|
|
8379
|
+
'replace.truncated': 'Too many matches; showing the first 200.',
|
|
8380
|
+
'replace.noResults': 'No matches.',
|
|
8381
|
+
'replace.field.source': 'Source',
|
|
8382
|
+
'replace.field.target': 'Target',
|
|
8383
|
+
'toast.replaced': 'Replaced {count} occurrences',
|
|
8384
|
+
'toast.replaceUndone': 'Replacement undone',
|
|
8385
|
+
'palette.title': 'Command palette',
|
|
8386
|
+
'palette.placeholder': 'Type a command…',
|
|
8387
|
+
'palette.empty': 'No matching commands.',
|
|
8388
|
+
'palette.footer': '↑↓ select · Enter run · Esc close · Ctrl+K open',
|
|
8389
|
+
'cmd.new': 'New document',
|
|
8390
|
+
'cmd.newHint': 'Start from text or a file',
|
|
8391
|
+
'cmd.import': 'Import file',
|
|
8392
|
+
'cmd.export': 'Export',
|
|
8393
|
+
'cmd.pretranslate': 'Pre-translate',
|
|
8394
|
+
'cmd.replace': 'Find & replace',
|
|
8395
|
+
'cmd.brief': 'Document brief',
|
|
8396
|
+
'cmd.resources': 'Document resources',
|
|
8397
|
+
'cmd.analysis': 'Document analysis',
|
|
8398
|
+
'cmd.clearPretranslated': 'Clear pre-translated targets',
|
|
8399
|
+
'cmd.notes': 'Segment notes',
|
|
8400
|
+
'cmd.review': 'AI review this document',
|
|
8401
|
+
'editor.notes': 'Notes',
|
|
8402
|
+
'notes.title': 'Segment notes',
|
|
8403
|
+
'notes.hint': 'Notes stay in this document and are never written to TM.',
|
|
8404
|
+
'notes.empty': 'No notes yet.',
|
|
8405
|
+
'notes.placeholder': 'Review comments, terminology questions, or hints for the agent…',
|
|
8406
|
+
'notes.add': 'Add note',
|
|
8407
|
+
'notes.save': 'Save',
|
|
8408
|
+
'notes.cancel': 'Cancel',
|
|
8409
|
+
'notes.edit': 'Edit',
|
|
8410
|
+
'notes.delete': 'Delete',
|
|
8411
|
+
'notes.user': 'You',
|
|
8412
|
+
'toast.noteSaved': 'Note saved',
|
|
8413
|
+
'toast.noteDeleted': 'Note deleted',
|
|
8414
|
+
'cmd.documents': 'Document workspace',
|
|
8415
|
+
'cmd.libraries': 'Library manager',
|
|
8416
|
+
'cmd.align': 'Alignment',
|
|
8417
|
+
'cmd.settings': 'Settings',
|
|
8418
|
+
})
|
|
8419
|
+
Object.assign(JA, {
|
|
8420
|
+
'panel.qa': 'チェック',
|
|
8421
|
+
'panel.concordance': '検索',
|
|
8422
|
+
'panel.concordancePlaceholder': '翻訳メモリを検索…',
|
|
8423
|
+
'panel.concordanceHint': 'マウント中の TM を原文・訳文で検索し、結果をクリックすると現在のセグメントの訳文に書き込みます。',
|
|
8424
|
+
'panel.noConcordance': '一致なし。',
|
|
8425
|
+
'panel.searchConcordance': '検索',
|
|
8426
|
+
'panel.brief': '文書ブリーフ',
|
|
8427
|
+
'panel.briefHint': '译者と agent のための分野・読者・トーン・用語指示。',
|
|
8428
|
+
'panel.termForbidden': '禁止',
|
|
8429
|
+
'panel.termPreferred': '必須',
|
|
8430
|
+
'brief.title': '文書ブリーフ',
|
|
8431
|
+
'brief.hint': 'この情報は事前翻訳と agent のコンテキストに渡されます。空欄は制約なし。',
|
|
8432
|
+
'brief.domain': '分野',
|
|
8433
|
+
'brief.audience': '読者',
|
|
8434
|
+
'brief.tone': 'トーン',
|
|
8435
|
+
'brief.notes': '補足',
|
|
8436
|
+
'brief.doNotTranslate': '翻訳しない語句',
|
|
8437
|
+
'brief.styleSample': 'スタイル例',
|
|
8438
|
+
'brief.save': 'ブリーフを保存',
|
|
8439
|
+
'brief.saved': 'ブリーフを保存しました',
|
|
8440
|
+
'brief.empty': 'ブリーフは未入力です',
|
|
8441
|
+
'brief.placeholder.domain': '例:法律、医療、ゲーム',
|
|
8442
|
+
'brief.placeholder.audience': '例:エンドユーザー、開発者',
|
|
8443
|
+
'brief.placeholder.tone': '例:正式、簡潔、会話的',
|
|
8444
|
+
'brief.placeholder.notes': '用語の好み、書式、背景…',
|
|
8445
|
+
'brief.placeholder.doNotTranslate': '1 行に 1 語句',
|
|
8446
|
+
'brief.placeholder.styleSample': '希望するスタイルの訳文を貼り付け…',
|
|
8447
|
+
'editor.unconfirm': '確認を取り消す',
|
|
8448
|
+
'editor.unconfirmHint': '確認だけを解除し、TM は変更しません',
|
|
8449
|
+
'editor.undo': '編集を元に戻す',
|
|
8450
|
+
'editor.redo': '編集をやり直す',
|
|
8451
|
+
'editor.lock': 'ロック',
|
|
8452
|
+
'editor.unlock': 'ロック解除',
|
|
8453
|
+
'editor.locked': 'ロック中',
|
|
8454
|
+
'toast.locked': 'セグメントをロックしました',
|
|
8455
|
+
'toast.unlocked': 'セグメントのロックを解除しました',
|
|
8456
|
+
'editor.brief': 'ブリーフ',
|
|
8457
|
+
'editor.risk': 'リスク',
|
|
8458
|
+
'qa.title': 'チェック',
|
|
8459
|
+
'qa.refresh': '再チェック',
|
|
8460
|
+
'qa.empty': '問題はありません。',
|
|
8461
|
+
'qa.loading': 'チェック中…',
|
|
8462
|
+
'qa.review': 'AI 校閲',
|
|
8463
|
+
'qa.reviewing': '校閲中…',
|
|
8464
|
+
'qa.reviewTitle': 'AI 校閲の提案',
|
|
8465
|
+
'qa.reviewEmpty': 'モデルが修正すべき文を見つけませんでした。',
|
|
8466
|
+
'qa.reviewApply': '提案を反映',
|
|
8467
|
+
'qa.high': '高',
|
|
8468
|
+
'qa.medium': '中',
|
|
8469
|
+
'qa.low': '低',
|
|
8470
|
+
'qa.jump': 'セグメントへ',
|
|
8471
|
+
'qa.risk': 'リスク',
|
|
8472
|
+
'qa.code.TERM_FORBIDDEN': '禁止用語',
|
|
8473
|
+
'qa.code.LENGTH_DRIFT': '長さの不一致',
|
|
8474
|
+
'qa.code.SUBTITLE_OVERLAP': '字幕の重なり',
|
|
8475
|
+
'qa.fixCopy': '原文をコピー',
|
|
8476
|
+
'qa.fixClear': '訳文を空にする',
|
|
8477
|
+
'qa.fixTerm': '用語を挿入',
|
|
8478
|
+
'toast.qaFixed': '修正しました。チェックを更新しました',
|
|
8479
|
+
'toast.unconfirmed': '確認を取り消しました。編集を続けられます',
|
|
8480
|
+
'toast.briefSaved': 'ブリーフを保存しました',
|
|
8481
|
+
'toast.termForbidden': '禁止用語のため挿入できません',
|
|
8482
|
+
'editor.resources': 'リソース',
|
|
8483
|
+
'resources.title': '文書リソース',
|
|
8484
|
+
'resources.hint': 'この文書が読み書きする TM と用語集を限定します。「グローバルを継承」を外すと、下で選んだライブラリだけを使います。',
|
|
8485
|
+
'resources.inheritTm': 'グローバル TM マウントを継承',
|
|
8486
|
+
'resources.inheritGlossary': 'グローバル用語集マウントを継承',
|
|
8487
|
+
'resources.tm': '翻訳メモリ',
|
|
8488
|
+
'resources.glossary': '用語集',
|
|
8489
|
+
'resources.defaultWrite': '既定の書き込み先',
|
|
8490
|
+
'resources.followDefault': 'グローバル既定に従う',
|
|
8491
|
+
'resources.firstBound': '最初のバインド先',
|
|
8492
|
+
'resources.none': '未バインド(読み書きなし)',
|
|
8493
|
+
'resources.empty': '利用できるライブラリがありません。',
|
|
8494
|
+
'resources.save': 'リソースを保存',
|
|
8495
|
+
'toast.resourcesSaved': '文書リソースを保存しました',
|
|
8496
|
+
'editor.analysis': '分析',
|
|
8497
|
+
'analysis.title': '文書分析',
|
|
8498
|
+
'analysis.hint': 'この文書に紐づく TM で集計します。マッチ率は見積もりの参考値です。',
|
|
8499
|
+
'analysis.words': '語数',
|
|
8500
|
+
'analysis.characters': '文字数',
|
|
8501
|
+
'analysis.segments': 'セグメント',
|
|
8502
|
+
'analysis.duplicates': '重複セグメント',
|
|
8503
|
+
'analysis.translated': '訳文あり',
|
|
8504
|
+
'analysis.confirmed': '確認済み',
|
|
8505
|
+
'analysis.ice': 'ICE / コンテキスト',
|
|
8506
|
+
'analysis.exact': '完全一致',
|
|
8507
|
+
'analysis.fuzzy85': '85–99%',
|
|
8508
|
+
'analysis.fuzzy70': '70–84%',
|
|
8509
|
+
'analysis.new': '新規',
|
|
8510
|
+
'analysis.matchRate': 'マッチ率',
|
|
8511
|
+
'analysis.leverage': '加重レバレッジ',
|
|
8512
|
+
'analysis.exportCsv': 'CSV を書き出す',
|
|
8513
|
+
'analysis.exportJson': 'JSON を書き出す',
|
|
8514
|
+
'analysis.close': '閉じる',
|
|
8515
|
+
'replace.open': '検索と置換',
|
|
8516
|
+
'replace.title': '検索と置換',
|
|
8517
|
+
'replace.hint': '原文または訳文を検索して置換します。適用前に影響範囲をプレビューしてください。',
|
|
8518
|
+
'replace.query': '検索',
|
|
8519
|
+
'replace.queryPlaceholder': '検索するテキスト',
|
|
8520
|
+
'replace.replacement': '置換後',
|
|
8521
|
+
'replace.replacementPlaceholder': '空欄なら削除',
|
|
8522
|
+
'replace.scope': '範囲',
|
|
8523
|
+
'replace.scope.target': '訳文のみ',
|
|
8524
|
+
'replace.scope.source': '原文のみ',
|
|
8525
|
+
'replace.scope.both': '原文と訳文',
|
|
8526
|
+
'replace.matchCase': '大文字と小文字を区別',
|
|
8527
|
+
'replace.regex': '正規表現',
|
|
8528
|
+
'replace.preview': 'プレビュー',
|
|
8529
|
+
'replace.apply': '置換を適用',
|
|
8530
|
+
'replace.undo': '直前の置換を元に戻す',
|
|
8531
|
+
'replace.results': '{count} 件、{segments} セグメント',
|
|
8532
|
+
'replace.truncated': '件数が多いため先頭 200 件のみ表示します。',
|
|
8533
|
+
'replace.noResults': '一致なし。',
|
|
8534
|
+
'replace.field.source': '原文',
|
|
8535
|
+
'replace.field.target': '訳文',
|
|
8536
|
+
'toast.replaced': '{count} 件置換しました',
|
|
8537
|
+
'toast.replaceUndone': '置換を元に戻しました',
|
|
8538
|
+
'palette.title': 'コマンドパレット',
|
|
8539
|
+
'palette.placeholder': 'コマンドを入力…',
|
|
8540
|
+
'palette.empty': '一致するコマンドがありません。',
|
|
8541
|
+
'palette.footer': '↑↓ 選択 · Enter 実行 · Esc 閉じる · Ctrl+K 開く',
|
|
8542
|
+
'cmd.new': '新規文書',
|
|
8543
|
+
'cmd.newHint': 'テキストまたはファイルから開始',
|
|
8544
|
+
'cmd.import': 'ファイルをインポート',
|
|
8545
|
+
'cmd.export': 'エクスポート',
|
|
8546
|
+
'cmd.pretranslate': '事前翻訳',
|
|
8547
|
+
'cmd.replace': '検索と置換',
|
|
8548
|
+
'cmd.brief': '文書ブリーフ',
|
|
8549
|
+
'cmd.resources': '文書リソース',
|
|
8550
|
+
'cmd.analysis': '文書分析',
|
|
8551
|
+
'cmd.clearPretranslated': '事前翻訳をクリア',
|
|
8552
|
+
'cmd.notes': 'セグメントメモ',
|
|
8553
|
+
'cmd.review': 'この文書を AI 校閲',
|
|
8554
|
+
'editor.notes': 'メモ',
|
|
8555
|
+
'notes.title': 'セグメントメモ',
|
|
8556
|
+
'notes.hint': 'メモはこの文書にのみ保存され、TM には書き込まれません。',
|
|
8557
|
+
'notes.empty': 'メモはまだありません。',
|
|
8558
|
+
'notes.placeholder': 'レビューコメント、用語の疑問、agent へのヒント…',
|
|
8559
|
+
'notes.add': 'メモを追加',
|
|
8560
|
+
'notes.save': '保存',
|
|
8561
|
+
'notes.cancel': 'キャンセル',
|
|
8562
|
+
'notes.edit': '編集',
|
|
8563
|
+
'notes.delete': '削除',
|
|
8564
|
+
'notes.user': 'あなた',
|
|
8565
|
+
'toast.noteSaved': 'メモを保存しました',
|
|
8566
|
+
'toast.noteDeleted': 'メモを削除しました',
|
|
8567
|
+
'cmd.documents': '文書ワークスペース',
|
|
8568
|
+
'cmd.libraries': 'ライブラリ管理',
|
|
8569
|
+
'cmd.align': 'アラインメント',
|
|
8570
|
+
'cmd.settings': '設定',
|
|
8571
|
+
})
|
|
8572
|
+
|
|
5888
8573
|
// UI languages offered by the in-app switch. 'auto' follows the DSH locale
|
|
5889
8574
|
// through the bound DSH translator; the other entries use the dictionaries
|
|
5890
8575
|
// above. Labels are native names, except 'auto', which is localized.
|
|
8576
|
+
Object.assign(ZH, {
|
|
8577
|
+
'editor.merge': '合并下一段',
|
|
8578
|
+
'editor.split': '拆分',
|
|
8579
|
+
'editor.mergeHint': '把当前句段和同一段落的下一句合并;合并后确认状态会重置。',
|
|
8580
|
+
'editor.splitHint': '按译文光标位置大致拆分当前句段;拆分后两侧都需要重新确认。',
|
|
8581
|
+
'toast.merged': '已合并句段',
|
|
8582
|
+
'toast.split': '已拆分句段',
|
|
8583
|
+
'modal.confirm.mergeTitle': '合并句段',
|
|
8584
|
+
'modal.confirm.merge': '合并会新建一个句段,并丢失两条原文各自的确认状态。确定继续?',
|
|
8585
|
+
'modal.confirm.splitTitle': '拆分句段',
|
|
8586
|
+
'modal.confirm.split': '拆分后两侧都会变成未确认状态,译文按比例切开。确定继续?',
|
|
8587
|
+
'editor.shift': '平移',
|
|
8588
|
+
'editor.shiftHint': '按秒平移当前文档所有字幕时间码;不能产生负时间码。',
|
|
8589
|
+
'editor.mergeCue': '合并下一条',
|
|
8590
|
+
'editor.splitCue': '按中点拆分',
|
|
8591
|
+
'toast.cuesMerged': '已合并字幕',
|
|
8592
|
+
'toast.cueSplit': '已拆分字幕',
|
|
8593
|
+
'toast.subtitlesShifted': '已平移字幕时间轴',
|
|
8594
|
+
'modal.confirm.shiftTitle': '平移字幕',
|
|
8595
|
+
'modal.confirm.shiftCue': '会平移当前文档所有字幕的时间码,且不修改译文。确定继续?',
|
|
8596
|
+
'modal.confirm.mergeCue': '合并当前字幕和下一条,时间范围取两者首尾;确定继续?',
|
|
8597
|
+
'modal.confirm.splitCue': '会在当前字幕时间范围的中点拆成两条,译文按比例切开;确定继续?',
|
|
8598
|
+
})
|
|
8599
|
+
Object.assign(EN, {
|
|
8600
|
+
'editor.merge': 'Merge next',
|
|
8601
|
+
'editor.split': 'Split',
|
|
8602
|
+
'editor.mergeHint': 'Merge this segment with the next one in the same paragraph; confirmation resets.',
|
|
8603
|
+
'editor.splitHint': 'Split at the target cursor position; both halves need confirmation again.',
|
|
8604
|
+
'toast.merged': 'Segment merged',
|
|
8605
|
+
'toast.split': 'Segment split',
|
|
8606
|
+
'modal.confirm.mergeTitle': 'Merge segments',
|
|
8607
|
+
'modal.confirm.merge': 'Merging creates a new segment and discards the confirmation state of both originals. Continue?',
|
|
8608
|
+
'modal.confirm.splitTitle': 'Split segment',
|
|
8609
|
+
'modal.confirm.split': 'Both halves become unconfirmed and the target is split proportionally. Continue?',
|
|
8610
|
+
'editor.shift': 'Shift',
|
|
8611
|
+
'editor.shiftHint': 'Shift every subtitle cue in this document by seconds; negative timecodes are rejected.',
|
|
8612
|
+
'editor.mergeCue': 'Merge next cue',
|
|
8613
|
+
'editor.splitCue': 'Split at midpoint',
|
|
8614
|
+
'toast.cuesMerged': 'Cues merged',
|
|
8615
|
+
'toast.cueSplit': 'Cue split',
|
|
8616
|
+
'toast.subtitlesShifted': 'Subtitle timeline shifted',
|
|
8617
|
+
'modal.confirm.shiftTitle': 'Shift subtitles',
|
|
8618
|
+
'modal.confirm.shiftCue': 'Every cue in this document moves by the same offset. Translations are unchanged. Continue?',
|
|
8619
|
+
'modal.confirm.mergeCue': 'Merge this cue with the next one, using the outer time range. Continue?',
|
|
8620
|
+
'modal.confirm.splitCue': 'Split this cue at its midpoint; the translation is split proportionally. Continue?',
|
|
8621
|
+
})
|
|
8622
|
+
Object.assign(JA, {
|
|
8623
|
+
'editor.merge': '次を結合',
|
|
8624
|
+
'editor.split': '分割',
|
|
8625
|
+
'editor.mergeHint': '同じ段落の次のセグメントと結合します。確認状態はリセットされます。',
|
|
8626
|
+
'editor.splitHint': '訳文のカーソル位置を目安に分割します。両側とも再確認が必要です。',
|
|
8627
|
+
'toast.merged': 'セグメントを結合しました',
|
|
8628
|
+
'toast.split': 'セグメントを分割しました',
|
|
8629
|
+
'modal.confirm.mergeTitle': 'セグメントを結合',
|
|
8630
|
+
'modal.confirm.merge': '結合すると新しいセグメントになり、元の確認状態は失われます。続行しますか?',
|
|
8631
|
+
'modal.confirm.splitTitle': 'セグメントを分割',
|
|
8632
|
+
'modal.confirm.split': '分割後は両側とも未確認になり、訳文は比率で分割されます。続行しますか?',
|
|
8633
|
+
'editor.shift': 'シフト',
|
|
8634
|
+
'editor.shiftHint': '文書内の全字幕を秒単位でシフトします。負のタイムコードは拒否されます。',
|
|
8635
|
+
'editor.mergeCue': '次のキューを結合',
|
|
8636
|
+
'editor.splitCue': '中点で分割',
|
|
8637
|
+
'toast.cuesMerged': '字幕を結合しました',
|
|
8638
|
+
'toast.cueSplit': '字幕を分割しました',
|
|
8639
|
+
'toast.subtitlesShifted': '字幕タイムラインをシフトしました',
|
|
8640
|
+
'modal.confirm.shiftTitle': '字幕をシフト',
|
|
8641
|
+
'modal.confirm.shiftCue': '文書内の全キューを同じ量だけ移動します。訳文は変更しません。続行しますか?',
|
|
8642
|
+
'modal.confirm.mergeCue': '現在のキューと次を結合し、外側の時間範囲を使います。続行しますか?',
|
|
8643
|
+
'modal.confirm.splitCue': '現在のキューの中央で二分割し、訳文を比率で分けます。続行しますか?',
|
|
8644
|
+
})
|
|
5891
8645
|
const UI_LANGS = [
|
|
5892
8646
|
['auto', ''],
|
|
5893
8647
|
['zh', '简体中文'],
|
|
@@ -5973,6 +8727,8 @@ window.__ModuleLoader__.load({
|
|
|
5973
8727
|
pageSizeOptions: PAGE_SIZE_OPTIONS,
|
|
5974
8728
|
pageSizeDefault: PAGE_SIZE_DEFAULT,
|
|
5975
8729
|
pageSizeStorageKey: PAGE_SIZE_STORAGE_KEY,
|
|
8730
|
+
defaultFuzzyThreshold: DEFAULT_FUZZY_THRESHOLD,
|
|
8731
|
+
fuzzyPercent,
|
|
5976
8732
|
cssScaled: CSS_SCALED,
|
|
5977
8733
|
syncPollMs: SYNC_POLL_MS,
|
|
5978
8734
|
makeTranslator,
|