[call-me] - refactoring
This commit is contained in:
+100
-149
@@ -14,13 +14,11 @@ async function fetchAvailableLocales() {
|
||||
const response = await fetch('/locales');
|
||||
if (!response.ok) throw new Error('Failed to fetch locales');
|
||||
const data = await response.json();
|
||||
if (data && Array.isArray(data.locales) && data.locales.length > 0) {
|
||||
return data.locales;
|
||||
}
|
||||
if (data?.locales?.length > 0) return data.locales;
|
||||
} catch (error) {
|
||||
console.warn('Unable to fetch available locales, using fallback list', error);
|
||||
}
|
||||
return ['en', 'es', 'fr', 'it', 'de', 'pt', 'ru', 'ar', 'hi', 'zh', 'ja']; // Fallback list of common locales
|
||||
return ['en', 'es', 'fr', 'it', 'de', 'pt', 'ru', 'ar', 'hi', 'zh', 'ja']; // Fallback list
|
||||
}
|
||||
|
||||
function getLocaleLabel(locale) {
|
||||
@@ -44,28 +42,21 @@ function getLocaleLabel(locale) {
|
||||
* Initialize i18n
|
||||
*/
|
||||
async function initI18n() {
|
||||
// Get saved locale from localStorage or use browser language or default
|
||||
const savedLocale = localStorage.getItem('locale');
|
||||
const browserLocale = navigator.language.split('-')[0]; // Get 'en' from 'en-US'
|
||||
const supportedLocales = await fetchAvailableLocales();
|
||||
i18n.availableLocales = supportedLocales;
|
||||
|
||||
// Determine which locale to use
|
||||
if (savedLocale && supportedLocales.includes(savedLocale)) {
|
||||
i18n.currentLocale = savedLocale;
|
||||
} else if (supportedLocales.includes(browserLocale)) {
|
||||
i18n.currentLocale = browserLocale;
|
||||
} else {
|
||||
i18n.currentLocale = i18n.defaultLocale;
|
||||
}
|
||||
const savedLocale = localStorage.getItem('locale');
|
||||
const browserLocale = navigator.language.split('-')[0]; // Get 'en' from 'en-US'
|
||||
|
||||
i18n.currentLocale =
|
||||
(savedLocale && supportedLocales.includes(savedLocale)) ? savedLocale :
|
||||
supportedLocales.includes(browserLocale) ? browserLocale :
|
||||
i18n.defaultLocale;
|
||||
|
||||
// Load translations
|
||||
// Load translations, set up language selector, and translate the page
|
||||
await loadTranslations(i18n.currentLocale);
|
||||
|
||||
// Set up language selector
|
||||
setupLanguageSelector();
|
||||
|
||||
// Translate the page
|
||||
translatePage();
|
||||
|
||||
console.log('i18n initialized with locale:', i18n.currentLocale);
|
||||
@@ -121,12 +112,10 @@ function t(key, replacements = {}) {
|
||||
}
|
||||
|
||||
// Replace placeholders in the translation
|
||||
let translation = value;
|
||||
for (const [placeholder, replacement] of Object.entries(replacements)) {
|
||||
translation = translation.replace(new RegExp(`__${placeholder}__`, 'g'), replacement);
|
||||
}
|
||||
|
||||
return translation;
|
||||
return Object.entries(replacements).reduce(
|
||||
(str, [placeholder, replacement]) => str.replace(new RegExp(`__${placeholder}__`, 'g'), replacement),
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,9 +128,7 @@ async function changeLanguage(locale) {
|
||||
|
||||
// Update language selector
|
||||
const languageSelect = document.getElementById('languageSelect');
|
||||
if (languageSelect) {
|
||||
languageSelect.value = locale;
|
||||
}
|
||||
if (languageSelect) languageSelect.value = locale;
|
||||
|
||||
// Dispatch custom event to notify other scripts that language has changed
|
||||
window.dispatchEvent(new CustomEvent('languageChanged', { detail: { locale } }));
|
||||
@@ -152,61 +139,74 @@ async function changeLanguage(locale) {
|
||||
*/
|
||||
function setupLanguageSelector() {
|
||||
const languageSelect = document.getElementById('languageSelect');
|
||||
if (languageSelect) {
|
||||
// Rebuild options dynamically
|
||||
const locales =
|
||||
Array.isArray(i18n.availableLocales) && i18n.availableLocales.length ? i18n.availableLocales : ['en'];
|
||||
languageSelect.innerHTML = '';
|
||||
locales.forEach((locale) => {
|
||||
const option = document.createElement('option');
|
||||
option.value = locale;
|
||||
option.textContent = getLocaleLabel(locale);
|
||||
languageSelect.appendChild(option);
|
||||
});
|
||||
if (!languageSelect) return;
|
||||
|
||||
// Set current locale
|
||||
languageSelect.value = i18n.currentLocale;
|
||||
// Rebuild options dynamically
|
||||
const locales = i18n.availableLocales.length ? i18n.availableLocales : ['en'];
|
||||
languageSelect.innerHTML = locales
|
||||
.map((locale) => `<option value="${locale}">${getLocaleLabel(locale)}</option>`)
|
||||
.join('');
|
||||
|
||||
// Add change event listener
|
||||
languageSelect.addEventListener('change', async (e) => {
|
||||
const newLocale = e.target.value;
|
||||
await changeLanguage(newLocale);
|
||||
});
|
||||
// Set current locale and add change event listener
|
||||
languageSelect.value = i18n.currentLocale;
|
||||
languageSelect.addEventListener('change', (e) => changeLanguage(e.target.value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply translation to an element based on attribute type
|
||||
* @param {Element} element
|
||||
* @param {string} key - Translation key
|
||||
* @param {string} attrType - 'i18n', 'placeholder', or 'title'
|
||||
*/
|
||||
function applyTranslation(element, key, attrType) {
|
||||
const translation = t(key);
|
||||
const isInput = element.tagName === 'INPUT' || element.tagName === 'TEXTAREA';
|
||||
|
||||
switch (attrType) {
|
||||
case 'i18n':
|
||||
if (isInput && (element.type === 'button' || element.type === 'submit')) {
|
||||
element.value = translation;
|
||||
} else if (isInput) {
|
||||
element.placeholder = translation;
|
||||
} else {
|
||||
element.textContent = translation;
|
||||
}
|
||||
break;
|
||||
case 'placeholder':
|
||||
element.placeholder = translation;
|
||||
break;
|
||||
case 'title':
|
||||
setElementTitle(element, translation);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate elements matching a selector
|
||||
* @param {Element} root - Root element to search within
|
||||
* @param {string} selector - CSS selector to find elements
|
||||
* @param {string} attribute - Attribute name (data-i18n, data-i18n-placeholder, data-i18n-title)
|
||||
* @param {string} attrType - Type for applyTranslation ('i18n', 'placeholder', 'title')
|
||||
*/
|
||||
function translateElements(root, selector, attribute, attrType) {
|
||||
root.querySelectorAll(selector).forEach((element) => {
|
||||
const key = element.getAttribute(attribute);
|
||||
if (key) applyTranslation(element, key, attrType);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate all elements with data-i18n attribute
|
||||
*/
|
||||
function translatePage() {
|
||||
// Translate elements with data-i18n attribute
|
||||
document.querySelectorAll('[data-i18n]').forEach((element) => {
|
||||
const key = element.getAttribute('data-i18n');
|
||||
const translation = t(key);
|
||||
const translationTypes = [
|
||||
{ selector: '[data-i18n]', attribute: 'data-i18n', type: 'i18n' },
|
||||
{ selector: '[data-i18n-placeholder]', attribute: 'data-i18n-placeholder', type: 'placeholder' },
|
||||
{ selector: '[data-i18n-title]', attribute: 'data-i18n-title', type: 'title' },
|
||||
];
|
||||
|
||||
// Update the appropriate property based on element type
|
||||
if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
|
||||
if (element.type === 'button' || element.type === 'submit') {
|
||||
element.value = translation;
|
||||
} else {
|
||||
element.placeholder = translation;
|
||||
}
|
||||
} else {
|
||||
element.textContent = translation;
|
||||
}
|
||||
});
|
||||
|
||||
// Translate elements with data-i18n-placeholder attribute
|
||||
document.querySelectorAll('[data-i18n-placeholder]').forEach((element) => {
|
||||
const key = element.getAttribute('data-i18n-placeholder');
|
||||
element.placeholder = t(key);
|
||||
});
|
||||
|
||||
// Translate elements with data-i18n-title attribute (for tooltips)
|
||||
document.querySelectorAll('[data-i18n-title]').forEach((element) => {
|
||||
const key = element.getAttribute('data-i18n-title');
|
||||
const translatedTitle = t(key);
|
||||
setElementTitle(element, translatedTitle);
|
||||
translationTypes.forEach(({ selector, attribute, type }) => {
|
||||
translateElements(document, selector, attribute, type);
|
||||
});
|
||||
|
||||
// Update document title
|
||||
@@ -251,13 +251,10 @@ function setElementTitle(element, translatedTitle) {
|
||||
* Update custom translations for specific elements
|
||||
*/
|
||||
function updateCustomTranslations() {
|
||||
// Update app title
|
||||
const appTitle = document.getElementById('appTitle');
|
||||
if (appTitle) appTitle.textContent = t('appTitle');
|
||||
|
||||
// Update app name
|
||||
const appName = document.getElementById('appName');
|
||||
if (appName) appName.textContent = t('appName');
|
||||
['appTitle', 'appName'].forEach((id) => {
|
||||
const element = document.getElementById(id);
|
||||
if (element) element.textContent = t(id);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -279,35 +276,22 @@ function showTranslatedAlert(titleKey, textKey, icon = 'info') {
|
||||
}
|
||||
|
||||
// Initialize i18n when DOM is ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initI18n);
|
||||
} else {
|
||||
// DOM already loaded, initialize immediately
|
||||
initI18n();
|
||||
}
|
||||
(document.readyState === 'loading'
|
||||
? document.addEventListener('DOMContentLoaded', initI18n)
|
||||
: initI18n());
|
||||
|
||||
// Re-translate when dynamic content is added (for mobile compatibility)
|
||||
if (typeof MutationObserver !== 'undefined') {
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.addedNodes.length) {
|
||||
mutation.addedNodes.forEach((node) => {
|
||||
if (node.nodeType === 1) {
|
||||
// Element node
|
||||
translateElement(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
mutation.addedNodes.forEach((node) => {
|
||||
if (node.nodeType === 1) translateElement(node); // Element node
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Start observing after a short delay to allow initial setup
|
||||
setTimeout(() => {
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
}, 100);
|
||||
setTimeout(() => observer.observe(document.body, { childList: true, subtree: true }), 100);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -315,59 +299,26 @@ if (typeof MutationObserver !== 'undefined') {
|
||||
* @param {Element} element - The element to translate
|
||||
*/
|
||||
function translateElement(element) {
|
||||
// Translate data-i18n
|
||||
if (element.hasAttribute && element.hasAttribute('data-i18n')) {
|
||||
const key = element.getAttribute('data-i18n');
|
||||
const translation = t(key);
|
||||
if (!element.hasAttribute) return;
|
||||
|
||||
if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
|
||||
if (element.type === 'button' || element.type === 'submit') {
|
||||
element.value = translation;
|
||||
} else {
|
||||
element.placeholder = translation;
|
||||
}
|
||||
} else {
|
||||
element.textContent = translation;
|
||||
const translationTypes = [
|
||||
{ attribute: 'data-i18n', type: 'i18n' },
|
||||
{ attribute: 'data-i18n-placeholder', type: 'placeholder' },
|
||||
{ attribute: 'data-i18n-title', type: 'title' },
|
||||
];
|
||||
|
||||
// Translate the element itself
|
||||
translationTypes.forEach(({ attribute, type }) => {
|
||||
if (element.hasAttribute(attribute)) {
|
||||
const key = element.getAttribute(attribute);
|
||||
if (key) applyTranslation(element, key, type);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Translate data-i18n-placeholder
|
||||
if (element.hasAttribute && element.hasAttribute('data-i18n-placeholder')) {
|
||||
const key = element.getAttribute('data-i18n-placeholder');
|
||||
element.placeholder = t(key);
|
||||
}
|
||||
|
||||
// Translate data-i18n-title
|
||||
if (element.hasAttribute && element.hasAttribute('data-i18n-title')) {
|
||||
const key = element.getAttribute('data-i18n-title');
|
||||
setElementTitle(element, t(key));
|
||||
}
|
||||
|
||||
// Recursively translate children
|
||||
// Translate children
|
||||
if (element.querySelectorAll) {
|
||||
element.querySelectorAll('[data-i18n]').forEach((child) => {
|
||||
const key = child.getAttribute('data-i18n');
|
||||
const translation = t(key);
|
||||
|
||||
if (child.tagName === 'INPUT' || child.tagName === 'TEXTAREA') {
|
||||
if (child.type === 'button' || child.type === 'submit') {
|
||||
child.value = translation;
|
||||
} else {
|
||||
child.placeholder = translation;
|
||||
}
|
||||
} else {
|
||||
child.textContent = translation;
|
||||
}
|
||||
});
|
||||
|
||||
element.querySelectorAll('[data-i18n-placeholder]').forEach((child) => {
|
||||
const key = child.getAttribute('data-i18n-placeholder');
|
||||
child.placeholder = t(key);
|
||||
});
|
||||
|
||||
element.querySelectorAll('[data-i18n-title]').forEach((child) => {
|
||||
const key = child.getAttribute('data-i18n-title');
|
||||
setElementTitle(child, t(key));
|
||||
translationTypes.forEach(({ attribute, type }) => {
|
||||
translateElements(element, `[${attribute}]`, attribute, type);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user