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
@@ -16,7 +16,7 @@ class GetCompanySettingsController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function __invoke(GetSettingsRequest $request)
|
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);
|
return response()->json($settings);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,6 @@ class GetUserSettingsController extends Controller
|
|||||||
{
|
{
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
|
||||||
return response()->json($user->getSettings($request->settings));
|
return response()->json($user->getSettings((array) $request->settings));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
42
lang/locales.js
vendored
42
lang/locales.js
vendored
@@ -1,43 +1,7 @@
|
|||||||
import cs from './cs.json'
|
|
||||||
import en from './en.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 {
|
export default {
|
||||||
cs,
|
en
|
||||||
en,
|
|
||||||
fr,
|
|
||||||
es,
|
|
||||||
ar,
|
|
||||||
de,
|
|
||||||
ja,
|
|
||||||
pt_BR,
|
|
||||||
it,
|
|
||||||
sr,
|
|
||||||
nl,
|
|
||||||
ko,
|
|
||||||
lv,
|
|
||||||
sv,
|
|
||||||
sk,
|
|
||||||
vi,
|
|
||||||
pl,
|
|
||||||
el,
|
|
||||||
hr,
|
|
||||||
th
|
|
||||||
}
|
}
|
||||||
|
|||||||
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 utils from '@/scripts/helpers/utilities.js'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { VTooltip } from 'v-tooltip'
|
import { VTooltip } from 'v-tooltip'
|
||||||
|
import { setI18nLanguage } from '@/scripts/helpers/language-loader.js'
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ export default class InvoiceShelf {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.bootingCallbacks = []
|
this.bootingCallbacks = []
|
||||||
this.messages = messages
|
this.messages = messages
|
||||||
|
this.i18n = null
|
||||||
}
|
}
|
||||||
|
|
||||||
booting(callback) {
|
booting(callback) {
|
||||||
@@ -30,6 +32,17 @@ export default class InvoiceShelf {
|
|||||||
_.merge(this.messages, moduleMessages)
|
_.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() {
|
start() {
|
||||||
this.executeCallbacks()
|
this.executeCallbacks()
|
||||||
|
|
||||||
@@ -37,7 +50,7 @@ export default class InvoiceShelf {
|
|||||||
|
|
||||||
app.provide('$utils', utils)
|
app.provide('$utils', utils)
|
||||||
|
|
||||||
const i18n = createI18n({
|
this.i18n = createI18n({
|
||||||
legacy: false,
|
legacy: false,
|
||||||
locale: 'en',
|
locale: 'en',
|
||||||
fallbackLocale: 'en',
|
fallbackLocale: 'en',
|
||||||
@@ -45,12 +58,15 @@ export default class InvoiceShelf {
|
|||||||
messages: this.messages,
|
messages: this.messages,
|
||||||
})
|
})
|
||||||
|
|
||||||
window.i18n = i18n
|
window.i18n = this.i18n
|
||||||
|
|
||||||
|
// Expose language loader globally
|
||||||
|
window.loadLanguage = this.loadLanguage.bind(this)
|
||||||
|
|
||||||
const { createPinia } = window.pinia
|
const { createPinia } = window.pinia
|
||||||
|
|
||||||
app.use(router)
|
app.use(router)
|
||||||
app.use(i18n)
|
app.use(this.i18n)
|
||||||
app.use(createPinia())
|
app.use(createPinia())
|
||||||
app.provide('utils', utils)
|
app.provide('utils', utils)
|
||||||
app.directive('tooltip', VTooltip)
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
axios
|
axios
|
||||||
.get('/api/v1/bootstrap')
|
.get('/api/v1/bootstrap')
|
||||||
.then((response) => {
|
.then(async (response) => {
|
||||||
const companyStore = useCompanyStore()
|
const companyStore = useCompanyStore()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const moduleStore = useModuleStore()
|
const moduleStore = useModuleStore()
|
||||||
@@ -71,8 +71,8 @@ export const useGlobalStore = (useWindow = false) => {
|
|||||||
moduleStore.apiToken = response.data.global_settings.api_token
|
moduleStore.apiToken = response.data.global_settings.api_token
|
||||||
moduleStore.enableModules = response.data.modules
|
moduleStore.enableModules = response.data.modules
|
||||||
|
|
||||||
// company store
|
// company store
|
||||||
companyStore.companies = response.data.companies
|
companyStore.companies = response.data.companies
|
||||||
companyStore.selectedCompany = response.data.current_company
|
companyStore.selectedCompany = response.data.current_company
|
||||||
companyStore.setSelectedCompany(response.data.current_company)
|
companyStore.setSelectedCompany(response.data.current_company)
|
||||||
companyStore.selectedCompanySettings =
|
companyStore.selectedCompanySettings =
|
||||||
@@ -80,9 +80,31 @@ export const useGlobalStore = (useWindow = false) => {
|
|||||||
companyStore.selectedCompanyCurrency =
|
companyStore.selectedCompanyCurrency =
|
||||||
response.data.current_company_currency
|
response.data.current_company_currency
|
||||||
|
|
||||||
if(typeof global.locale !== 'string') {
|
// Determine and load the appropriate language
|
||||||
global.locale.value =
|
const userLanguage = response.data.current_user_settings?.language
|
||||||
response.data.current_user_settings.language || 'en'
|
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
|
this.isAppLoaded = true
|
||||||
|
|||||||
@@ -65,7 +65,8 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(typeof res.data.profile_language === 'string') {
|
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)
|
let dbstep = parseInt(res.data.profile_complete)
|
||||||
|
|||||||
@@ -27,6 +27,8 @@
|
|||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
v-show="!isFetchingInitialData"
|
v-show="!isFetchingInitialData"
|
||||||
|
:loading="isChangingLanguage"
|
||||||
|
:disabled="isChangingLanguage"
|
||||||
@click="next"
|
@click="next"
|
||||||
>
|
>
|
||||||
{{ $t('wizard.continue') }}
|
{{ $t('wizard.continue') }}
|
||||||
@@ -43,12 +45,11 @@
|
|||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { useInstallationStore } from '@/scripts/admin/stores/installation.js'
|
import { useInstallationStore } from '@/scripts/admin/stores/installation.js'
|
||||||
|
|
||||||
const { global } = window.i18n
|
|
||||||
|
|
||||||
const emit = defineEmits(['next'])
|
const emit = defineEmits(['next'])
|
||||||
|
|
||||||
let isFetchingInitialData = ref(false)
|
let isFetchingInitialData = ref(false)
|
||||||
let isSaving = ref(false)
|
let isSaving = ref(false)
|
||||||
|
let isChangingLanguage = ref(false)
|
||||||
let languages = ref([])
|
let languages = ref([])
|
||||||
let currentLanguage = 'en'
|
let currentLanguage = 'en'
|
||||||
|
|
||||||
@@ -75,11 +76,19 @@ function next() {
|
|||||||
isSaving.value = false
|
isSaving.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeLanguage(event){
|
async function changeLanguage(event) {
|
||||||
if(typeof global.locale !== 'string') {
|
if (!event) return
|
||||||
global.locale.value = event
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -200,6 +200,9 @@ async function updateUserData() {
|
|||||||
// Update Language if changed
|
// Update Language if changed
|
||||||
|
|
||||||
if (userStore.currentUserSettings.language !== userForm.language) {
|
if (userStore.currentUserSettings.language !== userForm.language) {
|
||||||
|
// Load the new language dynamically before updating settings
|
||||||
|
await window.loadLanguage(userForm.language)
|
||||||
|
|
||||||
await userStore.updateUserSettings({
|
await userStore.updateUserSettings({
|
||||||
settings: {
|
settings: {
|
||||||
language: userForm.language,
|
language: userForm.language,
|
||||||
|
|||||||
@@ -105,7 +105,7 @@
|
|||||||
class="w-full"
|
class="w-full"
|
||||||
/>
|
/>
|
||||||
</BaseInputGroup>
|
</BaseInputGroup>
|
||||||
|
|
||||||
<BaseInputGroup
|
<BaseInputGroup
|
||||||
:label="$t('settings.preferences.time_format')"
|
:label="$t('settings.preferences.time_format')"
|
||||||
:content-loading="isFetchingInitialData"
|
:content-loading="isFetchingInitialData"
|
||||||
@@ -378,6 +378,12 @@ async function updatePreferencesData() {
|
|||||||
|
|
||||||
isSaving.value = true
|
isSaving.value = true
|
||||||
delete data.settings.link_expiry_days
|
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({
|
let res = await companyStore.updateCompanySettings({
|
||||||
data: data,
|
data: data,
|
||||||
message: 'settings.preferences.updated_message',
|
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