Add per-user language preference with company default fallback

Existing accounts inherited the company language at creation time and there was no way to change UI language per user. Add a 'Default (Company Language)' entry to the language selector in UserGeneralView, persist the choice through userStore.updateUserSettings and reload the i18n bundle via window.loadLanguage. The 'default' sentinel keeps the user opted in to the company-wide setting.

Bootstrap (global.store) now syncs userForm from current_user data and resolves the active UI language as user > company > 'en'. RegisterController, InvitationRegistrationController and MemberService seed new users with language=default instead of copying the current company setting, so promoting/inviting members no longer leaks the inviter's frozen language.
This commit is contained in:
Darko Gjorgjijoski
2026-04-07 04:41:00 +02:00
parent c5c9677ffc
commit 78ed332d06
7 changed files with 63 additions and 10 deletions

View File

@@ -73,6 +73,8 @@ class InvitationRegistrationController extends Controller
'password' => $request->password,
]);
$user->setSettings(['language' => 'default']);
$this->invitationService->accept($invitation, $user);
return response()->json([

View File

@@ -61,10 +61,14 @@ class RegisterController extends Controller
*/
protected function create(array $data)
{
return User::create([
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => $data['password'],
]);
$user->setSettings(['language' => 'default']);
return $user;
}
}

View File

@@ -3,7 +3,6 @@
namespace App\Services;
use App\Http\Requests\MemberRequest;
use App\Models\CompanySetting;
use App\Models\User;
use Silber\Bouncer\BouncerFacade;
@@ -14,7 +13,7 @@ class MemberService
$user = User::create($request->getUserPayload());
$user->setSettings([
'language' => CompanySetting::getSetting('language', $request->header('company')),
'language' => 'default',
]);
$companies = collect($request->companies);

View File

@@ -1178,7 +1178,8 @@
"profile_picture_description": "Upload or remove your profile photo.",
"save": "Save",
"section_description": "You can update your name, email & password using the form below.",
"updated_message": "Account Settings updated successfully"
"updated_message": "Account Settings updated successfully",
"default_language": "Default (Company Language)"
},
"user_profile": {
"name": "Name",

View File

@@ -5,14 +5,33 @@ import { required, minLength, email, helpers } from '@vuelidate/validators'
import { useVuelidate } from '@vuelidate/core'
import { useUserStore } from '../../../../stores/user.store'
import { useGlobalStore } from '../../../../stores/global.store'
import { useCompanyStore } from '../../../../stores/company.store'
const LANGUAGE_DEFAULT = 'default'
const userStore = useUserStore()
const globalStore = useGlobalStore()
const companyStore = useCompanyStore()
const { t } = useI18n()
const isSaving = ref<boolean>(false)
const userForm = computed(() => userStore.userForm)
const selectedLanguage = computed<string>({
get: () => userForm.value.language || LANGUAGE_DEFAULT,
set: (v: string) => {
userForm.value.language = v === LANGUAGE_DEFAULT ? '' : v
},
})
const languageOptions = computed(() => {
const languages = (globalStore.config as Record<string, unknown>)?.languages as Array<{ name: string; code: string }> ?? []
return [
{ name: t('settings.account_settings.default_language'), code: LANGUAGE_DEFAULT },
...languages,
]
})
const rules = computed(() => ({
name: {
required: helpers.withMessage(t('validation.required'), required),
@@ -32,11 +51,17 @@ async function updateGeneral(): Promise<void> {
isSaving.value = true
try {
const language = userForm.value.language || 'default'
await userStore.updateUserSettings({ settings: { language } })
await userStore.updateCurrentUser({
name: userForm.value.name,
email: userForm.value.email,
language: userForm.value.language || undefined,
})
const effectiveLanguage = (language === 'default' ? '' : language) || companyStore.selectedCompanySettings?.language || 'en'
await (window as Record<string, unknown>).loadLanguage?.(effectiveLanguage)
} finally {
isSaving.value = false
}
@@ -77,12 +102,13 @@ async function updateGeneral(): Promise<void> {
<BaseInputGroup :label="$t('settings.language')">
<BaseMultiselect
v-model="userForm.language"
:options="(globalStore.config as Record<string, unknown>)?.languages as Array<{ name: string; code: string }> ?? []"
v-model="selectedLanguage"
:options="languageOptions"
label="name"
value-prop="code"
track-by="name"
track-by="code"
:searchable="true"
:can-deselect="false"
/>
</BaseInputGroup>
</BaseInputGrid>

View File

@@ -68,6 +68,17 @@ export const useGlobalStore = defineStore('global', () => {
userStore.currentUserSettings = response.current_user_settings
userStore.currentAbilities = response.current_user_abilities
// Sync user form with bootstrap data
if (response.current_user) {
userStore.userForm = {
name: response.current_user.name ?? '',
email: response.current_user.email ?? '',
password: '',
confirm_password: '',
language: response.current_user_settings?.language ?? '',
}
}
// company store
companyStore.companies = response.companies
@@ -89,6 +100,16 @@ export const useGlobalStore = defineStore('global', () => {
}
isAppLoaded.value = true
// Load UI language: user preference > company setting > English
// 'default' means "use company language"
const userLang = userStore.currentUserSettings.language
const uiLanguage =
(userLang && userLang !== 'default' ? userLang : '') ||
(response.current_company_settings as Record<string, string>)?.language ||
'en'
await (window as Record<string, unknown>).loadLanguage?.(uiLanguage)
return response
} catch (err: unknown) {
handleApiError(err)

View File

@@ -45,7 +45,7 @@ export const useUserStore = defineStore('user', () => {
email: response.data.email,
password: '',
confirm_password: '',
language: '',
language: currentUserSettings.value.language || '',
}
return response
} catch (err: unknown) {
@@ -63,7 +63,7 @@ export const useUserStore = defineStore('user', () => {
email: response.data.email,
password: '',
confirm_password: '',
language: '',
language: currentUserSettings.value.language || '',
}
const notificationStore = useNotificationStore()