Skip to content
6 changes: 3 additions & 3 deletions packages/botonic-core/src/core-bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export class CoreBot {
}

private getBotContext(request: BotRequest): BotContext {
const { input, session, lastRoutePath } = request
const { input, session, lastRoutePath, settings, secrets } = request
return {
input,
session,
Expand All @@ -114,8 +114,8 @@ export class CoreBot {
setUserLocale: (locale: string) => this.setUserLocale(locale, session),
setSystemLocale: (locale: string) =>
this.setSystemLocale(locale, session),
settings: request.settings,
secrets: request.secrets,
settings,
secrets,
}
}

Expand Down
3 changes: 2 additions & 1 deletion packages/botonic-core/src/models/legacy-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ export interface SessionUser<TExtraData = any> {
locale: string
country: string
system_locale: string
system_locale_updated?: boolean
}

export interface HubtypeCaseContactReason {
Expand Down Expand Up @@ -344,7 +345,7 @@ export interface BotSettings {
LITELLM_API_URL: string
AZURE_OPENAI_API_BASE: string
AZURE_OPENAI_API_VERSION: string
LANGUAGE_DETECTION_ENABLED: string
LANGUAGE_DETECTION_ENABLED?: boolean
CUSTOM_SHORT_URL_HOST: string | null
custom: Record<string, any>
}
Expand Down
2 changes: 1 addition & 1 deletion packages/botonic-core/src/testing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const TEST_DEFAULTS = {
LITELLM_API_URL: 'https://api.litellm.com',
AZURE_OPENAI_API_BASE: 'https://api.openai.com',
AZURE_OPENAI_API_VERSION: '2026-02-01',
LANGUAGE_DETECTION_ENABLED: 'true',
LANGUAGE_DETECTION_ENABLED: true,
HUBTYPE_ACCESS_TOKEN: 'testAccessToken', // pragma: allowlist secret
LITELLM_API_KEY: 'testLiteLLMAPIKey', // pragma: allowlist secret
AZURE_OPENAI_API_KEY: 'testAzureOpenAIAPIKey', // pragma: allowlist secret
Expand Down
36 changes: 29 additions & 7 deletions packages/botonic-plugin-flow-builder/src/action/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,36 @@ export class FlowBuilderAction extends React.Component<FlowBuilderActionProps> {
return filteredContents
}

render(): JSX.Element | JSX.Element[] {
const { contents, webchatSettingsParams } = this.props
const botContext = this.context as BotContext
protected getWebchatSettingsParams(botContext: BotContext): {
shouldSendWebchatSettings: boolean
webchatSettingsParams?: WebchatSettingsProps
} {
let { webchatSettingsParams } = this.props
if (botContext.session.user.system_locale_updated) {
webchatSettingsParams = {
...webchatSettingsParams,
user: {
...webchatSettingsParams?.user,
system_locale: botContext.session.user.system_locale,
},
}
}
const shouldSendWebchatSettings =
(isWebchat(botContext.session) || isDev(botContext.session)) &&
!!webchatSettingsParams

return {
shouldSendWebchatSettings,
webchatSettingsParams,
}
}

render(): JSX.Element | JSX.Element[] {
const { contents } = this.props
const botContext = this.context as BotContext
const { shouldSendWebchatSettings, webchatSettingsParams } =
this.getWebchatSettingsParams(botContext)

return (
<>
{shouldSendWebchatSettings && (
Expand All @@ -93,11 +116,10 @@ export class FlowBuilderAction extends React.Component<FlowBuilderActionProps> {

export class FlowBuilderMultichannelAction extends FlowBuilderAction {
render(): JSX.Element | JSX.Element[] {
const { contents, webchatSettingsParams } = this.props
const { contents } = this.props
const botContext = this.context as BotContext
const shouldSendWebchatSettings =
(isWebchat(botContext.session) || isDev(botContext.session)) &&
!!webchatSettingsParams
const { shouldSendWebchatSettings, webchatSettingsParams } =
this.getWebchatSettingsParams(botContext)

return (
<Multichannel text={{ buttonsAsText: false }}>
Expand Down
51 changes: 9 additions & 42 deletions packages/botonic-plugin-flow-builder/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
type HtSmartIntentNode,
} from './content-fields/hubtype-fields'
import { type FlowBuilderApiOptions, ProcessEnvNodeEnvs } from './types'
import { FlowLocale } from './utils/flow-locale'

export class FlowBuilderApi {
url: string
Expand Down Expand Up @@ -310,47 +311,13 @@ export class FlowBuilderApi {
}

getResolvedLocale(): string {
const systemLocale = this.request.getSystemLocale()

const locale = this.resolveAsLocale(systemLocale)
if (locale) {
return locale
}

const language = this.resolveAsLanguage(systemLocale)
if (language) {
this.request.setSystemLocale(language)
return language
}

const defaultLocale = this.resolveAsDefaultLocale()
this.request.setSystemLocale(defaultLocale)
return defaultLocale
}

private resolveAsLocale(locale: string): string | undefined {
if (this.flow.locales.find(flowLocale => flowLocale === locale)) {
return locale
}
return undefined
}

private resolveAsLanguage(locale?: string): string | undefined {
const language = locale?.split('-')[0]
if (
language &&
this.flow.locales.find(flowLocale => flowLocale === language)
) {
console.log(`locale: ${locale} has been resolved as ${language}`)
return language
}
return undefined
}

private resolveAsDefaultLocale(): string {
console.log(
`Resolve locale with default locale: ${this.flow.default_locale_code}`
)
return this.flow.default_locale_code || 'en'
const flowLocales = this.flow.locales
const defaultLocaleCode = this.flow.default_locale_code

return new FlowLocale(
this.request,
flowLocales,
defaultLocaleCode
).resolve()
}
}
1 change: 1 addition & 0 deletions packages/botonic-plugin-flow-builder/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ export default class BotonicPluginFlowBuilder implements Plugin {

post(request: PluginPreRequest): void {
request.input.nluResolution = undefined
delete request.session.user.system_locale_updated
}

async getContentsByContentID(
Expand Down
93 changes: 93 additions & 0 deletions packages/botonic-plugin-flow-builder/src/utils/flow-locale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import type { BotContext } from '@botonic/core'
Comment thread
Iru89 marked this conversation as resolved.

export class FlowLocale {
constructor(
private readonly botContext: BotContext,
private readonly flowLocales: string[],
private readonly defaultLocaleCode: string
) {}

resolve(): string {
const priorityLocale = this.isLanguageDetectionEnabled()
? this.getPriorityLocale()
: this.botContext.getSystemLocale()

if (priorityLocale) {
const exactMatch = this.matchExactLocale(priorityLocale)
if (exactMatch) {
return this.applyLocale(exactMatch)
}

const languageMatch = this.matchLanguage(priorityLocale)
if (languageMatch) {
return this.applyLocale(languageMatch)
}
}

return this.applyLocale(this.getDefaultLocale())
}

private isLanguageDetectionEnabled(): boolean {
return !!this.botContext.settings.LANGUAGE_DETECTION_ENABLED
}

/**
* Rules:
* - If user and system languages differ, user locale takes priority.
* - If both share the same language, the more specific locale wins.
* - If both have the same specificity, user locale wins.
*/
private getPriorityLocale(): string | undefined {
const userLocale = this.botContext.getUserLocale()
const systemLocale = this.botContext.getSystemLocale()

if (!userLocale || !systemLocale) {
return undefined
}

const userLanguage = this.getLanguage(userLocale)
const systemLanguage = this.getLanguage(systemLocale)

if (userLanguage !== systemLanguage) {
return userLocale
}
const userIsSpecific = this.isSpecificLocale(userLocale)
const systemIsSpecific = this.isSpecificLocale(systemLocale)

if (userIsSpecific && !systemIsSpecific) {
return userLocale
}
if (!userIsSpecific && systemIsSpecific) {
return systemLocale
}

return userLocale
}

private matchExactLocale(locale: string): string | undefined {
return this.flowLocales.includes(locale) ? locale : undefined
}

private matchLanguage(locale: string): string | undefined {
const language = this.getLanguage(locale)
return this.flowLocales.includes(language) ? language : undefined
}

private getDefaultLocale(): string {
return this.defaultLocaleCode || 'en'
}

private applyLocale(locale: string): string {
this.botContext.setSystemLocale(locale)
this.botContext.session.user.system_locale_updated = true
return locale
}

private getLanguage(locale: string): string {
return locale.split('-')[0]
}

private isSpecificLocale(locale: string): boolean {
return locale.includes('-')
}
}
Loading
Loading