feat: align bilingual post phrases
This commit is contained in:
@@ -359,31 +359,41 @@ const shouldKeepEnglishPeriod = (text: string, index: number) => {
|
||||
return /^[A-Z]$/.test(token) && /[A-Za-z]/.test(nextNonSpace)
|
||||
}
|
||||
|
||||
const splitSentenceLine = (text: string) => {
|
||||
const sentences: string[] = []
|
||||
const isPhraseDelimiter = (character: string) =>
|
||||
/[,,;;。!?!?]/.test(character) || character === '.'
|
||||
|
||||
const shouldKeepPhraseDelimiter = (text: string, index: number, character: string) => {
|
||||
if (character === '.' && shouldKeepEnglishPeriod(text, index)) return true
|
||||
|
||||
const previous = text[index - 1] || ''
|
||||
const next = text[index + 1] || ''
|
||||
return /[,,]/.test(character) && /\d/.test(previous) && /\d/.test(next)
|
||||
}
|
||||
|
||||
const splitPhraseLine = (text: string) => {
|
||||
const phrases: string[] = []
|
||||
let start = 0
|
||||
|
||||
for (let index = 0; index < text.length; index += 1) {
|
||||
const character = text[index]
|
||||
const isStop = /[。!?!?]/.test(character) || character === '.'
|
||||
if (!isStop || (character === '.' && shouldKeepEnglishPeriod(text, index))) continue
|
||||
if (!isPhraseDelimiter(character) || shouldKeepPhraseDelimiter(text, index, character)) continue
|
||||
|
||||
let end = index + 1
|
||||
while (end < text.length && /[。!?.!?]/.test(text[end])) end += 1
|
||||
while (end < text.length && isPhraseDelimiter(text[end])) end += 1
|
||||
while (end < text.length && /[”’"'))】\]]/.test(text[end])) end += 1
|
||||
|
||||
const sentence = text.slice(start, end).trim()
|
||||
if (sentence) sentences.push(sentence)
|
||||
const phrase = text.slice(start, end).trim()
|
||||
if (phrase) phrases.push(phrase)
|
||||
start = end
|
||||
index = end - 1
|
||||
}
|
||||
|
||||
const tail = text.slice(start).trim()
|
||||
if (tail) sentences.push(tail)
|
||||
return sentences
|
||||
if (tail) phrases.push(tail)
|
||||
return phrases
|
||||
}
|
||||
|
||||
const splitContentLines = (value: unknown) => {
|
||||
const splitContentSegments = (value: unknown) => {
|
||||
const normalized = String(value || '')
|
||||
.replace(/\r\n/g, '\n')
|
||||
.replace(/\r/g, '\n')
|
||||
@@ -395,22 +405,64 @@ const splitContentLines = (value: unknown) => {
|
||||
.map((line) => line.replace(/\s+/g, ' ').trim())
|
||||
.filter(Boolean)
|
||||
|
||||
const sentenceLines = manualLines.flatMap((line) => splitSentenceLine(line))
|
||||
const segments = manualLines.flatMap((line) => splitPhraseLine(line))
|
||||
return segments.length ? segments : [normalized]
|
||||
}
|
||||
|
||||
return sentenceLines.length ? sentenceLines : [normalized]
|
||||
const getSegmentBodyLength = (segment: string) =>
|
||||
segment.replace(/[,,;;。!?.!?]/g, '').replace(/\s+/g, '').length
|
||||
|
||||
const joinSegments = (left: string, right: string) => {
|
||||
const normalizedLeft = left.trim()
|
||||
const normalizedRight = right.trim()
|
||||
const leftBodyEnd = normalizedLeft
|
||||
.replace(/[”’"'))】\]]+$/g, '')
|
||||
.replace(/[,,;;。!?.!?]+$/g, '')
|
||||
const needsSpace = /[A-Za-z0-9]$/.test(leftBodyEnd) && /^[A-Za-z0-9]/.test(normalizedRight)
|
||||
return `${normalizedLeft}${needsSpace ? ' ' : ''}${normalizedRight}`
|
||||
}
|
||||
|
||||
const mergeSegmentsToCount = (segments: string[], targetCount: number) => {
|
||||
const merged = segments.filter(Boolean).slice()
|
||||
if (targetCount <= 0) return []
|
||||
|
||||
while (merged.length > targetCount) {
|
||||
let shortestIndex = 0
|
||||
for (let index = 1; index < merged.length; index += 1) {
|
||||
if (getSegmentBodyLength(merged[index]) < getSegmentBodyLength(merged[shortestIndex])) {
|
||||
shortestIndex = index
|
||||
}
|
||||
}
|
||||
|
||||
const ending = merged[shortestIndex].replace(/[”’"'))】\]]+$/g, '').slice(-1)
|
||||
const mergeForward = shortestIndex === 0
|
||||
|| (shortestIndex < merged.length - 1 && /[,,;;]/.test(ending))
|
||||
|
||||
if (mergeForward) {
|
||||
merged.splice(shortestIndex, 2, joinSegments(merged[shortestIndex], merged[shortestIndex + 1]))
|
||||
} else {
|
||||
merged.splice(shortestIndex - 1, 2, joinSegments(merged[shortestIndex - 1], merged[shortestIndex]))
|
||||
}
|
||||
}
|
||||
|
||||
return merged
|
||||
}
|
||||
|
||||
const postPreview = computed(() => splitPostPreview(post.value?.content))
|
||||
const originalLines = computed(() => splitContentLines(post.value?.content))
|
||||
const translatedLines = computed(() => splitContentLines(translatedText.value))
|
||||
const originalSegments = computed(() => splitContentSegments(post.value?.content))
|
||||
const translatedSegments = computed(() => splitContentSegments(translatedText.value))
|
||||
const bilingualBlocks = computed(() => {
|
||||
const size = Math.max(originalLines.value.length, translatedLines.value.length)
|
||||
return Array.from({ length: size }, (_, index) => ({
|
||||
original: originalLines.value[index] || '',
|
||||
translated: translatedLines.value[index] || ''
|
||||
})).filter((block) => block.original || block.translated)
|
||||
const pairCount = Math.min(originalSegments.value.length, translatedSegments.value.length)
|
||||
if (!pairCount) return []
|
||||
|
||||
const alignedOriginal = mergeSegmentsToCount(originalSegments.value, pairCount)
|
||||
const alignedTranslation = mergeSegmentsToCount(translatedSegments.value, pairCount)
|
||||
return Array.from({ length: pairCount }, (_, index) => ({
|
||||
original: alignedOriginal[index],
|
||||
translated: alignedTranslation[index]
|
||||
}))
|
||||
})
|
||||
const hasBilingualContent = computed(() => originalLines.value.length > 0 && translatedLines.value.length > 0)
|
||||
const hasBilingualContent = computed(() => bilingualBlocks.value.length > 0)
|
||||
const previewCanExpand = computed(() => {
|
||||
if (hasBilingualContent.value) return bilingualBlocks.value.length > 1
|
||||
|
||||
|
||||
Reference in New Issue
Block a user