Initial commit: Add MathHomeworkHelper project with web and Android components
Some checks reported errors
continuous-integration/drone Build encountered an error

This commit is contained in:
Juan José Gutiérrez de Quevedo Pérez 2025-11-25 11:07:51 +01:00
commit f9558008e1
37 changed files with 5318 additions and 0 deletions

184
translations.js Normal file
View file

@ -0,0 +1,184 @@
// Translation Manager
class TranslationManager {
constructor() {
this.currentLanguage = 'en'; // Will be set during initialization
this.translations = {};
this.supportedLanguages = {
'en': { name: 'English', flag: '🇬🇧' },
'es': { name: 'Español', flag: '🇪🇸' },
'sv': { name: 'Svenska', flag: '🇸🇪' },
'el': { name: 'Ελληνικά', flag: '🇬🇷' }
};
// Country to language mapping
this.countryToLanguage = {
// Spanish-speaking countries
'ES': 'es', 'MX': 'es', 'AR': 'es', 'CO': 'es', 'PE': 'es', 'VE': 'es',
'CL': 'es', 'EC': 'es', 'BO': 'es', 'PY': 'es', 'UY': 'es', 'CU': 'es',
'DO': 'es', 'GT': 'es', 'HN': 'es', 'SV': 'es', 'NI': 'es', 'CR': 'es',
'PA': 'es', 'BZ': 'es', 'EQ': 'es',
// Swedish-speaking countries
'SE': 'sv', 'FI': 'sv', 'AX': 'sv',
// Greek-speaking countries
'GR': 'el', 'CY': 'el',
// Default to English for all others
};
}
// Detect language with geolocation fallback
async detectLanguageWithGeolocation() {
// Step 1: Check localStorage for saved preference
const savedLanguage = localStorage.getItem('preferredLanguage');
if (savedLanguage && this.supportedLanguages[savedLanguage]) {
console.log('Using saved language preference:', savedLanguage);
return savedLanguage;
}
// Step 2: Try browser language detection
const browserLang = this.detectBrowserLanguage();
if (browserLang !== 'en') {
console.log('Using browser language:', browserLang);
return browserLang;
}
// Step 3: Try geolocation API to get country and map to language
try {
const countryCode = await this.getCountryFromGeolocation();
if (countryCode) {
const language = this.countryToLanguage[countryCode] || 'en';
console.log('Using geolocation-based language:', language, 'from country:', countryCode);
return language;
}
} catch (error) {
console.warn('Geolocation failed, continuing with fallback:', error.message);
}
// Step 4: Fallback to English
console.log('Falling back to English');
return 'en';
}
// Detect browser language
detectBrowserLanguage() {
const browserLang = navigator.language || navigator.userLanguage;
const langCode = browserLang.split('-')[0];
// Check if the detected language is supported
if (this.supportedLanguages[langCode]) {
return langCode;
}
// Return 'en' if not supported (will trigger geolocation)
return 'en';
}
// Get country code from geolocation API
async getCountryFromGeolocation() {
const timeout = 5000; // 5 second timeout
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
// Try ip-api.com (free, no auth required)
const response = await fetch('https://ipapi.co/json/', {
signal: controller.signal,
headers: { 'Accept': 'application/json' }
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`API returned status ${response.status}`);
}
const data = await response.json();
const countryCode = data.country_code;
if (countryCode && typeof countryCode === 'string') {
console.log('Geolocation API returned country:', countryCode);
return countryCode;
}
throw new Error('No country code in response');
} catch (error) {
if (error.name === 'AbortError') {
console.warn('Geolocation API request timed out');
} else {
console.warn('Geolocation API error:', error.message);
}
return null;
}
}
// Load translation file
async loadTranslation(language) {
try {
const response = await fetch(`translations/${language}.json`);
if (!response.ok) {
throw new Error(`Failed to load ${language} translation`);
}
this.translations[language] = await response.json();
this.currentLanguage = language;
localStorage.setItem('preferredLanguage', language);
return true;
} catch (error) {
console.error('Error loading translation:', error);
return false;
}
}
// Get translated string
t(key) {
if (this.translations[this.currentLanguage] && this.translations[this.currentLanguage][key]) {
return this.translations[this.currentLanguage][key];
}
// Fallback to English if key not found
if (this.translations['en'] && this.translations['en'][key]) {
return this.translations['en'][key];
}
// Return key if no translation found
return key;
}
// Get current language
getCurrentLanguage() {
return this.currentLanguage;
}
// Get all supported languages
getSupportedLanguages() {
return this.supportedLanguages;
}
// Set language
setLanguage(language) {
if (this.supportedLanguages[language]) {
this.currentLanguage = language;
localStorage.setItem('preferredLanguage', language);
return true;
}
return false;
}
// Initialize translations
async initialize() {
// Detect language using geolocation fallback chain
this.currentLanguage = await this.detectLanguageWithGeolocation();
// Load all supported language files
const loadPromises = Object.keys(this.supportedLanguages).map(lang =>
this.loadTranslation(lang)
);
await Promise.all(loadPromises);
// Ensure current language is set correctly
if (!this.translations[this.currentLanguage]) {
this.currentLanguage = 'en';
}
}
}
// Create global instance
const i18n = new TranslationManager();