Skip to content

Commit

Permalink
feat(channel-web): allow changing language of UI
Browse files Browse the repository at this point in the history
  • Loading branch information
epaminond committed Jan 9, 2020
1 parent 330bd3b commit ea96b0c
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 40 deletions.
7 changes: 4 additions & 3 deletions modules/builtin/src/actions/switchLanguage.js
Expand Up @@ -5,9 +5,10 @@
* @author Botpress, Inc.
* @param {string} lang - The language code, e.g. "en"
*/
const myAction = async lang => {
event.state.user.language = lang
const switchLanguage = async language => {
event.state.user.language = language
bp.realtime.sendPayload(bp.RealTimePayload.forVisitor(event.target, 'webchat.data', { payload: { language } }))
await event.setFlag(bp.IO.WellKnownFlags.FORCE_PERSIST_STATE, true)
}

return myAction(args.lang)
return switchLanguage(args.lang)
2 changes: 1 addition & 1 deletion modules/channel-web/src/views/lite/components/Composer.tsx
Expand Up @@ -100,7 +100,7 @@ export default inject(({ store }: { store: RootStore }) => ({
enableArrowNavigation: store.config.enableArrowNavigation,
enableResetSessionShortcut: store.config.enableResetSessionShortcut,
resetSession: store.resetSession
}))(observer(Composer))
}))(injectIntl(observer(Composer)))

type ComposerProps = {
focused: boolean
Expand Down
Expand Up @@ -2,6 +2,7 @@ import differenceInMinutes from 'date-fns/difference_in_minutes'
import { debounce } from 'lodash'
import { observe } from 'mobx'
import { inject, observer } from 'mobx-react'
import { injectIntl, InjectedIntlProps } from 'react-intl'
import React from 'react'

import constants from '../../core/constants'
Expand Down Expand Up @@ -113,6 +114,9 @@ class MessageList extends React.Component<MessageListProps, State> {
groups.push(currentGroup)
}

if (currentGroup.find(x => x.id === m.id)) {
return
}
currentGroup.push(m)

lastSpeaker = speaker
Expand Down Expand Up @@ -211,18 +215,19 @@ export default inject(({ store }: { store: RootStore }) => ({
focusedArea: store.view.focusedArea,
showUserAvatar: store.config.showUserAvatar,
enableArrowNavigation: store.config.enableArrowNavigation
}))(observer(MessageList))

type MessageListProps = Pick<
StoreDef,
| 'intl'
| 'isBotTyping'
| 'focusedArea'
| 'focusPrevious'
| 'focusNext'
| 'botAvatarUrl'
| 'botName'
| 'enableArrowNavigation'
| 'showUserAvatar'
| 'currentMessages'
>
}))(injectIntl(observer(MessageList)))

