Add comprehensive internationalization (i18n) with Spanish language support
This major feature introduces complete internationalization architecture with Spanish (Mexico) as the first additional language: ## New Features: - **I18n Architecture**: Complete client-side and server-side internationalization system - **Spanish (Mexico) Support**: Full es-MX translations for all user-facing text - **Language Selector**: Dynamic language switching UI component in header - **API Endpoints**: RESTful endpoints for serving translations (/api/i18n) - **Progressive Enhancement**: Language detection via Accept-Language header and cookies ## Technical Implementation: - **Frontend**: Client-side i18n.js with automatic DOM translation and language selector - **Backend**: Server-side i18n service with locale detection middleware - **Build Process**: Automated copying of translation files to dist/ directory - **Responsive Design**: Language selector integrated into header controls layout ## Files Added: - public/i18n.js - Client-side internationalization library - src/i18n/index.ts - Server-side i18n service - src/i18n/locales/en.json - English translations - src/i18n/locales/es-MX.json - Spanish (Mexico) translations - src/routes/i18n.ts - API endpoints for translations ## Files Modified: - package.json - Updated build process to include i18n files - public/index.html - Added i18n attributes and language selector - public/app.js - Integrated dynamic translation updates - src/server.ts - Added locale detection middleware - src/scss/pages/_index.scss - Language selector styling This implementation supports easy addition of future languages and maintains backward compatibility while providing a seamless multilingual experience. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
9b7325a9dd
commit
8b1787ec47
10 changed files with 1006 additions and 17 deletions
|
@ -1,4 +1,9 @@
|
|||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Initialize language selector
|
||||
if (window.i18n) {
|
||||
window.i18n.createLanguageSelector('language-selector-container');
|
||||
}
|
||||
|
||||
const map = L.map('map').setView([42.96008, -85.67403], 10);
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
|
@ -82,7 +87,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
.then(locations => {
|
||||
showMarkers(locations);
|
||||
const countElement = document.getElementById('location-count');
|
||||
countElement.textContent = `${locations.length} active report${locations.length !== 1 ? 's' : ''}`;
|
||||
const reportsText = locations.length === 1 ?
|
||||
(window.t ? window.t('map.activeReports') : 'active report') :
|
||||
(window.t ? window.t('map.activeReportsPlural') : 'active reports');
|
||||
countElement.textContent = `${locations.length} ${reportsText}`;
|
||||
|
||||
// Add visual indicator of last update
|
||||
const now = new Date();
|
||||
|
@ -92,11 +100,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
countElement.title = `Last updated: ${timeStr}`;
|
||||
const lastUpdatedText = window.t ? window.t('time.lastUpdated') : 'Last updated:';
|
||||
countElement.title = `${lastUpdatedText} ${timeStr}`;
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Error fetching locations:', err);
|
||||
document.getElementById('location-count').textContent = 'Error loading locations';
|
||||
const errorText = window.t ? window.t('table.errorLoading') : 'Error loading locations';
|
||||
document.getElementById('location-count').textContent = errorText;
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -242,13 +252,15 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
.then(location => {
|
||||
// Immediately refresh all locations to show the new one
|
||||
refreshLocations();
|
||||
messageDiv.textContent = 'Location reported successfully!';
|
||||
const successText = window.t ? window.t('form.reportSubmittedMsg') : 'Location reported successfully!';
|
||||
messageDiv.textContent = successText;
|
||||
messageDiv.className = 'message success';
|
||||
locationForm.reset();
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Error reporting location:', err);
|
||||
messageDiv.textContent = 'Error reporting location.';
|
||||
const errorText = window.t ? window.t('form.submitError') : 'Error reporting location.';
|
||||
messageDiv.textContent = errorText;
|
||||
messageDiv.className = 'message error';
|
||||
})
|
||||
.finally(() => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue