mirror of
https://github.com/sassanix/Warracker.git
synced 2026-05-03 06:49:52 -05:00
23028fe696
Removed vite, and consolidated files to frontend folder
470 lines
17 KiB
JavaScript
470 lines
17 KiB
JavaScript
/**
|
|
* i18next Configuration and Initialization
|
|
* Handles frontend localization with offline support
|
|
*/
|
|
|
|
// Global i18n object to make translation functions available
|
|
window.i18n = {};
|
|
|
|
// Supported languages
|
|
const SUPPORTED_LANGUAGES = ['en', 'fr', 'es', 'de', 'it', 'cs', 'nl', 'hi', 'fa', 'ar', 'ru', 'uk', 'zh_CN', 'zh_HK', 'ja', 'pt', 'ko', 'tr', 'pl', 'he'];
|
|
const DEFAULT_LANGUAGE = 'en';
|
|
const RTL_LANGUAGES = ['ar', 'fa', 'he']; // Arabic, Persian, and Hebrew are RTL languages
|
|
|
|
/**
|
|
* Initialize i18next with configuration
|
|
*/
|
|
async function initializeI18n() {
|
|
console.log('Initializing i18next...');
|
|
|
|
// Debug: Check if required libraries are loaded
|
|
if (typeof i18next === 'undefined') {
|
|
console.error('i18next library not loaded');
|
|
return;
|
|
}
|
|
|
|
console.log('i18next library loaded successfully');
|
|
|
|
// Check optional libraries
|
|
const hasHttpBackend = typeof i18nextHttpBackend !== 'undefined';
|
|
const hasLanguageDetector = typeof i18nextBrowserLanguageDetector !== 'undefined';
|
|
|
|
console.log('Optional libraries:', {
|
|
httpBackend: hasHttpBackend,
|
|
languageDetector: hasLanguageDetector
|
|
});
|
|
|
|
try {
|
|
// Get current language from various sources
|
|
const savedLanguage = getCurrentLanguage();
|
|
console.log('Initial language detected:', savedLanguage);
|
|
|
|
// Load translations manually since HttpBackend might not be available
|
|
const translations = await loadTranslations(savedLanguage);
|
|
|
|
// Configure i18next with or without plugins
|
|
let i18nextInstance = i18next;
|
|
|
|
if (hasLanguageDetector) {
|
|
i18nextInstance = i18nextInstance.use(i18nextBrowserLanguageDetector);
|
|
}
|
|
|
|
await i18nextInstance.init({
|
|
fallbackLng: DEFAULT_LANGUAGE,
|
|
supportedLngs: SUPPORTED_LANGUAGES,
|
|
debug: false,
|
|
|
|
// Set initial language and resources
|
|
lng: savedLanguage,
|
|
resources: translations,
|
|
|
|
// Language detection configuration (only if detector is available)
|
|
...(hasLanguageDetector && {
|
|
detection: {
|
|
order: ['cookie', 'localStorage', 'navigator', 'htmlTag'],
|
|
lookupCookie: 'lang',
|
|
lookupLocalStorage: 'preferred_language',
|
|
caches: ['cookie', 'localStorage'],
|
|
cookieOptions: {
|
|
path: '/',
|
|
maxAge: 365 * 24 * 60 * 60 // 1 year
|
|
}
|
|
}
|
|
}),
|
|
|
|
// Interpolation options
|
|
interpolation: {
|
|
escapeValue: false
|
|
}
|
|
});
|
|
|
|
// Store translation function globally
|
|
window.i18n.t = i18next.t.bind(i18next);
|
|
window.i18n.changeLanguage = changeLanguage;
|
|
window.i18n.getCurrentLanguage = getCurrentLanguage;
|
|
window.i18n.getSupportedLanguages = () => SUPPORTED_LANGUAGES;
|
|
|
|
// Initialize page translations
|
|
translatePage();
|
|
|
|
// Set initial page direction
|
|
updatePageDirection(i18next.language);
|
|
|
|
// Set up language change listener
|
|
i18next.on('languageChanged', (lng) => {
|
|
console.log('Language changed to:', lng);
|
|
translatePage();
|
|
updateLanguageAttribute(lng);
|
|
updatePageDirection(lng);
|
|
});
|
|
|
|
console.log('i18next initialized successfully with language:', i18next.language);
|
|
|
|
// Dispatch event to notify other scripts that i18n is ready
|
|
window.dispatchEvent(new CustomEvent('i18nReady', {
|
|
detail: {
|
|
language: i18next.language,
|
|
t: window.i18n.t
|
|
}
|
|
}));
|
|
|
|
} catch (error) {
|
|
console.error('Failed to initialize i18next:', error);
|
|
// Fallback to default language without i18next
|
|
window.i18n.t = (key) => key; // Return key as fallback
|
|
window.i18n.changeLanguage = () => {};
|
|
window.i18n.getCurrentLanguage = () => DEFAULT_LANGUAGE;
|
|
window.i18n.getSupportedLanguages = () => SUPPORTED_LANGUAGES;
|
|
|
|
// Still dispatch event so other scripts know i18n is "ready" (even with fallback)
|
|
window.dispatchEvent(new CustomEvent('i18nReady', {
|
|
detail: {
|
|
language: DEFAULT_LANGUAGE,
|
|
t: window.i18n.t,
|
|
fallback: true
|
|
}
|
|
}));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load translations for a specific language
|
|
*/
|
|
async function loadTranslations(language) {
|
|
console.log(`loadTranslations called for language: ${language}`);
|
|
const translations = {};
|
|
|
|
try {
|
|
// Load primary language
|
|
const url = `/locales/${language}/translation.json?v=${Date.now()}`;
|
|
console.log(`Fetching translations from: ${url}`);
|
|
const response = await fetch(url);
|
|
console.log(`Fetch response status: ${response.status} for language: ${language}`);
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
translations[language] = { translation: data };
|
|
const keyCount = Object.keys(data).length;
|
|
console.log(`Successfully loaded ${keyCount} translation keys for ${language}:`, Object.keys(data).slice(0, 5));
|
|
} else {
|
|
console.warn(`Failed to load translations for ${language}, status: ${response.status}`);
|
|
console.warn(`Response text:`, await response.text().catch(() => 'Could not read response text'));
|
|
}
|
|
} catch (error) {
|
|
console.error(`Error loading translations for ${language}:`, error);
|
|
}
|
|
|
|
// Always load fallback language if different
|
|
if (language !== DEFAULT_LANGUAGE) {
|
|
try {
|
|
const fallbackUrl = `/locales/${DEFAULT_LANGUAGE}/translation.json?v=${Date.now()}`;
|
|
console.log(`Loading fallback language from: ${fallbackUrl}`);
|
|
const fallbackResponse = await fetch(fallbackUrl);
|
|
console.log(`Fallback fetch response status: ${fallbackResponse.status}`);
|
|
|
|
if (fallbackResponse.ok) {
|
|
const fallbackData = await fallbackResponse.json();
|
|
translations[DEFAULT_LANGUAGE] = { translation: fallbackData };
|
|
const fallbackKeyCount = Object.keys(fallbackData).length;
|
|
console.log(`Loaded ${fallbackKeyCount} fallback translation keys for ${DEFAULT_LANGUAGE}`);
|
|
}
|
|
} catch (error) {
|
|
console.error(`Error loading fallback translations:`, error);
|
|
}
|
|
}
|
|
|
|
console.log(`loadTranslations completed. Languages loaded: ${Object.keys(translations)}`);
|
|
return translations;
|
|
}
|
|
|
|
/**
|
|
* Get current language from storage or user preferences
|
|
*/
|
|
function getCurrentLanguage() {
|
|
// URL override (e.g., ?lang=tr)
|
|
try {
|
|
const params = new URLSearchParams(window.location.search);
|
|
const urlLang = params.get('lang');
|
|
if (urlLang && SUPPORTED_LANGUAGES.includes(urlLang)) {
|
|
setCookie('lang', urlLang, 365);
|
|
localStorage.setItem('preferred_language', urlLang);
|
|
return urlLang;
|
|
}
|
|
} catch (e) {
|
|
// Ignore URL parsing errors and continue
|
|
}
|
|
// Check cookie first
|
|
const cookieLang = getCookie('lang');
|
|
if (cookieLang && SUPPORTED_LANGUAGES.includes(cookieLang)) {
|
|
return cookieLang;
|
|
}
|
|
|
|
// Check localStorage
|
|
const storedLang = localStorage.getItem('preferred_language');
|
|
if (storedLang && SUPPORTED_LANGUAGES.includes(storedLang)) {
|
|
return storedLang;
|
|
}
|
|
|
|
// Check user preferences from API if authenticated
|
|
const userInfo = JSON.parse(localStorage.getItem('user_info') || '{}');
|
|
if (userInfo.preferred_language && SUPPORTED_LANGUAGES.includes(userInfo.preferred_language)) {
|
|
return userInfo.preferred_language;
|
|
}
|
|
|
|
// Fallback to browser language or default
|
|
const browserLang = navigator.language?.split('-')[0];
|
|
return SUPPORTED_LANGUAGES.includes(browserLang) ? browserLang : DEFAULT_LANGUAGE;
|
|
}
|
|
|
|
/**
|
|
* Change language and persist preference
|
|
*/
|
|
async function changeLanguage(language) {
|
|
console.log('changeLanguage called with:', language);
|
|
|
|
if (!SUPPORTED_LANGUAGES.includes(language)) {
|
|
console.warn('Unsupported language:', language);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
console.log('Loading translations for language:', language);
|
|
// Load translations for the new language
|
|
const translations = await loadTranslations(language);
|
|
console.log('Translations loaded:', Object.keys(translations));
|
|
|
|
// Add new language resources to i18next
|
|
for (const [lng, resources] of Object.entries(translations)) {
|
|
console.log('Adding resource bundle for language:', lng);
|
|
i18next.addResourceBundle(lng, 'translation', resources.translation, true, true);
|
|
}
|
|
|
|
// Change language in i18next
|
|
console.log('Changing i18next language to:', language);
|
|
await i18next.changeLanguage(language);
|
|
console.log('i18next language changed successfully to:', i18next.language);
|
|
|
|
// Persist to cookie and localStorage
|
|
setCookie('lang', language, 365);
|
|
localStorage.setItem('preferred_language', language);
|
|
console.log('Language preference saved to cookie and localStorage:', language);
|
|
|
|
// Update user preference in backend if authenticated
|
|
if (localStorage.getItem('auth_token')) {
|
|
try {
|
|
await saveLanguagePreference(language);
|
|
console.log('Language preference saved to backend');
|
|
} catch (error) {
|
|
console.warn('Failed to save language preference to backend:', error);
|
|
}
|
|
}
|
|
|
|
// Dispatch event for other components
|
|
window.dispatchEvent(new CustomEvent('languageChanged', { detail: { language } }));
|
|
console.log('languageChanged event dispatched for:', language);
|
|
|
|
} catch (error) {
|
|
console.error('Failed to change language:', error);
|
|
throw error; // Re-throw to let calling code handle it
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save language preference to backend
|
|
*/
|
|
async function saveLanguagePreference(language) {
|
|
const token = localStorage.getItem('auth_token');
|
|
if (!token) return;
|
|
|
|
const response = await fetch('/api/auth/user/language-preference', {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ preferred_language: language })
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to save language preference');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Translate all elements on the current page
|
|
*/
|
|
function translatePage() {
|
|
if (!window.i18n.t) {
|
|
console.warn('Translation function not available, skipping translation');
|
|
return;
|
|
}
|
|
|
|
console.log('Starting page translation...');
|
|
let translatedCount = 0;
|
|
|
|
// Update dynamic titles that are set by JavaScript
|
|
updateDynamicTitles();
|
|
|
|
// Translate elements with data-i18n attribute
|
|
document.querySelectorAll('[data-i18n]').forEach(element => {
|
|
const raw = element.getAttribute('data-i18n');
|
|
let handled = false;
|
|
|
|
// Support syntax like [placeholder]warranties.search_placeholder
|
|
const attrMatch = raw && raw.match(/^\s*\[(\w+)\]\s*(.+)$/);
|
|
if (attrMatch) {
|
|
const attrName = attrMatch[1];
|
|
const key = attrMatch[2];
|
|
const translation = window.i18n.t(key);
|
|
if (translation && translation !== key) {
|
|
element.setAttribute(attrName, translation);
|
|
translatedCount++;
|
|
handled = true;
|
|
}
|
|
}
|
|
|
|
if (!handled) {
|
|
const key = raw;
|
|
const translation = window.i18n.t(key);
|
|
if (translation && translation !== key) {
|
|
if (element.tagName === 'INPUT' && (element.type === 'text' || element.type === 'email' || element.type === 'password')) {
|
|
element.placeholder = translation;
|
|
} else if (element.hasAttribute('title')) {
|
|
element.title = translation;
|
|
} else {
|
|
element.textContent = translation;
|
|
}
|
|
translatedCount++;
|
|
} else {
|
|
console.warn(`No translation found for key: ${key}`);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Translate elements with data-i18n-html attribute (allows HTML content)
|
|
document.querySelectorAll('[data-i18n-html]').forEach(element => {
|
|
const key = element.getAttribute('data-i18n-html');
|
|
const translation = window.i18n.t(key);
|
|
element.innerHTML = translation;
|
|
});
|
|
|
|
// Translate elements with data-i18n-title attribute
|
|
document.querySelectorAll('[data-i18n-title]').forEach(element => {
|
|
const key = element.getAttribute('data-i18n-title');
|
|
const translation = window.i18n.t(key);
|
|
element.title = translation;
|
|
});
|
|
|
|
// Translate elements with data-i18n-placeholder attribute
|
|
document.querySelectorAll('[data-i18n-placeholder]').forEach(element => {
|
|
const key = element.getAttribute('data-i18n-placeholder');
|
|
const translation = window.i18n.t(key);
|
|
if (translation && translation !== key) {
|
|
element.placeholder = translation;
|
|
translatedCount++;
|
|
}
|
|
});
|
|
|
|
console.log(`Page translation completed. Translated ${translatedCount} elements.`);
|
|
|
|
// Re-process warranties to update status text and labels if warranties exist
|
|
if (typeof warranties !== 'undefined' && warranties && warranties.length > 0) {
|
|
console.log(`[i18n.js] Re-processing ${warranties.length} warranties after language change`);
|
|
// Re-process each warranty to update status text with new language
|
|
warranties = warranties.map(warranty => {
|
|
if (typeof processWarrantyData === 'function') {
|
|
return processWarrantyData(warranty);
|
|
}
|
|
return warranty;
|
|
});
|
|
// Re-render warranties with new translations
|
|
if (typeof applyFilters === 'function') {
|
|
applyFilters();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update dynamic titles that are set by JavaScript
|
|
*/
|
|
function updateDynamicTitles() {
|
|
// Update warranties panel title on index.html
|
|
if (typeof updateWarrantiesPanelTitle === 'function' && typeof isGlobalView !== 'undefined') {
|
|
updateWarrantiesPanelTitle(isGlobalView);
|
|
}
|
|
|
|
// Update dashboard title on status.html
|
|
if (typeof updateDashboardTitle === 'function') {
|
|
updateDashboardTitle();
|
|
}
|
|
|
|
// Re-process warranty cards if they exist (for dynamic status text)
|
|
if (typeof processAllWarranties === 'function') {
|
|
processAllWarranties();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update HTML lang attribute
|
|
*/
|
|
function updateLanguageAttribute(language) {
|
|
document.documentElement.lang = language;
|
|
}
|
|
|
|
/**
|
|
* Update page direction for RTL languages
|
|
*/
|
|
function updatePageDirection(language) {
|
|
const isRTL = RTL_LANGUAGES.includes(language);
|
|
const htmlElement = document.documentElement;
|
|
const bodyElement = document.body;
|
|
|
|
if (isRTL) {
|
|
htmlElement.dir = 'rtl';
|
|
htmlElement.setAttribute('data-rtl', 'true');
|
|
bodyElement.classList.add('rtl');
|
|
console.log(`Applied RTL direction for language: ${language}`);
|
|
} else {
|
|
htmlElement.dir = 'ltr';
|
|
htmlElement.removeAttribute('data-rtl');
|
|
bodyElement.classList.remove('rtl');
|
|
console.log(`Applied LTR direction for language: ${language}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Utility function to get cookie value
|
|
*/
|
|
function getCookie(name) {
|
|
const value = `; ${document.cookie}`;
|
|
const parts = value.split(`; ${name}=`);
|
|
if (parts.length === 2) return parts.pop().split(';').shift();
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Utility function to set cookie
|
|
*/
|
|
function setCookie(name, value, days) {
|
|
const expires = new Date();
|
|
expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000));
|
|
document.cookie = `${name}=${value};expires=${expires.toUTCString()};path=/`;
|
|
}
|
|
|
|
/**
|
|
* Add translation helper for dynamic content
|
|
*/
|
|
function t(key, options = {}) {
|
|
return window.i18n.t ? window.i18n.t(key, options) : key;
|
|
}
|
|
|
|
// Expose translation function globally
|
|
window.t = t;
|
|
|
|
// Initialize when DOM is ready
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', initializeI18n);
|
|
} else {
|
|
initializeI18n();
|
|
}
|