From d26b2c6c1343bcfb18288436ecf42cede0adc775 Mon Sep 17 00:00:00 2001 From: Alexandre CUER Date: Sun, 1 Nov 2026 10:51:40 +0100 Subject: [PATCH 1/7] add i18n to idf-legend and idf-viewer and various code quality corrections --- _layouts/default.html | 2 +- lib/modules/components/idf-legend.js | 5 +++-- lib/modules/components/idf-viewer.js | 23 ++++++++++++----------- lib/modules/components/obm-carousel.js | 2 +- lib/modules/locales/locale-en.js | 17 +++++++++++++++++ lib/modules/locales/locale-fr.js | 19 ++++++++++++++++++- 6 files changed, 52 insertions(+), 16 deletions(-) diff --git a/_layouts/default.html b/_layouts/default.html index 2cf0831..d970cb3 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -8,7 +8,7 @@

- Maitrisez le fonctionnement de votre bâtiment à travers son jumeau numérique + Maitrisez le fonctionnement de votre bâtiment à travers un jumeau numérique

diff --git a/lib/modules/components/idf-legend.js b/lib/modules/components/idf-legend.js index 3b24997..1360df5 100644 --- a/lib/modules/components/idf-legend.js +++ b/lib/modules/components/idf-legend.js @@ -73,7 +73,8 @@ const TEMPLATE = /* html */ ` export class IdfLegend extends HTMLElement { static get observedAttributes() { return ['viewer']; } constructor() { - super(); this.attachShadow({ mode:'open' }); this.shadowRoot.innerHTML = TEMPLATE; + super(); this.attachShadow({ mode:'open' }); + this.shadowRoot.innerHTML = TEMPLATE; this._panel = this.shadowRoot.querySelector('.panel'); this._viewer = null; this._onData = (event) => this._render(event.detail?.data ?? null); } @@ -131,7 +132,7 @@ export class IdfLegend extends HTMLElement { const keysColumn = doc.createElement('section'); keysColumn.className = 'column'; const keysTitle = doc.createElement('h4'); - keysTitle.textContent = 'Légende'; + keysTitle.textContent = t("legend.title"); keysColumn.append(keysTitle); for (const key of data.keys) { diff --git a/lib/modules/components/idf-viewer.js b/lib/modules/components/idf-viewer.js index b63a9dc..0473571 100644 --- a/lib/modules/components/idf-viewer.js +++ b/lib/modules/components/idf-viewer.js @@ -22,6 +22,7 @@ import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.170.0/+esm'; import { OrbitControls } from 'https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/controls/OrbitControls.js/+esm'; import { parseGeometryIDF } from './idf-parser.js'; import { buildPolygonGeometry, polygonNormal } from './idf-geometry.js'; +import { t } from "../locales/locales.js"; export { parseGeometryIDF }; @@ -705,24 +706,24 @@ export class IdfViewer extends HTMLElement { } if (this._colorMode === 'type') { keys = [ - ['Mur extérieur', TYPE_COLORS.wall], - ['Mur intérieur', TYPE_COLORS.wallInterior], - ['Mur enterré', TYPE_COLORS.wallGround], - ['Toiture', TYPE_COLORS.roof], - ['Plafond', TYPE_COLORS.ceiling], - ['Plancher', TYPE_COLORS.floor], - ['Plancher sur sol', TYPE_COLORS.floorGround], + [t("legend.type.wall"), TYPE_COLORS.wall], + [t("legend.type.wallInterior"), TYPE_COLORS.wallInterior], + [t("legend.type.wallGround"), TYPE_COLORS.wallGround], + [t("legend.type.roof"), TYPE_COLORS.roof], + [t("legend.type.ceiling"), TYPE_COLORS.ceiling], + [t("legend.type.floor"), TYPE_COLORS.floor], + [t("legend.type.floorGround"), TYPE_COLORS.floorGround] ]; } if (this._model.fenestrations.length) { keys.push( - ['Fenêtre', TYPE_COLORS.window], - ['Porte vitrée', TYPE_COLORS.glassdoor], - ['Porte', TYPE_COLORS.door], + [t("legend.type.window"), TYPE_COLORS.window], + [t("legend.type.glassdoor"), TYPE_COLORS.glassdoor], + [t("legend.type.door"), TYPE_COLORS.door], ); } if (this._model.shading.length) { - keys.push(['Masque', TYPE_COLORS.shading]); + keys.push([t("legend.type.shading"), TYPE_COLORS.shading]); } return { colorMode: this._colorMode, zones, keys: keys.map(([label, color]) => ({ label, color: hex(color) })) }; } diff --git a/lib/modules/components/obm-carousel.js b/lib/modules/components/obm-carousel.js index 3416a62..370a113 100644 --- a/lib/modules/components/obm-carousel.js +++ b/lib/modules/components/obm-carousel.js @@ -1,4 +1,4 @@ -import { getLocale, setLocale, t } from "../locales/locales.js"; +import { t } from "../locales/locales.js"; class ObmCarousel extends HTMLElement { constructor() { diff --git a/lib/modules/locales/locale-en.js b/lib/modules/locales/locale-en.js index e93acba..5637e16 100644 --- a/lib/modules/locales/locale-en.js +++ b/lib/modules/locales/locale-en.js @@ -2,6 +2,23 @@ export default { language: "Language", nav: { home: "OBM home", menu: "Show menu", main: "Main navigation", philosophy: "Our philosophy" }, footer: { contact: "Contact" }, + legend: { + title: "Legend", + type: { + wall: "wall", + wallInterior: "interior wall", + wallGround: "ground wall", + roof: "roof", + ceiling: "ceiling", + floor: "floor", + floorGround: "ground floor", + window: "window", + glassdoor: "glassdoor", + door: "door", + shading: "shading", + other: "other" + }, + }, home: { master: "Understand and control your building's operations through a digital twin", title: "Take control of your energy consumption", diff --git a/lib/modules/locales/locale-fr.js b/lib/modules/locales/locale-fr.js index ca5ffbc..b098e34 100644 --- a/lib/modules/locales/locale-fr.js +++ b/lib/modules/locales/locale-fr.js @@ -2,8 +2,25 @@ export default { language: "Langue", nav: { home: "Accueil OBM", menu: "Afficher le menu", main: "Navigation principale", philosophy: "Notre philosophie" }, footer: { contact: "Contact" }, + legend: { + title: "Légende", + type: { + wall: "Mur extérieur", + wallInterior: "Mur intérieur", + wallGround: "Mur enterré", + roof: "Toiture", + ceiling: "Plafond", + floor: "Plancher", + floorGround: "Plancher sur sol", + window: "Fenêtre", + glassdoor: "Porte vitrée", + door: "Porte", + shading: "Masque", + other: "Autre" + }, + }, home: { - master: "Maitrisez le fonctionnement de votre bâtiment à travers son jumeau numérique", + master: "Maitrisez le fonctionnement de votre bâtiment à travers un jumeau numérique", title: "Pilotez vos consommations énergétiques", intro: "Chez OBM, nous prenons soin de vos systèmes énergétiques, chaudières, centrales de traitement d'air.", supervision: "Nous en supervisons le fonctionnement et faisons le lien avec votre exploitant pour la maintenance préventive.", From 60840d86a93131210571ad3d9169648249dc8116 Mon Sep 17 00:00:00 2001 From: Alexandre CUER Date: Sun, 1 Nov 2026 11:09:17 +0100 Subject: [PATCH 2/7] improve traduction and add t import in idf-legend.js + improve lisibility in idf-legend.js --- lib/modules/components/idf-legend.js | 30 +++++++++++++++++++++------- lib/modules/locales/locale-en.js | 5 +++-- lib/modules/locales/locale-fr.js | 1 + 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/lib/modules/components/idf-legend.js b/lib/modules/components/idf-legend.js index 1360df5..a462138 100644 --- a/lib/modules/components/idf-legend.js +++ b/lib/modules/components/idf-legend.js @@ -1,4 +1,6 @@ /** Standalone legend for an element. */ +import { t } from "../locales/locales.js"; + const TEMPLATE = /* html */ ` `; export class IdfLegend extends HTMLElement { - static get observedAttributes() { return ['viewer']; } + static get observedAttributes() { + return ['viewer']; + } constructor() { super(); this.attachShadow({ mode:'open' }); this.shadowRoot.innerHTML = TEMPLATE; this._panel = this.shadowRoot.querySelector('.panel'); this._viewer = null; this._onData = (event) => this._render(event.detail?.data ?? null); } - connectedCallback() { this._connectViewer(); } - disconnectedCallback() { this._disconnectViewer(); } - attributeChangedCallback(name, oldValue, newValue) { if (oldValue !== newValue && this.isConnected) this._connectViewer(); } + connectedCallback() { + this._connectViewer(); + } + disconnectedCallback() { + this._disconnectViewer(); + } + attributeChangedCallback(name, oldValue, newValue) { + if (oldValue !== newValue && this.isConnected) this._connectViewer(); + } _connectViewer() { this._disconnectViewer(); const id = this.getAttribute('viewer'); this._viewer = id ? this.ownerDocument.getElementById(id) : this.parentElement?.querySelector('idf-viewer'); - if (!this._viewer || this._viewer.localName !== 'idf-viewer') { this._render(null); return; } + if (!this._viewer || this._viewer.localName !== 'idf-viewer') { + this._render(null); + return; + } this._viewer.addEventListener('legend-data-changed', this._onData); this._render(this._viewer.getLegendData?.() ?? null); } - _disconnectViewer() { this._viewer?.removeEventListener('legend-data-changed', this._onData); this._viewer = null; } + _disconnectViewer() { + this._viewer?.removeEventListener('legend-data-changed', this._onData); + this._viewer = null; + } _render(data) { if (!data || this._viewer?.hasAttribute('no-legend')) { this._panel.hidden = true; @@ -101,7 +117,7 @@ export class IdfLegend extends HTMLElement { const zonesColumn = doc.createElement('section'); zonesColumn.className = 'column'; const zonesTitle = doc.createElement('h4'); - zonesTitle.textContent = 'Zones'; + zonesTitle.textContent = t("legend.zones"); zonesColumn.append(zonesTitle); for (const zone of data.zones) { diff --git a/lib/modules/locales/locale-en.js b/lib/modules/locales/locale-en.js index 5637e16..2409d25 100644 --- a/lib/modules/locales/locale-en.js +++ b/lib/modules/locales/locale-en.js @@ -4,14 +4,15 @@ export default { footer: { contact: "Contact" }, legend: { title: "Legend", + zones: "Zones", type: { wall: "wall", wallInterior: "interior wall", - wallGround: "ground wall", + wallGround: "below-grade wall", roof: "roof", ceiling: "ceiling", floor: "floor", - floorGround: "ground floor", + floorGround: "slab-on-ground", window: "window", glassdoor: "glassdoor", door: "door", diff --git a/lib/modules/locales/locale-fr.js b/lib/modules/locales/locale-fr.js index b098e34..51248bd 100644 --- a/lib/modules/locales/locale-fr.js +++ b/lib/modules/locales/locale-fr.js @@ -4,6 +4,7 @@ export default { footer: { contact: "Contact" }, legend: { title: "Légende", + zones: "Zones", type: { wall: "Mur extérieur", wallInterior: "Mur intérieur", From ba8163aa208e49419e1090c4bf30fcaf0300e714 Mon Sep 17 00:00:00 2001 From: Alexandre CUER Date: Sun, 1 Nov 2026 15:16:10 +0100 Subject: [PATCH 3/7] add listener on obm-locale-changed in idf-legend and complete i18n --- lib/modules/components/idf-legend.js | 7 ++ lib/modules/components/idf-viewer.js | 147 ++++++++++++++++------- lib/modules/components/obm-navigation.js | 14 ++- lib/modules/locales/locale-en.js | 24 +++- lib/modules/locales/locale-fr.js | 22 +++- 5 files changed, 168 insertions(+), 46 deletions(-) diff --git a/lib/modules/components/idf-legend.js b/lib/modules/components/idf-legend.js index a462138..df5c2f1 100644 --- a/lib/modules/components/idf-legend.js +++ b/lib/modules/components/idf-legend.js @@ -82,11 +82,18 @@ export class IdfLegend extends HTMLElement { this._panel = this.shadowRoot.querySelector('.panel'); this._viewer = null; this._onData = (event) => this._render(event.detail?.data ?? null); } + _onLocaleChanged = () => { + if (this._legendData) { + this._render(this._legendData); + } + } connectedCallback() { this._connectViewer(); + document.addEventListener("obm-locale-changed", this._onLocaleChanged); } disconnectedCallback() { this._disconnectViewer(); + document.removeEventListener("obm-locale-changed", this._onLocaleChanged); } attributeChangedCallback(name, oldValue, newValue) { if (oldValue !== newValue && this.isConnected) this._connectViewer(); diff --git a/lib/modules/components/idf-viewer.js b/lib/modules/components/idf-viewer.js index 0473571..f43a69a 100644 --- a/lib/modules/components/idf-viewer.js +++ b/lib/modules/components/idf-viewer.js @@ -59,8 +59,20 @@ const ZONE_PALETTE = [ 0xb07aa1, 0xff9da7, 0x9c755f, 0xbab0ac, 0x1f77b4, 0x2ca02c, ]; -const INTERIOR_BC = new Set(['surface', 'zone', 'adiabatic', 'otherzone', 'othersidecoefficients', 'othersideconditionsmodel']); -const GLAZED_TYPES = new Set(['window', 'glassdoor', 'tubulardaylightdome', 'tubulardaylightdiffuser']); +const INTERIOR_BC = new Set([ + 'surface', + 'zone', + 'adiabatic', + 'otherzone', + 'othersidecoefficients', + 'othersideconditionsmodel' +]); +const GLAZED_TYPES = new Set([ + 'window', + 'glassdoor', + 'tubulardaylightdome', + 'tubulardaylightdiffuser' +]); function isInteriorBC(bc) { return INTERIOR_BC.has((bc || '').toLowerCase()); @@ -117,7 +129,13 @@ const TEMPLATE = /* html */ ` } :host([hidden]) { display: none; } .canvas-host { position: absolute; inset: 0; } - .canvas-host canvas { display: block; width: 100%; height: 100%; outline: none; touch-action: none; } + .canvas-host canvas { + display: block; + width: 100%; + height: 100%; + outline: none; + touch-action: none; + } .panel { position: absolute; background: rgba(255, 255, 255, 0.92); @@ -126,25 +144,54 @@ const TEMPLATE = /* html */ ` padding: 6px 8px; backdrop-filter: blur(4px); } - .toolbar { top: 8px; left: 8px; display: flex; gap: 8px; flex-wrap: wrap; align-items: center; } - .toolbar label { display: inline-flex; align-items: center; gap: 4px; cursor: pointer; white-space: nowrap; } + .toolbar { + top: 8px; + left: 8px; + display: flex; + gap: 8px; + flex-wrap: wrap; + align-items: center; + } + .toolbar label { + display: inline-flex; + align-items: center; + gap: 4px; + cursor: pointer; + white-space: nowrap; + } .toolbar select, .toolbar button, .toolbar .file-btn { - font: inherit; padding: 3px 8px; border: 1px solid #c8ccd2; border-radius: 4px; background: #fff; cursor: pointer; + font: inherit; + padding: 3px 8px; + border: 1px solid #c8ccd2; + border-radius: 4px; + background: #fff; + cursor: pointer; } .toolbar button:hover, .toolbar .file-btn:hover { background: #f3f5f8; } .toolbar .file-btn { font-weight: 600; } .toolbar .file-btn[hidden] { display: none; } .tooltip { position: absolute; pointer-events: none; z-index: 2; - background: rgba(31, 35, 40, 0.92); color: #fff; padding: 6px 8px; border-radius: 5px; - font-size: 12px; max-width: 280px; white-space: nowrap; + background: rgba(31, 35, 40, 0.92); + color: #fff; + padding: 6px 8px; + border-radius: 5px; + font-size: 12px; + max-width: 280px; + white-space: nowrap; } .tooltip b { font-size: 13px; } .tooltip .dim { color: #c6cbd1; } .status { - position: absolute; left: 50%; bottom: 10px; transform: translateX(-50%); - background: rgba(31, 35, 40, 0.85); color: #fff; padding: 6px 12px; border-radius: 6px; font-size: 12px; - max-width: 90%; white-space: pre-wrap; + position: absolute; left: 50%; bottom: 10px; + transform: translateX(-50%); + background: rgba(31, 35, 40, 0.85); + color: #fff; + padding: 6px 12px; + border-radius: 6px; + font-size: 12px; + max-width: 90%; + white-space: pre-wrap; } .status.error { background: rgba(176, 32, 32, 0.92); } .empty { @@ -156,19 +203,19 @@ const TEMPLATE = /* html */ `
- - ${t("legend.open_idf")} + - - - - + + + +
-
Déposez un fichier .idf ici
ou utilisez « Ouvrir un IDF… »
+
${t("legend.drop_idf")}
${t("legend.or_use")} « ${t("legend.open_idf")} »
`; @@ -408,7 +455,13 @@ export class IdfViewer extends HTMLElement { /** Accès aux objets three.js pour les usages avancés. */ get three() { - return { scene: this._scene, camera: this._camera, renderer: this._renderer, controls: this._controls, group: this._group }; + return { + scene: this._scene, + camera: this._camera, + renderer: this._renderer, + controls: this._controls, + group: this._group + }; } // ----- three.js ---------------------------------------------------------- @@ -697,39 +750,45 @@ export class IdfViewer extends HTMLElement { let keys = []; if (this._colorMode === 'boundary') { keys = [ - ['Extérieur', BOUNDARY_COLORS.outdoors], - ['Sol', BOUNDARY_COLORS.ground], - ['Surface / zone', BOUNDARY_COLORS.surface], - ['Adiabatique', BOUNDARY_COLORS.adiabatic], - ['Autre', BOUNDARY_COLORS.other], + [t("legend.boundaries.outdoors"), BOUNDARY_COLORS.outdoors], + [t("legend.boundaries.ground"), BOUNDARY_COLORS.ground], + [t("legend.boundaries.surface"), BOUNDARY_COLORS.surface], + [t("legend.boundaries.adiabatic"), BOUNDARY_COLORS.adiabatic], + [t("legend.boundaries.other"), BOUNDARY_COLORS.other] ]; } if (this._colorMode === 'type') { keys = [ - [t("legend.type.wall"), TYPE_COLORS.wall], - [t("legend.type.wallInterior"), TYPE_COLORS.wallInterior], - [t("legend.type.wallGround"), TYPE_COLORS.wallGround], - [t("legend.type.roof"), TYPE_COLORS.roof], - [t("legend.type.ceiling"), TYPE_COLORS.ceiling], - [t("legend.type.floor"), TYPE_COLORS.floor], - [t("legend.type.floorGround"), TYPE_COLORS.floorGround] + [t("legend.types.wall"), TYPE_COLORS.wall], + [t("legend.types.wallInterior"), TYPE_COLORS.wallInterior], + [t("legend.types.wallGround"), TYPE_COLORS.wallGround], + [t("legend.types.roof"), TYPE_COLORS.roof], + [t("legend.types.ceiling"), TYPE_COLORS.ceiling], + [t("legend.types.floor"), TYPE_COLORS.floor], + [t("legend.types.floorGround"), TYPE_COLORS.floorGround] ]; } if (this._model.fenestrations.length) { keys.push( - [t("legend.type.window"), TYPE_COLORS.window], - [t("legend.type.glassdoor"), TYPE_COLORS.glassdoor], - [t("legend.type.door"), TYPE_COLORS.door], + [t("legend.types.window"), TYPE_COLORS.window], + [t("legend.types.glassdoor"), TYPE_COLORS.glassdoor], + [t("legend.types.door"), TYPE_COLORS.door], ); } if (this._model.shading.length) { - keys.push([t("legend.type.shading"), TYPE_COLORS.shading]); + keys.push([t("legend.types.shading"), TYPE_COLORS.shading]); } - return { colorMode: this._colorMode, zones, keys: keys.map(([label, color]) => ({ label, color: hex(color) })) }; + return { + colorMode: this._colorMode, + zones, + keys: keys.map(([label, color]) => ({ label, color: hex(color) })) }; } _emitLegendData() { - this.dispatchEvent(new CustomEvent('legend-data-changed', { detail: { data: this.getLegendData() }, bubbles: true, composed: true })); + this.dispatchEvent(new CustomEvent( + 'legend-data-changed', + { detail: { data: this.getLegendData() }, bubbles: true, composed: true } + )); } _setStatus(message, isError = false) { @@ -745,7 +804,13 @@ export class IdfViewer extends HTMLElement { } function escapeHTML(str) { - return String(str).replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]); + return String(str).replace(/[&<>"']/g, (c) => ({ + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + })[c]); } if (!customElements.get('idf-viewer')) customElements.define('idf-viewer', IdfViewer); diff --git a/lib/modules/components/obm-navigation.js b/lib/modules/components/obm-navigation.js index d2e6604..0361797 100644 --- a/lib/modules/components/obm-navigation.js +++ b/lib/modules/components/obm-navigation.js @@ -34,14 +34,24 @@ class ObmSiteNav extends HTMLElement { @media (max-width: 800px) { nav { flex-wrap: wrap; padding-bottom: 0; } .toggle { display: block; } - .links { display: none; order: 3; flex: 0 0 100%; align-items: flex-start; flex-direction: column; gap: 0; padding: 0 0 8px 56px; } + .links { + display: none; + order: 3; + flex: 0 0 100%; + align-items: flex-start; + flex-direction: column; + gap: 0; + padding: 0 0 8px 56px; + } .links.open { display: flex; } .brand { display: none; } .language { margin-left: 0; } }