import { createSlice, nanoid, PayloadAction } from '@reduxjs/toolkit' import { ChannelConversation } from 'api/api.types' import { initializeApp, resetApp } from 'domains/app/actions' import { initializeConfig } from 'domains/config/actions' import { setLocale } from 'domains/i18n/actions' import { addEvent, setEvents } from 'domains/store/slice' import type { ChannelEvent } from 'domains/store/store.types' import type { TranslationState } from 'domains/translations/translations.types' export const translationsInitialState: TranslationState = { isActive: false, currentLocale: undefined, isAvailable: false, languages: [], containerId: nanoid(), translatedEventGroups: {}, translationProposal: null, } const getLastGroupId = (events: ChannelEvent[], id: string) => { const eventGroup = [...events].reduce>( (acc, { payload, type }, _index, arr) => { if (acc[id] && payload.id) { // Splice to break early (make the reducer think we are done) // This is needed to avoid events of other groups from being added to the array. if ( type === 'info' && payload?.type === 'divider' && // @ts-ignore payload?.body?.translationEnabled ) { arr.splice(0) return acc } acc[id].push(payload.id) } if (payload.id === id) acc[id] = [] return acc }, {}, ) const [[groupId, eventIds]] = Object.entries(eventGroup) const lastGroupId = events // @ts-ignore .filter((event) => event.payload?.type === 'divider') .map((event) => event.payload.id) .at(-1) return { lastGroupId, groupId, eventIds } } export const translationSlice = createSlice({ name: 'translation', initialState: translationsInitialState, reducers: { enableTranslation: (state, { payload }) => { state.isActive = true state.currentLocale = payload }, disableTranslation: (state) => { state.isActive = false state.currentLocale = undefined }, enableEventsTranslation: ( state, { payload: { events, id }, }: PayloadAction<{ events: ChannelEvent[]; id: string }>, ) => { delete state.translatedEventGroups[id] const { lastGroupId } = getLastGroupId(events, id) state.lastGroupId = lastGroupId }, disableEventsTranslation: ( state, { payload: { events, id }, }: PayloadAction<{ events: ChannelEvent[]; id: string }>, ) => { const { lastGroupId, groupId, eventIds } = getLastGroupId(events, id) state.lastGroupId = lastGroupId state.translatedEventGroups[groupId] = eventIds }, disableTranslationProposalPrompt: (state) => { state.translationProposal = null }, setTranslationProposalPrompt: ( state, { payload, }: PayloadAction, ) => { state.translationProposal = payload }, }, extraReducers: (builder) => { builder .addCase(resetApp.pending, () => translationsInitialState) .addCase(initializeConfig.fulfilled, (state, { payload }) => { const feature = payload?.features?.translation if (!feature?.languages) return state.isAvailable = feature.enabled === true state.languages = [...feature.languages].sort((a, b) => { if (a.locale === payload.defaultUserLocale) return -1 if (b.locale === payload.defaultUserLocale) return 1 return ( a.nativeName?.localeCompare(b.nativeName || '', undefined, { sensitivity: 'base', }) || 0 ) }) }) .addCase(setLocale.fulfilled, (state, { payload }) => { state.currentLocale = payload.userLocale }) .addCase(addEvent, (state, { payload }) => { if ( payload.payload.id && state.lastGroupId && state.translatedEventGroups[state.lastGroupId] ) { state.translatedEventGroups[state.lastGroupId].push( payload.payload.id, ) } }) .addCase(setEvents, (state, { payload }) => { state.translationProposal = payload?.ui?.translationProposal }) .addCase(initializeApp.fulfilled, (state, { payload }) => { state.translationProposal = payload.initialState?.ui?.translationProposal }) }, }) export const { enableTranslation, disableTranslation, enableEventsTranslation, disableEventsTranslation, setTranslationProposalPrompt, disableTranslationProposalPrompt, } = translationSlice.actions export default translationSlice.reducer