diff --git a/app/Http/Controllers/V1/Admin/Settings/GetCompanySettingsController.php b/app/Http/Controllers/V1/Admin/Settings/GetCompanySettingsController.php index c832c83a..e174f336 100644 --- a/app/Http/Controllers/V1/Admin/Settings/GetCompanySettingsController.php +++ b/app/Http/Controllers/V1/Admin/Settings/GetCompanySettingsController.php @@ -16,7 +16,7 @@ class GetCompanySettingsController extends Controller */ public function __invoke(GetSettingsRequest $request) { - $settings = CompanySetting::getSettings($request->settings, $request->header('company')); + $settings = CompanySetting::getSettings((array) $request->settings, $request->header('company')); return response()->json($settings); } diff --git a/app/Http/Controllers/V1/Admin/Settings/GetUserSettingsController.php b/app/Http/Controllers/V1/Admin/Settings/GetUserSettingsController.php index 8f768cd8..4f5dc8de 100644 --- a/app/Http/Controllers/V1/Admin/Settings/GetUserSettingsController.php +++ b/app/Http/Controllers/V1/Admin/Settings/GetUserSettingsController.php @@ -17,6 +17,6 @@ class GetUserSettingsController extends Controller { $user = $request->user(); - return response()->json($user->getSettings($request->settings)); + return response()->json($user->getSettings((array) $request->settings)); } } diff --git a/lang/locales.js b/lang/locales.js index e71c6883..82717267 100644 --- a/lang/locales.js +++ b/lang/locales.js @@ -1,43 +1,7 @@ -import cs from './cs.json' import en from './en.json' -import fr from './fr.json' -import es from './es.json' -import ar from './ar.json' -import de from './de.json' -import ja from './ja.json' -import pl from './pl.json' -import pt_BR from './pt-br.json' -import it from './it.json' -import sr from './sr.json' -import nl from './nl.json' -import ko from './ko.json' -import lv from './lv.json' -import sv from './sv.json' -import sk from './sk.json' -import vi from './vi.json' -import el from './el.json' -import hr from './hr.json' -import th from './th.json' +// Only load English by default to reduce initial bundle size +// Other languages will be loaded dynamically when needed export default { - cs, - en, - fr, - es, - ar, - de, - ja, - pt_BR, - it, - sr, - nl, - ko, - lv, - sv, - sk, - vi, - pl, - el, - hr, - th + en } diff --git a/resources/scripts/InvoiceShelf.js b/resources/scripts/InvoiceShelf.js index 9e69d3a1..b619defa 100644 --- a/resources/scripts/InvoiceShelf.js +++ b/resources/scripts/InvoiceShelf.js @@ -7,6 +7,7 @@ import { defineGlobalComponents } from './global-components' import utils from '@/scripts/helpers/utilities.js' import _ from 'lodash' import { VTooltip } from 'v-tooltip' +import { setI18nLanguage } from '@/scripts/helpers/language-loader.js' const app = createApp(App) @@ -14,6 +15,7 @@ export default class InvoiceShelf { constructor() { this.bootingCallbacks = [] this.messages = messages + this.i18n = null } booting(callback) { @@ -30,6 +32,17 @@ export default class InvoiceShelf { _.merge(this.messages, moduleMessages) } + /** + * Dynamically load and set a language + * @param {string} locale - Language code to load + * @returns {Promise} + */ + async loadLanguage(locale) { + if (this.i18n) { + await setI18nLanguage(this.i18n, locale) + } + } + start() { this.executeCallbacks() @@ -37,7 +50,7 @@ export default class InvoiceShelf { app.provide('$utils', utils) - const i18n = createI18n({ + this.i18n = createI18n({ legacy: false, locale: 'en', fallbackLocale: 'en', @@ -45,12 +58,15 @@ export default class InvoiceShelf { messages: this.messages, }) - window.i18n = i18n + window.i18n = this.i18n + + // Expose language loader globally + window.loadLanguage = this.loadLanguage.bind(this) const { createPinia } = window.pinia app.use(router) - app.use(i18n) + app.use(this.i18n) app.use(createPinia()) app.provide('utils', utils) app.directive('tooltip', VTooltip) diff --git a/resources/scripts/admin/stores/global.js b/resources/scripts/admin/stores/global.js index c88d7a19..be86f9cd 100644 --- a/resources/scripts/admin/stores/global.js +++ b/resources/scripts/admin/stores/global.js @@ -50,7 +50,7 @@ export const useGlobalStore = (useWindow = false) => { return new Promise((resolve, reject) => { axios .get('/api/v1/bootstrap') - .then((response) => { + .then(async (response) => { const companyStore = useCompanyStore() const userStore = useUserStore() const moduleStore = useModuleStore() @@ -71,8 +71,8 @@ export const useGlobalStore = (useWindow = false) => { moduleStore.apiToken = response.data.global_settings.api_token moduleStore.enableModules = response.data.modules - // company store - companyStore.companies = response.data.companies + // company store + companyStore.companies = response.data.companies companyStore.selectedCompany = response.data.current_company companyStore.setSelectedCompany(response.data.current_company) companyStore.selectedCompanySettings = @@ -80,9 +80,31 @@ export const useGlobalStore = (useWindow = false) => { companyStore.selectedCompanyCurrency = response.data.current_company_currency - if(typeof global.locale !== 'string') { - global.locale.value = - response.data.current_user_settings.language || 'en' + // Determine and load the appropriate language + const userLanguage = response.data.current_user_settings?.language + const companyLanguage = response.data.current_company_settings?.language + const targetLanguage = userLanguage || companyLanguage || 'en' + + // Load the language dynamically if it's not English + if (targetLanguage !== 'en' && window.loadLanguage) { + try { + await window.loadLanguage(targetLanguage) + } catch (error) { + console.warn('Failed to load language during bootstrap:', error) + // Fall back to English if loading fails + if (typeof global.locale !== 'string') { + global.locale.value = 'en' + } else { + global.locale = 'en' + } + } + } else { + // Set locale for English or when loadLanguage is not available + if (typeof global.locale !== 'string') { + global.locale.value = targetLanguage + } else { + global.locale = targetLanguage + } } this.isAppLoaded = true diff --git a/resources/scripts/admin/views/installation/Installation.vue b/resources/scripts/admin/views/installation/Installation.vue index cea45b8c..a138f2fd 100644 --- a/resources/scripts/admin/views/installation/Installation.vue +++ b/resources/scripts/admin/views/installation/Installation.vue @@ -65,7 +65,8 @@ export default { } if(typeof res.data.profile_language === 'string') { - global.locale.value = res.data.profile_language + // Use dynamic language loading instead of direct assignment + await window.loadLanguage(res.data.profile_language) } let dbstep = parseInt(res.data.profile_complete) diff --git a/resources/scripts/admin/views/installation/Step0SetLanguage.vue b/resources/scripts/admin/views/installation/Step0SetLanguage.vue index 9f7ac479..b58949da 100644 --- a/resources/scripts/admin/views/installation/Step0SetLanguage.vue +++ b/resources/scripts/admin/views/installation/Step0SetLanguage.vue @@ -27,6 +27,8 @@ {{ $t('wizard.continue') }} @@ -43,12 +45,11 @@ import { ref, onMounted } from 'vue' import { useInstallationStore } from '@/scripts/admin/stores/installation.js' -const { global } = window.i18n - const emit = defineEmits(['next']) let isFetchingInitialData = ref(false) let isSaving = ref(false) +let isChangingLanguage = ref(false) let languages = ref([]) let currentLanguage = 'en' @@ -75,11 +76,19 @@ function next() { isSaving.value = false } -function changeLanguage(event){ - if(typeof global.locale !== 'string') { - global.locale.value = event +async function changeLanguage(event) { + if (!event) return + + isChangingLanguage.value = true + + try { + // Dynamically load the selected language + await window.loadLanguage(event) + currentLanguage.value = event + } catch (error) { + console.error('Failed to change language:', error) + } finally { + isChangingLanguage.value = false } } - - diff --git a/resources/scripts/admin/views/settings/AccountSetting.vue b/resources/scripts/admin/views/settings/AccountSetting.vue index 689638a6..6c8fe5c5 100644 --- a/resources/scripts/admin/views/settings/AccountSetting.vue +++ b/resources/scripts/admin/views/settings/AccountSetting.vue @@ -200,6 +200,9 @@ async function updateUserData() { // Update Language if changed if (userStore.currentUserSettings.language !== userForm.language) { + // Load the new language dynamically before updating settings + await window.loadLanguage(userForm.language) + await userStore.updateUserSettings({ settings: { language: userForm.language, diff --git a/resources/scripts/admin/views/settings/PreferencesSetting.vue b/resources/scripts/admin/views/settings/PreferencesSetting.vue index 08550eb6..23f9a91a 100644 --- a/resources/scripts/admin/views/settings/PreferencesSetting.vue +++ b/resources/scripts/admin/views/settings/PreferencesSetting.vue @@ -105,7 +105,7 @@ class="w-full" /> - + } - Language messages object + */ +export async function loadLanguage(locale) { + // Return cached language if already loaded + if (languageCache.has(locale)) { + return languageCache.get(locale) + } + + try { + // Dynamic import of language file + const languageModule = await import(`../../../lang/${locale === 'pt_BR' ? 'pt-br' : locale}.json`) + const messages = languageModule.default || languageModule + + // Cache the loaded language + languageCache.set(locale, messages) + loadedLanguages.add(locale) + + return messages + } catch (error) { + console.warn(`Failed to load language: ${locale}`, error) + + // Fallback to English if available + if (locale !== 'en' && !languageCache.has('en')) { + try { + const fallbackModule = await import('../../../lang/en.json') + const fallbackMessages = fallbackModule.default || fallbackModule + languageCache.set('en', fallbackMessages) + return fallbackMessages + } catch (fallbackError) { + console.error('Failed to load fallback language (en)', fallbackError) + return {} + } + } + + return languageCache.get('en') || {} + } +} + +/** + * Load and set language in i18n instance + * @param {Object} i18n - Vue i18n instance + * @param {string} locale - Language code to load + * @returns {Promise} + */ +export async function setI18nLanguage(i18n, locale) { + // Load the language if not already loaded + if (!loadedLanguages.has(locale)) { + const messages = await loadLanguage(locale) + i18n.global.setLocaleMessage(locale, messages) + } + + // Set the locale + i18n.global.locale.value = locale +} + +/** + * Check if a language is already loaded + * @param {string} locale - Language code + * @returns {boolean} + */ +export function isLanguageLoaded(locale) { + return loadedLanguages.has(locale) +}