feat: Agent language settings (#11222)

# Pull Request Template

## Description

This Pull Request will provide a language selector in the Profile
Settings for each user, and allows them to change the UI language per
agent, defaulting back to the account locale.

Fixes # #678 This does PR addresses the Dashboard view but does not
change the language of the agents emails

## Type of change

Please delete options that are not relevant.
- [X ] New feature (non-breaking change which adds functionality)

## How Has This Been Tested?

1. Go to an Agents Profile settings page
2. Select a language from the Language drop down
3. the UI will update to the new i18n locale
4. navigate through the UI to make sure the appropriate language is
being used
5. Refresh the page to test that the locale persists


270

- [X] My code follows the style guidelines of this project
- [X] I have performed a self-review of my code
- [X] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [X] My changes generate no new warnings
- [X] I have added tests that prove my fix is effective or that my
feature works
- [X] New and existing unit tests pass locally with my changes
- [X] Any dependent changes have been merged and published in downstream
modules
Checklist:.724.2708

---------

Co-authored-by: Sojan Jose <sojan@pepalo.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
micahmills
2025-09-09 11:57:36 +03:00
committed by GitHub
parent a4d2cb18f9
commit b989ca6397
11 changed files with 261 additions and 55 deletions

View File

@@ -7,7 +7,6 @@ import { useUISettings } from 'dashboard/composables/useUISettings';
import { useConfig } from 'dashboard/composables/useConfig';
import { useAccount } from 'dashboard/composables/useAccount';
import { FEATURE_FLAGS } from '../../../../featureFlags';
import { getLanguageDirection } from 'dashboard/components/widgets/conversation/advancedFilterItems/languages';
import WithLabel from 'v3/components/Form/WithLabel.vue';
import NextInput from 'next/input/Input.vue';
import BaseSettingsHeader from '../components/BaseSettingsHeader.vue';
@@ -33,12 +32,12 @@ export default {
NextInput,
},
setup() {
const { updateUISettings } = useUISettings();
const { updateUISettings, uiSettings } = useUISettings();
const { enabledLanguages } = useConfig();
const { accountId } = useAccount();
const v$ = useVuelidate();
return { updateUISettings, v$, enabledLanguages, accountId };
return { updateUISettings, uiSettings, v$, enabledLanguages, accountId };
},
data() {
return {
@@ -112,7 +111,7 @@ export default {
const { name, locale, id, domain, support_email, features } =
this.getAccount(this.accountId);
this.$root.$i18n.locale = locale;
this.$root.$i18n.locale = this.uiSettings?.locale || locale;
this.name = name;
this.locale = locale;
this.id = id;
@@ -137,21 +136,19 @@ export default {
domain: this.domain,
support_email: this.supportEmail,
});
this.$root.$i18n.locale = this.locale;
// If user locale is set, update the locale with user locale
if (this.uiSettings?.locale) {
this.$root.$i18n.locale = this.uiSettings?.locale;
} else {
// If user locale is not set, update the locale with account locale
this.$root.$i18n.locale = this.locale;
}
this.getAccount(this.id).locale = this.locale;
this.updateDirectionView(this.locale);
useAlert(this.$t('GENERAL_SETTINGS.UPDATE.SUCCESS'));
} catch (error) {
useAlert(this.$t('GENERAL_SETTINGS.UPDATE.ERROR'));
}
},
updateDirectionView(locale) {
const isRTLSupported = getLanguageDirection(locale);
this.updateUISettings({
rtl_view: isRTLSupported,
});
},
},
};
</script>

View File

@@ -11,6 +11,7 @@ import UserProfilePicture from './UserProfilePicture.vue';
import UserBasicDetails from './UserBasicDetails.vue';
import MessageSignature from './MessageSignature.vue';
import FontSize from './FontSize.vue';
import UserLanguageSelect from './UserLanguageSelect.vue';
import HotKeyCard from './HotKeyCard.vue';
import ChangePassword from './ChangePassword.vue';
import NotificationPreferences from './NotificationPreferences.vue';
@@ -28,6 +29,7 @@ export default {
MessageSignature,
FormSection,
FontSize,
UserLanguageSelect,
UserProfilePicture,
Policy,
UserBasicDetails,
@@ -230,6 +232,12 @@ export default {
"
@change="updateFontSize"
/>
<UserLanguageSelect
:label="$t('PROFILE_SETTINGS.FORM.INTERFACE_SECTION.LANGUAGE.TITLE')"
:description="
$t('PROFILE_SETTINGS.FORM.INTERFACE_SECTION.LANGUAGE.NOTE')
"
/>
</FormSection>
<FormSection
:title="$t('PROFILE_SETTINGS.FORM.MESSAGE_SIGNATURE_SECTION.TITLE')"

View File

@@ -0,0 +1,103 @@
<script setup>
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useAlert } from 'dashboard/composables';
import { useConfig } from 'dashboard/composables/useConfig';
import { useAccount } from 'dashboard/composables/useAccount';
import { useUISettings } from 'dashboard/composables/useUISettings';
import FormSelect from 'v3/components/Form/Select.vue';
defineProps({
label: { type: String, default: '' },
description: { type: String, default: '' },
});
const { t, locale } = useI18n();
const { updateUISettings, uiSettings } = useUISettings();
const { enabledLanguages } = useConfig();
const { currentAccount } = useAccount();
const currentLanguage = computed(() => uiSettings.value?.locale ?? '');
const languageOptions = computed(() => [
{
name: t(
'PROFILE_SETTINGS.FORM.INTERFACE_SECTION.LANGUAGE.USE_ACCOUNT_DEFAULT'
),
iso_639_1_code: '',
},
...(enabledLanguages ?? []),
]);
const updateLanguage = async languageCode => {
try {
if (!languageCode) {
// Clear preference to use account default
await updateUISettings({ locale: null });
locale.value = currentAccount.value.locale;
useAlert(
t('PROFILE_SETTINGS.FORM.INTERFACE_SECTION.LANGUAGE.UPDATE_SUCCESS')
);
return;
}
const valid = (enabledLanguages || []).some(
l => l.iso_639_1_code === languageCode
);
if (!valid) {
throw new Error(`Invalid language code: ${languageCode}`);
}
await updateUISettings({ locale: languageCode });
// Apply immediately if the user explicitly chose a preference
locale.value = languageCode;
useAlert(
t('PROFILE_SETTINGS.FORM.INTERFACE_SECTION.LANGUAGE.UPDATE_SUCCESS')
);
} catch (error) {
useAlert(
t('PROFILE_SETTINGS.FORM.INTERFACE_SECTION.LANGUAGE.UPDATE_ERROR')
);
throw error;
}
};
const selectedValue = computed({
get: () => currentLanguage.value,
set: value => {
updateLanguage(value);
},
});
</script>
<template>
<div class="flex gap-2 justify-between w-full items-start">
<div>
<label class="text-n-gray-12 font-medium leading-6 text-sm">
{{ label }}
</label>
<p class="text-n-gray-11">
{{ description }}
</p>
</div>
<FormSelect
v-model="selectedValue"
name="language"
spacing="compact"
class="min-w-28 mt-px"
:options="languageOptions"
label=""
>
<option
v-for="option in languageOptions"
:key="option.iso_639_1_code || 'default'"
:value="option.iso_639_1_code"
:selected="option.iso_639_1_code === selectedValue"
>
{{ option.name }}
</option>
</FormSelect>
</div>
</template>