MyAstroBoard - Internationalization (i18n) System
Overview
MyAstroBoard implements a structured multilingual system supporting English, French, Spanish, German, Italian, and Portuguese. The i18n system ensures user-facing content can be translated while preventing hardcoded strings.
Current Status: ✅ Active implementation for weather alerts, astro-weather dynamic content, and events API translations.
Architecture
Components
MyAstroBoard i18n System
│
├── Frontend (JavaScript)
│ ├── static/js/i18n.js # Global i18n manager
│ ├── static/i18n/en.json # English translations
│ ├── static/i18n/fr.json # French translations
│ ├── static/i18n/xx.json # Other translations
│ ├── static/manifest.webmanifest # General webmanifest (English)
│ └── static/manifest.xx.webmanifest # Translated webmanifest
│
└── Backend (Python)
└── backend/utils/i18n_utils.py # Translation utilities
Language Support
| Language | Code | Status | Translation | Type |
|---|---|---|---|---|
| English | en | ✅ Active | Human | Default |
| French | fr | ✅ Active | Human | - |
| Spanish | es | ✅ Active | Google* | - |
| German | de | ✅ Active | Google* | - |
| Italian | it | ✅ Active | Google* | - |
| Portuguese | pt | ✅ Active | Google* | - |
Note: Google is used in scripts/translate_i18n_values.py
Frontend Implementation
i18n.js - The Global Manager
The file static/js/i18n.js:
- Loads translation files from
static/i18n/[language].json - Detects browser language automatically
- Manages language switching with persistent storage (localStorage)
- Handles placeholders in translation strings
- Provides fallback to English for missing keys
- Dispatches events when language changes
Using Translations in HTML
<h2 data-i18n="astro_weather.section_title">Current Conditions</h2>
<p data-i18n="common.loading">Loading...</p>
Using Translations in JavaScript
const msg = i18n.t('common.loading');
const alert = i18n.t('weather_alerts.critical_dew_risk', { time: '14:30' });
const lang = i18n.getCurrentLanguage();
await i18n.setLanguage('fr');
Language Selector
Files involved:
templates/index.htmlstatic/js/language_selector.js
Features:
- ✅ Dropdown selector for English, French, Spanish, German, Italian, Portuguese
- ✅ Automatic current-language selection
- ✅ URL/localStorage persistence
- ✅
i18nLanguageChangedevent propagation - ✅ Responsive behavior
Backend Implementation
i18n_utils.py
The module backend/utils/i18n_utils.py provides:
- Translation loading from JSON files
- Dot-notation key lookup
- Placeholder replacement
- Language management per request
- Alert creation helpers with translated content
Example:
from i18n_utils import I18nManager, get_translated_message
msg = get_translated_message('common.loading', language='fr')
manager = I18nManager('en')
caption = manager.t('weather_alerts.critical_dew_risk', time='14:30')
API Language Contract
For endpoints returning translated user-facing content:
- Frontend sends current language via
?lang=<code> - Backend may fallback to
Accept-Language, then default toen - Backend resolves placeholders before serialization
- Responses should not return hardcoded English where i18n keys exist
Implemented examples:
/api/weather/alerts?lang=fr/api/weather/astro-analysis?hours=24&lang=fr/api/events/upcoming?lang=fr
Exception - MQTT: the MQTT connector’s background publish loop (backend/connectors/mqtt_payloads.py)
has no request or session to read a language from, so next_event’s title/description are
hardcoded to en and always render English prose, on purpose - this is not a bug. MQTT consumers
that want localized text (e.g. a Home Assistant Lovelace card) should translate client-side off
the always-English, always-untranslated structured attributes instead: event_type, and the
per-event-kind fields eclipse_type, planet/planet2, shower_name, comet_name on
next_event_attributes (see docs/HOME_ASSISTANT.md). top_target_attributes.object_type is
the same pattern, already relied on by the card today.
Translation Keys Structure
Keys use dot notation:
namespace.section.key
Example:
{
"common": {
"loading": "Loading...",
"error": "Error"
},
"astro_weather": {
"section_title": "Current Conditions",
"loading_message": "Loading...",
"alerts_title": "Weather Alerts"
}
}
Add new translation
Translate all keys
First you need to translate all json key stored in static/i18n
You can copy en.json as reference or use Google translation script, which will automatically translate and create the json.
py scripts/translate_i18n_values.py --lang pt
By safety, the language must be manually add in this file first.
parser.add_argument("--lang", choices=["es", "de", "it", "pt"], required=True)
After a long time, you will receive a feedback. Please be carefull with it!
stats: translated=1223 kept=186 failed=2
You can also make a quick check to see if there is no missing translation.
py scripts/translate_checker.py
Of course, Google Translation must be manually checked, specially if this is your native language
Declare new translation
This is a multi-step checklist. Every item must be updated or the new language will be partially broken.
1. backend/utils/i18n_utils.py - translation file registry
# Supported languages
DEFAULT_LANGUAGE = 'en'
_TRANSLATION_FILENAMES = {
'en': 'en.json',
'fr': 'fr.json',
'es': 'es.json',
'de': 'de.json',
'it': 'it.json',
'pt': 'pt.json',
}
2. backend/app.py - manifest route allowlist
The web_manifest_localized() route has an explicit allowlist. Adding the language here is mandatory - missing it returns 404 and breaks the PWA service worker install.
@app.route('/manifest.<lang>.webmanifest')
def web_manifest_localized(lang):
allowed = {'fr', 'es', 'de', 'it', 'pt'} # <- add new language code here
if lang not in allowed:
return '', 404
...
3. static/sw.js - service worker shell cache
Add both the manifest and the i18n JSON file to APP_SHELL_URLS so they are pre-cached on PWA install:
const APP_SHELL_URLS = [
...
'/manifest.xx.webmanifest', // <- add localized manifest
...
'/static/i18n/xx.json', // <- add i18n dictionary
...
];
4. templates/index.html - language selector and manifest switcher
Add the language option to the footer selector:
<select id="language-select-footer" class="form-select form-select-sm language-select" aria-label="Language">
<option value="en">English</option>
<option value="fr">Français</option>
<option value="es">Español</option>
<option value="de">Deutsch</option>
<option value="it">Italiano</option>
<option value="pt">Português</option>
<!-- add new language here -->
</select>
Also add the language code to the supported array in the inline <script> block that switches the PWA manifest URL:
var supported = ['fr', 'es', 'de', 'it', 'pt']; // <- add new language code
5. scripts/translate_i18n_values.py - translation script
Add the language code to the choices list before running the script:
parser.add_argument("--lang", choices=["es", "de", "it", "pt", "xx"], required=True)
Translate webmanifest
Webmanifest is used in the PWA app - translating it gives native-language shortcut labels and app description on the home screen.
Webmanifest files are located in static/manifest.xx.webmanifest, where xx is the language code.
Be carefull, only some keys must be translated:
"description"
"lang"
"screenshots"->"label" (all)
"shortcuts"->"name" (all)
"shortcuts"->"short_name" (all)
"shortcuts"->"description" (all)
Then, translated webmanifest have to be declared on index.html
<script>
localStorage.setItem('myastroboard_app_version', '{{ version }}');
(function() {
var lang = localStorage.getItem('myastroboard_language') || '';
var supported = ['fr', 'es', 'de', 'it', 'pt'];
if (supported.indexOf(lang) !== -1) {
document.getElementById('pwa-manifest').href = '/manifest.' + lang + '.webmanifest?v={{ version }}';
}
})();
</script>
Last updated 2026-11-13