dsh-cait 1.1.6 → 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 +43 -0
- package/README.ja.md +20 -10
- package/README.md +91 -72
- package/README.zh-CN.md +20 -10
- package/client/components.js +126 -13
- package/client/locales.js +74 -2
- package/client/styles.js +6 -2
- package/client.js +206 -17
- package/lib/aligner.js +6 -6
- package/lib/api.js +6 -0
- package/lib/docx.js +124 -26
- package/lib/import-export.js +115 -10
- package/lib/localization.js +1098 -0
- package/lib/persistence.js +14 -0
- package/lib/pptx.js +33 -1
- package/lib/qa.js +18 -0
- package/lib/store-utils.js +8 -3
- package/lib/store.js +14 -2
- package/lib/subtitles.js +213 -0
- package/lib/xlsx.js +176 -66
- package/package.json +1 -1
package/lib/import-export.js
CHANGED
|
@@ -6,14 +6,16 @@ import { stripMarkdown } from './markdown.js'
|
|
|
6
6
|
import { DOCX_MIME, buildBilingualDocx, buildDocx, buildDocxFromText, parseDocxDetailed } from './docx.js'
|
|
7
7
|
import { EPUB_MIME, buildEpub, buildEpubFromText, parseEpub } from './epub.js'
|
|
8
8
|
import { PPTX_MIME, buildPptx, parsePptxDetailed } from './pptx.js'
|
|
9
|
-
import { XLSX_MIME, buildXlsx, parseXlsx } from './xlsx.js'
|
|
9
|
+
import { XLSX_MIME, buildXlsx, parseXlsx, patchXlsx } from './xlsx.js'
|
|
10
10
|
import { buildTmx, parseTmx } from './tmx.js'
|
|
11
11
|
import { buildHtml, buildJson, buildPo, buildRtf, buildSrt, buildVtt, buildXliff, buildXliff2, buildXliffReview, parseCsv, parseHtml, parseJson, parsePo, parseRtf, parseSrt, parseVtt, parseXliff } from './formats.js'
|
|
12
|
+
import { LOCALIZATION_EXPORT, LOCALIZATION_EXTENSIONS, buildLocalization, parseLocalization } from './localization.js'
|
|
12
13
|
import { decodeXml } from './xml.js'
|
|
13
14
|
|
|
14
15
|
const BINARY_SOURCES = {
|
|
15
16
|
docx: { label: 'DOCX', fallback: 'source.docx' },
|
|
16
17
|
pptx: { label: 'PPTX', fallback: 'source.pptx' },
|
|
18
|
+
xlsx: { label: 'XLSX', fallback: 'source.xlsx' },
|
|
17
19
|
epub: { label: 'EPUB', fallback: 'source.epub' },
|
|
18
20
|
}
|
|
19
21
|
|
|
@@ -143,6 +145,18 @@ export const importExportMethods = {
|
|
|
143
145
|
return readSource(this, doc, 'pptx')
|
|
144
146
|
},
|
|
145
147
|
|
|
148
|
+
attachXlsxSource(doc, buffer, name = 'source.xlsx') {
|
|
149
|
+
attachSource(this, doc, 'xlsx', buffer, name)
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
removeXlsxSource(doc) {
|
|
153
|
+
removeSource(this, doc, 'xlsx')
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
readXlsxSource(doc) {
|
|
157
|
+
return readSource(this, doc, 'xlsx')
|
|
158
|
+
},
|
|
159
|
+
|
|
146
160
|
attachEpubSource(doc, buffer, name = 'source.epub') {
|
|
147
161
|
attachSource(this, doc, 'epub', buffer, name)
|
|
148
162
|
},
|
|
@@ -333,11 +347,13 @@ export const importExportMethods = {
|
|
|
333
347
|
name: fileName,
|
|
334
348
|
text: content.text,
|
|
335
349
|
paragraphMap: content.paragraphs.map((paragraph) => paragraph.index),
|
|
350
|
+
paragraphMeta: Object.fromEntries(content.paragraphs.map((paragraph) => [paragraph.index, paragraph.meta])),
|
|
336
351
|
docxSource: buffer,
|
|
337
352
|
docxName: fileName,
|
|
338
353
|
sourceLang,
|
|
339
354
|
targetLang,
|
|
340
355
|
format: 'docx',
|
|
356
|
+
formatMeta: content.formatMeta,
|
|
341
357
|
})
|
|
342
358
|
}
|
|
343
359
|
|
|
@@ -391,6 +407,7 @@ export const importExportMethods = {
|
|
|
391
407
|
targetLang,
|
|
392
408
|
format: extension,
|
|
393
409
|
...(extension === 'pptx' ? { pptxSource: buffer, pptxName: fileName } : {}),
|
|
410
|
+
...(extension === 'xlsx' ? { xlsxSource: buffer, xlsxName: fileName } : {}),
|
|
394
411
|
})
|
|
395
412
|
return {
|
|
396
413
|
...created,
|
|
@@ -411,6 +428,49 @@ export const importExportMethods = {
|
|
|
411
428
|
})
|
|
412
429
|
}
|
|
413
430
|
|
|
431
|
+
let localizationKind = LOCALIZATION_EXTENSIONS[extension]
|
|
432
|
+
if (!localizationKind && extension === 'xml' && /<resources\b/i.test(String(text ?? ''))) {
|
|
433
|
+
localizationKind = 'android'
|
|
434
|
+
}
|
|
435
|
+
if (localizationKind) {
|
|
436
|
+
const sourceText = String(text ?? '')
|
|
437
|
+
if (!sourceText.trim()) throw new CatError('EMPTY_FILE', '文件内容为空。')
|
|
438
|
+
if (sourceText.length > MAX_IMPORT_CHARS) throw new CatError('FILE_TOO_LARGE', '文件过大。')
|
|
439
|
+
let parsed
|
|
440
|
+
try {
|
|
441
|
+
parsed = parseLocalization(sourceText, localizationKind)
|
|
442
|
+
} catch (error) {
|
|
443
|
+
if (error instanceof CatError) throw error
|
|
444
|
+
throw new CatError(
|
|
445
|
+
`${localizationKind.toUpperCase()}_PARSE`,
|
|
446
|
+
`${localizationKind} 解析失败: ${error?.message ?? error}`,
|
|
447
|
+
)
|
|
448
|
+
}
|
|
449
|
+
const src = cleanLang(
|
|
450
|
+
sourceLang || parsed.sourceLang || this.settings.sourceLang,
|
|
451
|
+
this.settings.sourceLang,
|
|
452
|
+
)
|
|
453
|
+
const tgt = cleanLang(
|
|
454
|
+
targetLang || parsed.targetLang || this.settings.targetLang,
|
|
455
|
+
this.settings.targetLang,
|
|
456
|
+
)
|
|
457
|
+
const created = this.createDocument({
|
|
458
|
+
name: fileName,
|
|
459
|
+
segments: parsed.segments,
|
|
460
|
+
sourceLang: src,
|
|
461
|
+
targetLang: tgt,
|
|
462
|
+
format: parsed.format || localizationKind,
|
|
463
|
+
formatMeta: parsed.formatMeta,
|
|
464
|
+
})
|
|
465
|
+
return {
|
|
466
|
+
...created,
|
|
467
|
+
sourceLang: src,
|
|
468
|
+
targetLang: tgt,
|
|
469
|
+
units: parsed.segments.length,
|
|
470
|
+
warnings: parsed.warnings ?? [],
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
414
474
|
const structured = {
|
|
415
475
|
csv: { parser: parseCsv, format: 'csv' },
|
|
416
476
|
srt: { parser: parseSrt, format: 'srt' },
|
|
@@ -507,13 +567,26 @@ export const importExportMethods = {
|
|
|
507
567
|
|
|
508
568
|
if (format === 'docx') {
|
|
509
569
|
const grouped = new Map()
|
|
510
|
-
|
|
511
|
-
|
|
570
|
+
const byPart = new Map()
|
|
571
|
+
let hasPartMeta = false
|
|
572
|
+
for (const [index, segment] of doc.segments.entries()) {
|
|
573
|
+
const meta = segment.meta && typeof segment.meta === 'object' ? segment.meta : null
|
|
574
|
+
const part = meta?.docxPart || 'word/document.xml'
|
|
575
|
+
const ordinal = Number.isInteger(meta?.docxParagraph)
|
|
576
|
+
? meta.docxParagraph
|
|
577
|
+
: Number.isInteger(segment.paragraph)
|
|
578
|
+
? segment.paragraph
|
|
579
|
+
: index
|
|
580
|
+
if (meta?.docxPart && Number.isInteger(meta?.docxParagraph)) hasPartMeta = true
|
|
581
|
+
const key = `${part}\u0000${ordinal}`
|
|
512
582
|
if (!grouped.has(key)) grouped.set(key, [])
|
|
513
583
|
grouped.get(key).push(segment)
|
|
584
|
+
if (!byPart.has(part)) byPart.set(part, new Map())
|
|
585
|
+
const partMap = byPart.get(part)
|
|
586
|
+
if (!partMap.has(ordinal)) partMap.set(ordinal, [])
|
|
587
|
+
partMap.get(ordinal).push(segment)
|
|
514
588
|
}
|
|
515
|
-
const
|
|
516
|
-
for (const [key, segments] of grouped) {
|
|
589
|
+
const combine = (segments) => {
|
|
517
590
|
const translated = segments
|
|
518
591
|
.map((segment) => segment.target)
|
|
519
592
|
.filter(Boolean)
|
|
@@ -524,22 +597,42 @@ export const importExportMethods = {
|
|
|
524
597
|
.filter(Boolean)
|
|
525
598
|
.join(' ')
|
|
526
599
|
.trim()
|
|
527
|
-
|
|
600
|
+
return translated || fallback
|
|
601
|
+
}
|
|
602
|
+
const replacements = new Map()
|
|
603
|
+
for (const [key, segments] of grouped) {
|
|
604
|
+
const ordinal = Number(key.split('\u0000')[1])
|
|
605
|
+
const text = combine(segments)
|
|
606
|
+
if (text) replacements.set(ordinal, text)
|
|
607
|
+
}
|
|
608
|
+
for (const [part, partMap] of byPart) {
|
|
609
|
+
for (const [ordinal, segments] of partMap) {
|
|
610
|
+
const text = combine(segments)
|
|
611
|
+
if (text) partMap.set(ordinal, text)
|
|
612
|
+
else partMap.delete(ordinal)
|
|
613
|
+
}
|
|
528
614
|
}
|
|
529
615
|
|
|
530
616
|
let content = null
|
|
531
617
|
const template = this.readDocxSource(doc)
|
|
532
618
|
if (template) {
|
|
533
619
|
try {
|
|
534
|
-
content =
|
|
620
|
+
content = hasPartMeta
|
|
621
|
+
? buildDocx(template, null, byPart)
|
|
622
|
+
: buildDocx(template, replacements)
|
|
535
623
|
} catch (error) {
|
|
536
624
|
this.logger?.warn?.(`CAIT: DOCX 模板套用失败,改用纯文本 DOCX: ${error.message}`)
|
|
537
625
|
}
|
|
538
626
|
}
|
|
539
627
|
if (!content) {
|
|
540
628
|
const paragraphs = [...grouped.keys()]
|
|
541
|
-
.sort((left, right) =>
|
|
542
|
-
|
|
629
|
+
.sort((left, right) => {
|
|
630
|
+
const [leftPart, leftOrdinal] = left.split('\u0000')
|
|
631
|
+
const [rightPart, rightOrdinal] = right.split('\u0000')
|
|
632
|
+
if (leftPart !== rightPart) return leftPart.localeCompare(rightPart)
|
|
633
|
+
return Number(leftOrdinal) - Number(rightOrdinal)
|
|
634
|
+
})
|
|
635
|
+
.map((key) => replacements.get(Number(key.split('\u0000')[1])))
|
|
543
636
|
.filter(Boolean)
|
|
544
637
|
content = buildDocxFromText(paragraphs)
|
|
545
638
|
}
|
|
@@ -583,6 +676,16 @@ export const importExportMethods = {
|
|
|
583
676
|
}
|
|
584
677
|
}
|
|
585
678
|
|
|
679
|
+
const localizationExport = LOCALIZATION_EXPORT[format]
|
|
680
|
+
if (localizationExport) {
|
|
681
|
+
return {
|
|
682
|
+
filename: `${base}.${doc.targetLang}.${localizationExport.extension}`,
|
|
683
|
+
mime: localizationExport.mime,
|
|
684
|
+
content: buildLocalization(doc, format),
|
|
685
|
+
count: doc.segments.length,
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
586
689
|
if (format === 'srt') {
|
|
587
690
|
return {
|
|
588
691
|
filename: `${base}.${doc.targetLang}.srt`,
|
|
@@ -668,10 +771,12 @@ export const importExportMethods = {
|
|
|
668
771
|
}
|
|
669
772
|
|
|
670
773
|
if (format === 'xlsx') {
|
|
774
|
+
const source = this.readXlsxSource(doc)
|
|
775
|
+
const patched = source ? patchXlsx(doc, source) : null
|
|
671
776
|
return {
|
|
672
777
|
filename: `${base}.${doc.targetLang}.xlsx`,
|
|
673
778
|
mime: XLSX_MIME,
|
|
674
|
-
content: buildXlsx(doc),
|
|
779
|
+
content: patched ? patched.buffer : buildXlsx(doc),
|
|
675
780
|
count: doc.segments.length,
|
|
676
781
|
}
|
|
677
782
|
}
|