refactor(i18n): optimize translation loading and initialization

- Updated ensureTranslationsLoaded function to prevent duplicate requests by caching loaded translations and managing loading promises.
- Simplified translation preloading on app startup to only load essential namespaces for the current language.
- Adjusted useTranslationPreloader hook to avoid multiple requests for translations and ensure efficient loading state management.
This commit is contained in:
chamikaJ
2025-07-07 12:31:11 +05:30
parent a6f9046b42
commit 26b47aac53
3 changed files with 81 additions and 32 deletions

View File

@@ -2,7 +2,6 @@
import React, { Suspense, useEffect, memo, useMemo, useCallback } from 'react'; import React, { Suspense, useEffect, memo, useMemo, useCallback } from 'react';
import { RouterProvider } from 'react-router-dom'; import { RouterProvider } from 'react-router-dom';
import i18next from 'i18next'; import i18next from 'i18next';
import { ensureTranslationsLoaded } from './i18n';
// Components // Components
import ThemeWrapper from './features/theme/ThemeWrapper'; import ThemeWrapper from './features/theme/ThemeWrapper';
@@ -66,8 +65,8 @@ const App: React.FC = memo(() => {
// Initialize CSRF token // Initialize CSRF token
await initializeCsrfToken(); await initializeCsrfToken();
// Preload essential translations // Note: Translation preloading is handled in i18n.ts initialization
await ensureTranslationsLoaded(); // No need to call ensureTranslationsLoaded here to avoid duplicate requests
} catch (error) { } catch (error) {
if (isMounted) { if (isMounted) {
logger.error('Failed to initialize app:', error); logger.error('Failed to initialize app:', error);

View File

@@ -26,7 +26,7 @@ export const useTranslationPreloader = (
try { try {
setIsLoading(true); setIsLoading(true);
// Ensure translations are loaded // Only load translations for current language to avoid multiple requests
await ensureTranslationsLoaded(namespaces); await ensureTranslationsLoaded(namespaces);
// Wait for i18next to be ready // Wait for i18next to be ready
@@ -47,12 +47,18 @@ export const useTranslationPreloader = (
} }
}; };
loadTranslations(); // Only load if not already loaded
if (!isLoaded && !ready) {
loadTranslations();
} else if (ready && !isLoaded) {
setIsLoaded(true);
setIsLoading(false);
}
return () => { return () => {
isMounted = false; isMounted = false;
}; };
}, [namespaces, ready]); }, [namespaces, ready, isLoaded]);
return { return {
t, t,

View File

@@ -12,6 +12,10 @@ const ESSENTIAL_NAMESPACES = [
'settings', 'settings',
]; ];
// Cache to track loaded translations and prevent duplicate requests
const loadedTranslations = new Set<string>();
const loadingPromises = new Map<string, Promise<any>>();
i18n i18n
.use(HttpApi) .use(HttpApi)
.use(initReactI18next) .use(initReactI18next)
@@ -37,33 +41,67 @@ i18n
}); });
// Utility function to ensure translations are loaded // Utility function to ensure translations are loaded
export const ensureTranslationsLoaded = async (namespaces: string[] = ESSENTIAL_NAMESPACES) => { export const ensureTranslationsLoaded = async (
const currentLang = i18n.language || 'en'; namespaces: string[] = ESSENTIAL_NAMESPACES,
languages: string[] = [i18n.language || 'en']
) => {
try { try {
// Load all essential namespaces for the current language const loadPromises: Promise<any>[] = [];
await Promise.all(
namespaces.map(ns =>
i18n.loadNamespaces(ns).catch(() => {
logger.error(`Failed to load namespace: ${ns}`);
})
)
);
// Also preload for other languages to prevent delays on language switch for (const lang of languages) {
const otherLangs = ['en', 'es', 'pt', 'alb', 'de'].filter(lang => lang !== currentLang); for (const ns of namespaces) {
await Promise.all( const key = `${lang}:${ns}`;
otherLangs.map(lang =>
Promise.all(
namespaces.map(ns =>
i18n.loadNamespaces(ns).catch(() => {
logger.error(`Failed to load namespace: ${ns}`);
})
)
)
)
);
// Skip if already loaded
if (loadedTranslations.has(key)) {
continue;
}
// Check if already loading
if (loadingPromises.has(key)) {
loadPromises.push(loadingPromises.get(key)!);
continue;
}
// Create loading promise
const loadingPromise = new Promise<void>((resolve, reject) => {
// Switch to the target language temporarily if needed
const currentLang = i18n.language;
const shouldSwitchLang = currentLang !== lang;
const loadForLanguage = async () => {
try {
if (shouldSwitchLang) {
await i18n.changeLanguage(lang);
}
await i18n.loadNamespaces(ns);
// Switch back to original language if we changed it
if (shouldSwitchLang && currentLang) {
await i18n.changeLanguage(currentLang);
}
loadedTranslations.add(key);
resolve();
} catch (error) {
logger.error(`Failed to load namespace: ${ns} for language: ${lang}`, error);
reject(error);
} finally {
loadingPromises.delete(key);
}
};
loadForLanguage();
});
loadingPromises.set(key, loadingPromise);
loadPromises.push(loadingPromise);
}
}
// Wait for all loading promises to complete
await Promise.all(loadPromises);
return true; return true;
} catch (error) { } catch (error) {
logger.error('Failed to load translations:', error); logger.error('Failed to load translations:', error);
@@ -71,7 +109,13 @@ export const ensureTranslationsLoaded = async (namespaces: string[] = ESSENTIAL_
} }
}; };
// Initialize translations on app startup // Preload essential translations for current language only on startup
ensureTranslationsLoaded(); const initializeTranslations = async () => {
const currentLang = i18n.language || 'en';
await ensureTranslationsLoaded(ESSENTIAL_NAMESPACES, [currentLang]);
};
// Initialize translations on app startup (only once)
initializeTranslations();
export default i18n; export default i18n;