mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-04-07 13:41:23 +00:00
Dynamically load language files (#446)
This commit is contained in:
committed by
GitHub
parent
32f7bc053a
commit
a40bf5840d
22
resources/scripts/InvoiceShelf.js
vendored
22
resources/scripts/InvoiceShelf.js
vendored
@@ -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<void>}
|
||||
*/
|
||||
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)
|
||||
|
||||
34
resources/scripts/admin/stores/global.js
vendored
34
resources/scripts/admin/stores/global.js
vendored
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
|
||||
<BaseButton
|
||||
v-show="!isFetchingInitialData"
|
||||
:loading="isChangingLanguage"
|
||||
:disabled="isChangingLanguage"
|
||||
@click="next"
|
||||
>
|
||||
{{ $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
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
class="w-full"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.preferences.time_format')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
@@ -378,6 +378,12 @@ async function updatePreferencesData() {
|
||||
|
||||
isSaving.value = true
|
||||
delete data.settings.link_expiry_days
|
||||
|
||||
// If language is being changed, load it dynamically first
|
||||
if (companyStore.selectedCompanySettings.language !== settingsForm.language) {
|
||||
await window.loadLanguage(settingsForm.language)
|
||||
}
|
||||
|
||||
let res = await companyStore.updateCompanySettings({
|
||||
data: data,
|
||||
message: 'settings.preferences.updated_message',
|
||||
|
||||
74
resources/scripts/helpers/language-loader.js
vendored
Normal file
74
resources/scripts/helpers/language-loader.js
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Dynamic language loader utility
|
||||
* Loads language files on demand to reduce bundle size
|
||||
*/
|
||||
|
||||
const loadedLanguages = new Set()
|
||||
const languageCache = new Map()
|
||||
|
||||
/**
|
||||
* Dynamically import a language file
|
||||
* @param {string} locale - Language code (e.g., 'en', 'fr', 'pt_BR')
|
||||
* @returns {Promise<Object>} - 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<void>}
|
||||
*/
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user