feat: add translation support for polls
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { POLL_TYPE } from '@/constants'
|
import { POLL_TYPE } from '@/constants'
|
||||||
|
import { useTranslatedEvent } from '@/hooks'
|
||||||
import { useFetchPollResults } from '@/hooks/useFetchPollResults'
|
import { useFetchPollResults } from '@/hooks/useFetchPollResults'
|
||||||
import { createPollResponseDraftEvent } from '@/lib/draft-event'
|
import { createPollResponseDraftEvent } from '@/lib/draft-event'
|
||||||
import { getPollMetadataFromEvent } from '@/lib/event-metadata'
|
import { getPollMetadataFromEvent } from '@/lib/event-metadata'
|
||||||
@@ -16,12 +17,16 @@ import { toast } from 'sonner'
|
|||||||
|
|
||||||
export default function Poll({ event, className }: { event: Event; className?: string }) {
|
export default function Poll({ event, className }: { event: Event; className?: string }) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const translatedEvent = useTranslatedEvent(event.id)
|
||||||
const { pubkey, publish, startLogin } = useNostr()
|
const { pubkey, publish, startLogin } = useNostr()
|
||||||
const [isVoting, setIsVoting] = useState(false)
|
const [isVoting, setIsVoting] = useState(false)
|
||||||
const [selectedOptionIds, setSelectedOptionIds] = useState<string[]>([])
|
const [selectedOptionIds, setSelectedOptionIds] = useState<string[]>([])
|
||||||
const pollResults = useFetchPollResults(event.id)
|
const pollResults = useFetchPollResults(event.id)
|
||||||
const [isLoadingResults, setIsLoadingResults] = useState(false)
|
const [isLoadingResults, setIsLoadingResults] = useState(false)
|
||||||
const poll = useMemo(() => getPollMetadataFromEvent(event), [event])
|
const poll = useMemo(
|
||||||
|
() => getPollMetadataFromEvent(translatedEvent ?? event),
|
||||||
|
[event, translatedEvent]
|
||||||
|
)
|
||||||
const votedOptionIds = useMemo(() => {
|
const votedOptionIds = useMemo(() => {
|
||||||
if (!pollResults || !pubkey) return []
|
if (!pollResults || !pubkey) return []
|
||||||
return Object.entries(pollResults.results)
|
return Object.entries(pollResults.results)
|
||||||
|
|||||||
@@ -24,9 +24,13 @@ export default function TranslateButton({
|
|||||||
const translatedEvent = useTranslatedEvent(event.id)
|
const translatedEvent = useTranslatedEvent(event.id)
|
||||||
const supported = useMemo(
|
const supported = useMemo(
|
||||||
() =>
|
() =>
|
||||||
[kinds.ShortTextNote, kinds.Highlights, ExtendedKind.COMMENT, ExtendedKind.PICTURE].includes(
|
[
|
||||||
event.kind
|
kinds.ShortTextNote,
|
||||||
),
|
kinds.Highlights,
|
||||||
|
ExtendedKind.COMMENT,
|
||||||
|
ExtendedKind.PICTURE,
|
||||||
|
ExtendedKind.POLL
|
||||||
|
].includes(event.kind),
|
||||||
[event]
|
[event]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ExtendedKind } from '@/constants'
|
||||||
|
import { getPollMetadataFromEvent } from '@/lib/event-metadata'
|
||||||
import libreTranslate from '@/services/libre-translate.service'
|
import libreTranslate from '@/services/libre-translate.service'
|
||||||
import storage from '@/services/local-storage.service'
|
import storage from '@/services/local-storage.service'
|
||||||
import translation from '@/services/translation.service'
|
import translation from '@/services/translation.service'
|
||||||
@@ -96,25 +98,51 @@ export function TranslationServiceProvider({ children }: { children: React.React
|
|||||||
const translateHighlightEvent = async (event: Event): Promise<Event> => {
|
const translateHighlightEvent = async (event: Event): Promise<Event> => {
|
||||||
const target = i18n.language
|
const target = i18n.language
|
||||||
const comment = event.tags.find((tag) => tag[0] === 'comment')?.[1]
|
const comment = event.tags.find((tag) => tag[0] === 'comment')?.[1]
|
||||||
if (!event.content && !comment) {
|
|
||||||
return event
|
|
||||||
}
|
|
||||||
const [translatedContent, translatedComment] = await Promise.all([
|
|
||||||
translate(event.content, target),
|
|
||||||
!!comment && translate(comment, target)
|
|
||||||
])
|
|
||||||
|
|
||||||
const translatedEvent: Event = {
|
const texts = {
|
||||||
...event,
|
content: event.content,
|
||||||
content: translatedContent
|
comment
|
||||||
}
|
}
|
||||||
if (translatedComment) {
|
const joinedText = joinTexts(texts)
|
||||||
translatedEvent.tags = event.tags.map((tag) =>
|
if (!joinedText) return event
|
||||||
tag[0] === 'comment' ? ['comment', translatedComment] : tag
|
|
||||||
|
const translatedText = await translate(joinedText, target)
|
||||||
|
const translatedTexts = splitTranslatedText(translatedText)
|
||||||
|
return {
|
||||||
|
...event,
|
||||||
|
content: translatedTexts.content ?? event.content,
|
||||||
|
tags: event.tags.map((tag) =>
|
||||||
|
tag[0] === 'comment' ? ['comment', translatedTexts.comment ?? tag[1]] : tag
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const translatePollEvent = async (event: Event): Promise<Event> => {
|
||||||
|
const target = i18n.language
|
||||||
|
const pollMetadata = getPollMetadataFromEvent(event)
|
||||||
|
|
||||||
|
const texts: Record<string, string> = {
|
||||||
|
question: event.content,
|
||||||
|
...pollMetadata?.options.reduce(
|
||||||
|
(acc, option) => {
|
||||||
|
acc[option.id] = option.label
|
||||||
|
return acc
|
||||||
|
},
|
||||||
|
{} as Record<string, string>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const joinedText = joinTexts(texts)
|
||||||
|
if (!joinedText) return event
|
||||||
|
|
||||||
|
const translatedText = await translate(joinedText, target)
|
||||||
|
const translatedTexts = splitTranslatedText(translatedText)
|
||||||
|
return {
|
||||||
|
...event,
|
||||||
|
content: translatedTexts.question ?? '',
|
||||||
|
tags: event.tags.map((tag) =>
|
||||||
|
tag[0] === 'option' ? ['option', tag[1], translatedTexts[tag[1]] ?? tag[2]] : tag
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
setTranslatedEventIdSet((prev) => new Set(prev.add(event.id)))
|
|
||||||
return translatedEvent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const translateEvent = async (event: Event): Promise<Event | void> => {
|
const translateEvent = async (event: Event): Promise<Event | void> => {
|
||||||
@@ -134,6 +162,8 @@ export function TranslationServiceProvider({ children }: { children: React.React
|
|||||||
let translatedEvent: Event | undefined
|
let translatedEvent: Event | undefined
|
||||||
if (event.kind === kinds.Highlights) {
|
if (event.kind === kinds.Highlights) {
|
||||||
translatedEvent = await translateHighlightEvent(event)
|
translatedEvent = await translateHighlightEvent(event)
|
||||||
|
} else if (event.kind === ExtendedKind.POLL) {
|
||||||
|
translatedEvent = await translatePollEvent(event)
|
||||||
} else {
|
} else {
|
||||||
const translatedText = await translate(event.content, target)
|
const translatedText = await translate(event.content, target)
|
||||||
if (!translatedText) {
|
if (!translatedText) {
|
||||||
@@ -178,3 +208,28 @@ export function TranslationServiceProvider({ children }: { children: React.React
|
|||||||
</TranslationServiceContext.Provider>
|
</TranslationServiceContext.Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function joinTexts(texts: Record<string, string | undefined>): string {
|
||||||
|
return (
|
||||||
|
Object.entries(texts).filter(([, content]) => content && content.trim() !== '') as [
|
||||||
|
string,
|
||||||
|
string
|
||||||
|
][]
|
||||||
|
)
|
||||||
|
.map(([key, content]) => `=== ${key} ===\n${content.trim()}\n=== ${key} ===`)
|
||||||
|
.join('\n\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitTranslatedText(translated: string) {
|
||||||
|
const regex = /=== (.+?) ===\n([\s\S]*?)\n=== \1 ===/g
|
||||||
|
const results: Record<string, string | undefined> = {}
|
||||||
|
|
||||||
|
let match: RegExpExecArray | null
|
||||||
|
while ((match = regex.exec(translated)) !== null) {
|
||||||
|
const key = match[1].trim()
|
||||||
|
const content = match[2].trim()
|
||||||
|
results[key] = content
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user