type MessageListProps = InjectedIntlProps &
Pick<
StoreDef,
| 'intl'
| 'isBotTyping'
| 'focusedArea'
| 'focusPrevious'
| 'focusNext'
| 'botAvatarUrl'
| 'botName'
| 'enableArrowNavigation'
| 'showUserAvatar'
| 'currentMessages'
>
2 changes: 2 additions & 0 deletions modules/channel-web/src/views/lite/core/socket.tsx
Expand Up @@ -7,6 +7,7 @@ export default class BpSocket {

public onMessage: (event: any) => void
public onTyping: (event: any) => void
public onData: (event: any) => void
public onUserIdChanged: (userId: string) => void

constructor(bp, config: Config) {
Expand All @@ -24,6 +25,7 @@ export default class BpSocket {

this.events.on('guest.webchat.message', this.onMessage)
this.events.on('guest.webchat.typing', this.onTyping)
this.events.on('guest.webchat.data', this.onData)

// firehose events to parent page
this.events.onAny(this.postToParent)
Expand Down
60 changes: 42 additions & 18 deletions modules/channel-web/src/views/lite/index.tsx
@@ -1,30 +1,54 @@
import { configure } from 'mobx'
import { Provider } from 'mobx-react'
import { Provider, observer, inject } from 'mobx-react'
import DevTools from 'mobx-react-devtools'
import React from 'react'
import { IntlProvider } from 'react-intl'

import Chat from './main'
import { RootStore } from './store'
import { defaultLocale, getUserLocale, initializeLocale, translations } from './translations'

import { defaultLocale, translations } from './translations'
configure({ enforceActions: 'observed' })

export const Embedded = props => WebChat(props, false)
export const Fullscreen = props => WebChat(props, true)

initializeLocale()
const locale = getUserLocale()

const WebChat = (props, fullscreen) => (
<IntlProvider locale={locale} messages={translations[locale]} defaultLocale={defaultLocale}>
<Provider store={new RootStore({ fullscreen })}>
<React.Fragment>
<Chat {...props} />
{process.env.NODE_ENV === 'development' && <DevTools className="bpw-mobx-tools" />}
</React.Fragment>
</Provider>
</IntlProvider>
)
export const Embedded = props => new Wrapper(props, false)
export const Fullscreen = props => new Wrapper(props, true)

interface State {
fullscreen: any
store: any
}

interface Props {}

class ExposedWebChat extends React.Component<Props, State> {
constructor(props, fullscreen) {
super(props)

this.state = {
fullscreen,
store: new RootStore({ fullscreen })
}
}

render() {
const { fullscreen } = this.state
const store = this.state.store
const { botUILanguage: locale } = store

return (
<Provider store={store}>
<IntlProvider locale={locale} messages={translations[locale]} defaultLocale={defaultLocale}>
<React.Fragment>
<Chat {...this.props} />
{process.env.NODE_ENV === 'development' && <DevTools className="bpw-mobx-tools" />}
</React.Fragment>
</IntlProvider>
</Provider>
)
}
}

const Wrapper = observer(ExposedWebChat)

/**
* @deprecated Since the way views are handled has changed, we're also exporting views in lowercase.
Expand Down
17 changes: 16 additions & 1 deletion modules/channel-web/src/views/lite/main.tsx
Expand Up @@ -59,6 +59,7 @@ class Web extends React.Component<MainProps> {
this.socket = new BpSocket(this.props.bp, config)
this.socket.onMessage = this.handleNewMessage
this.socket.onTyping = this.props.updateTyping
this.socket.onData = this.handleDataMessage
this.socket.onUserIdChanged = this.props.setUserId
this.socket.setup()

Expand Down Expand Up @@ -162,6 +163,19 @@ class Web extends React.Component<MainProps> {
this.handleResetUnreadCount()
}

handleDataMessage = event => {
if (!event || !event.payload) {
return
}

const { language } = event.payload
if (!language) {
return
}

this.props.updateBotUILanguage(language)
}

async playSound() {
if (this.state.played) {
return
Expand Down Expand Up @@ -239,7 +253,7 @@ export default inject(({ store }: { store: RootStore }) => ({
updateTyping: store.updateTyping,
sendMessage: store.sendMessage,
setReference: store.setReference,

updateBotUILanguage: store.updateBotUILanguage,
isWebchatReady: store.view.isWebchatReady,
showWidgetButton: store.view.showWidgetButton,
hasUnreadMessages: store.view.hasUnreadMessages,
Expand Down Expand Up @@ -267,6 +281,7 @@ type MainProps = { store: RootStore } & Pick<
| 'intl'
| 'updateTyping'
| 'setReference'
| 'updateBotUILanguage'
| 'hideChat'
| 'showChat'
| 'toggleBotInfo'
Expand Down
17 changes: 16 additions & 1 deletion modules/channel-web/src/views/lite/store/index.ts
Expand Up @@ -6,7 +6,8 @@ import { InjectedIntl } from 'react-intl'

import WebchatApi from '../core/api'
import constants from '../core/constants'
import { getUserLocale } from '../translations'
import { getUserLocale, initializeLocale, translations } from '../translations'

import {
BotInfo,
Config,
Expand All @@ -24,6 +25,9 @@ import ViewStore from './view'
/** Includes the partial definitions of all classes */
export type StoreDef = Partial<RootStore> & Partial<ViewStore> & Partial<ComposerStore> & Partial<Config>

initializeLocale()
const chosenLocale = getUserLocale()

class RootStore {
public bp: StudioConnector
public composer: ComposerStore
Expand Down Expand Up @@ -55,6 +59,9 @@ class RootStore {
@observable
public messageWrapper: MessageWrapper | undefined

@observable
public botUILanguage: string = chosenLocale

constructor({ fullscreen }) {
this.composer = new ComposerStore(this)
this.view = new ViewStore(this, fullscreen)
Expand Down Expand Up @@ -360,6 +367,14 @@ class RootStore {
this._typingInterval = undefined
}

@action.bound
updateBotUILanguage(lang: string): void {
runInAction('-> setBotUILanguage', () => {
this.botUILanguage = lang
localStorage.setItem('bp/channel-web/user-lang', lang)
})
}

/** Returns the current conversation ID, or the last one if it didn't expired. Otherwise, returns nothing. */
private _getCurrentConvoId(): number | undefined {
if (this.currentConversationId) {
Expand Down
2 changes: 1 addition & 1 deletion modules/channel-web/src/views/lite/translations/index.tsx
Expand Up @@ -20,7 +20,7 @@ const translations = { en, fr, pt, es, ar, ru, uk }

const getUserLocale = () => {
const locale = navigator.language || navigator['userLanguage'] || ''
const langCode = locale.split('-')[0]
const langCode = localStorage.getItem('bp/channel-web/user-lang') || locale.split('-')[0]

return translations[langCode] ? langCode : defaultLocale
}
Expand Down

0 comments on commit ea96b0c

Please sign in to comment.