Implement lightweight offline i18n system with French/English support

**Core System:**
- Add i18n.js translation library with data-attribute support
- Create translation files (fr.json, en.json) with offline support
- Store language preference in SQLite config_table
- Add backend endpoints for get/set language

**UI Features:**
- Add language switcher dropdown to topbar (🇫🇷 FR / 🇬🇧 EN)
- Auto-sync language selection across all pages
- Support for static HTML and dynamically created elements

**Implementation:**
- Migrate sensors.html as working example
- Add data-i18n attributes to all UI elements
- Support for buttons, inputs, and dynamic content
- Comprehensive README documentation in html/lang/

**Technical Details:**
- Works completely offline (local JSON files)
- No external dependencies
- Database-backed user preference
- Event-based language change notifications
- Automatic translation on page load

Next steps: Gradually migrate other pages (admin, wifi, index, etc.)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
PaulVua
2026-01-05 18:10:06 +01:00
parent 906eaa851d
commit 163d60bf34
8 changed files with 561 additions and 24 deletions

View File

@@ -49,11 +49,11 @@
</aside>
<!-- Main content -->
<main class="col-md-10 ms-sm-auto col-lg-11 offset-md-2 offset-lg-1 px-md-4">
<h1 class="mt-4">Les sondes de mesure</h1>
<p>Votre capteur NebuleAir est équipé de une ou plusieurs sondes qui permettent de mesurer certaines variables environnementales. La mesure
<h1 class="mt-4" data-i18n="sensors.title">Les sondes de mesure</h1>
<p data-i18n="sensors.description">Votre capteur NebuleAir est équipé de une ou plusieurs sondes qui permettent de mesurer certaines variables environnementales. La mesure
est automatique mais vous pouvez ici vous assurer de leur bon fonctionnement.
</p>
<div class="row mb-3" id="card-container"></div>
<div class="row mb-3" id="card-container"></div>
</main>
</div>
</div>
@@ -64,6 +64,8 @@
<script src="assets/jquery/jquery-3.7.1.min.js"></script>
<!-- Link Bootstrap JS and Popper.js locally -->
<script src="assets/js/bootstrap.bundle.js"></script>
<!-- i18n translation system -->
<script src="assets/js/i18n.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
@@ -313,13 +315,13 @@ error: function(xhr, status, error) {
const cardHTML = `
<div class="col-sm-3">
<div class="card">
<div class="card-header">
<div class="card-header" data-i18n="sensors.npm.headerUart">
Port UART
</div>
<div class="card-body">
<h5 class="card-title">NextPM</h5>
<p class="card-text">Capteur particules fines.</p>
<button class="btn btn-primary" onclick="getNPM_values('ttyAMA5')">Get Data</button>
<h5 class="card-title" data-i18n="sensors.npm.title">NextPM</h5>
<p class="card-text" data-i18n="sensors.npm.description">Capteur particules fines.</p>
<button class="btn btn-primary" onclick="getNPM_values('ttyAMA5')" data-i18n="common.getData">Get Data</button>
<br>
<div id="loading_ttyAMA5" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<table class="table table-striped-columns">
@@ -337,13 +339,13 @@ error: function(xhr, status, error) {
const i2C_BME_HTML = `
<div class="col-sm-3">
<div class="card">
<div class="card-header">
<div class="card-header" data-i18n="sensors.bme280.headerI2c">
Port I2C
</div>
<div class="card-body">
<h5 class="card-title">BME280 Temp/Hum sensor</h5>
<p class="card-text">Capteur température et humidité sur le port I2C.</p>
<button class="btn btn-primary mb-1" onclick="getBME280_values()">Get Data</button>
<h5 class="card-title" data-i18n="sensors.bme280.title">BME280 Temp/Hum sensor</h5>
<p class="card-text" data-i18n="sensors.bme280.description">Capteur température et humidité sur le port I2C.</p>
<button class="btn btn-primary mb-1" onclick="getBME280_values()" data-i18n="common.getData">Get Data</button>
<br>
<div id="loading_BME280" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<table class="table table-striped-columns">
@@ -352,7 +354,7 @@ error: function(xhr, status, error) {
</div>
</div>
</div>`;
container.innerHTML += i2C_BME_HTML; // Add the I2C card if condition is met
}
@@ -361,16 +363,16 @@ error: function(xhr, status, error) {
const i2C_HTML = `
<div class="col-sm-3">
<div class="card">
<div class="card-header">
<div class="card-header" data-i18n="sensors.noise.headerI2c">
Port I2C
</div>
<div class="card-body">
<h5 class="card-title">Decibel Meter</h5>
<p class="card-text">Capteur bruit sur le port I2C.</p>
<button class="btn btn-primary mb-1" onclick="getNoise_values()">Get Data</button>
<h5 class="card-title" data-i18n="sensors.noise.title">Decibel Meter</h5>
<p class="card-text" data-i18n="sensors.noise.description">Capteur bruit sur le port I2C.</p>
<button class="btn btn-primary mb-1" onclick="getNoise_values()" data-i18n="common.getData">Get Data</button>
<br>
<button class="btn btn-success" onclick="startNoise()">Start recording</button>
<button class="btn btn-danger" onclick="stopNoise()">Stop recording</button>
<button class="btn btn-success" onclick="startNoise()" data-i18n="common.startRecording">Start recording</button>
<button class="btn btn-danger" onclick="stopNoise()" data-i18n="common.stopRecording">Stop recording</button>
<div id="loading_noise" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<table class="table table-striped-columns">
<tbody id="data-table-body_noise"></tbody>
@@ -378,7 +380,7 @@ error: function(xhr, status, error) {
</div>
</div>
</div>`;
container.innerHTML += i2C_HTML; // Add the I2C card if condition is met
}
@@ -409,8 +411,8 @@ error: function(xhr, status, error) {
</div>
<div class="card-body">
<h5 class="card-title">Sonde Envea ${name}</h5>
<p class="card-text">Capteur gas.</p>
<button class="btn btn-primary" onclick="getENVEA_values('${port}','${name}')">Get Data</button>
<p class="card-text" data-i18n="sensors.envea.description">Capteur gas.</p>
<button class="btn btn-primary" onclick="getENVEA_values('${port}','${name}')" data-i18n="common.getData">Get Data</button>
<div id="loading_envea${name}" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<table class="table table-striped-columns">
<tbody id="data-table-body_envea${name}"></tbody>
@@ -420,6 +422,9 @@ error: function(xhr, status, error) {
</div>`;
container.innerHTML += cardHTML; // Ajouter la carte au conteneur
});
// Apply translations to dynamically created Envea cards
i18n.applyTranslations();
@@ -432,6 +437,9 @@ error: function(xhr, status, error) {
}//end if envea
// Apply translations to all dynamically created sensor cards
i18n.applyTranslations();
} // end createSensorCards function
//get local RTC