Files
houseplan-card/dist/houseplan-assets/backdrop-pick-DIYZEnDX.js
T
Codex 89fa8ca588 feat: French localization — community contribution by @OUARZA (#371)
The complete French dictionary contributed in #371 (1026 keys, zero empty
values, zero placeholder mismatches) lands as the second lazy locale on the
fr.ts + one static entry. The contributor's snapshot predated this week's
keys, so the 121 additions (device inbox #29, backdrop guard #39, junction
limits #331, lazy-editor toasts #353/#354, furniture #159) are translated
in this commit and flagged in the issue for the author's review; 21 stale
pre-#62 keys are dropped; key order follows en.json. The HA integration
translations file ships as contributed (full parity).

Wiring: loadFrench + __HOUSEPLAN_FR_RETRY_ASSET__ with the same 1/1
replacement guarantee as German; locale roles and localeRoots generalise to
(de|fr); the stale-entry fallback panel (#353) gains its French branch —
the r1 reviewer caught that this second hardcoded language list would have
silently degraded French to English on a cached entry. French profiles
(fr, fr-FR, fr-CA, fr-BE, fr-CH) select automatically.

Proofs: the registry-driven parity suite covers fr by construction
(1128/1128 keys); French analogues of both German-personal tests (product
glossary + non-translation scan with a reviewed equal-to-English
allow-list of 22 legitimate homographs); smoke_french_locale — fr-CA
profile commits French from the real bundle, one lazy chunk per page,
initial graph free of fr; smoke_entry_stale gains the French run; a
registry mutant drops the fr entry and is killed by the smoke. Initial
view: +0.5 KB (registry entry + fallback branch); the 23 KB dictionary is
lazy.

Issue: #371
User-Visible: yes
2026-08-29 13:39:21 +03:00

17 lines
6.8 KiB
JavaScript

globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="0f7edde68a64b82e24b51acbb2ba3050dbe2684c43fd3cb3428ee481b9de3d0f";import{b as e}from"./houseplan-card-DVfaouS8.js";function n(e,n){return{id:e,title:n,plan_url:null,view_box:[0,0,1,1],rooms:[],wall_segments:[]}}function t(e="file"){const n="draw"===e;return{source:e,showBorders:n,showNames:n,displayTouched:!1}}function r(e,n){if(e.displayTouched)return{...e,source:n};const t="draw"===n;return{...e,source:n,showBorders:t,showNames:t}}function a(e,n,t){return{...e,[n]:t,displayTouched:!0}}function o(e,n,t){const r=(e?.spaces||[]).find(e=>e?.id===t),a=new Set((r?.rooms||[]).map(e=>String(e?.id||"")).filter(Boolean)),o=[...new Set((e?.markers||[]).filter(e=>!0!==e?.removed&&"string"==typeof e?.id).filter(e=>e.space===t||"string"==typeof e.room_id&&a.has(e.room_id)||n?.[e.id]?.s===t).map(e=>e.id))].sort((e,n)=>e.localeCompare(n));return{markerIds:o,count:o.length}}const i=1e6,l=()=>({kind:"unknown",width:null,height:null,alpha:!1,decodedBytes:null}),d=(e,n,t)=>{if(!Number.isInteger(e)||!Number.isInteger(n)||e<=0||n<=0||e>i||n>i)return{kind:"unknown",width:null,height:null,alpha:!1,decodedBytes:null};const r=e*n*4;return{kind:Math.max(e,n)>16384?"hard":r>134217728?"warn":"safe",width:e,height:n,alpha:t,decodedBytes:r}},s=(e,n)=>(e[n]<<24|e[n+1]<<16|e[n+2]<<8|e[n+3])>>>0,u=(e,n)=>e[n]<<8|e[n+1],c=(e,n)=>e[n]|e[n+1]<<8|e[n+2]<<16,h=(e,n)=>e[n]|e[n+1]<<8,f=(e,n,t)=>{for(let r=0;r<t.length;r++)if(e[n+r]!==t.charCodeAt(r))return!1;return!0};function g(e,n){try{return"png"===n?function(e){if(e.length<33||2303741511!==s(e,0)||218765834!==s(e,4))return{kind:"unknown",width:null,height:null,alpha:!1,decodedBytes:null};if(!f(e,12,"IHDR"))return{kind:"unknown",width:null,height:null,alpha:!1,decodedBytes:null};const n=s(e,16),t=s(e,20),r=e[25];let a=4===r||6===r;if(!a){let n=33;for(let t=0;t<64&&n+8<=e.length;t++){const t=s(e,n);if(f(e,n+4,"tRNS")){a=!0;break}if(f(e,n+4,"IDAT")||f(e,n+4,"IEND"))break;if(t>e.length)break;n+=12+t}}return d(n,t,a)}(e):"jpg"===n?function(e){if(e.length<4||255!==e[0]||216!==e[1])return{kind:"unknown",width:null,height:null,alpha:!1,decodedBytes:null};let n=2;for(let t=0;t<256&&n+4<=e.length;t++){if(255!==e[n]){n++;continue}const t=e[n+1];if(216===t||t>=208&&t<=217){n+=2;continue}if(t>=192&&t<=207&&196!==t&&200!==t&&204!==t)return n+9>e.length?l():d(u(e,n+7),u(e,n+5),!1);if(218===t)return l();const r=u(e,n+2);if(r<2)return l();n+=2+r}return{kind:"unknown",width:null,height:null,alpha:!1,decodedBytes:null}}(e):"webp"===n?function(e){if(e.length<30||!f(e,0,"RIFF")||!f(e,8,"WEBP"))return{kind:"unknown",width:null,height:null,alpha:!1,decodedBytes:null};if(f(e,12,"VP8X")){const n=!!(16&e[20]);return d(c(e,24)+1,c(e,27)+1,n)}if(f(e,12,"VP8 "))return 157!==e[23]||1!==e[24]||42!==e[25]?{kind:"unknown",width:null,height:null,alpha:!1,decodedBytes:null}:d(16383&h(e,26),16383&h(e,28),!1);if(f(e,12,"VP8L")){if(47!==e[20])return{kind:"unknown",width:null,height:null,alpha:!1,decodedBytes:null};const n=e[21]|e[22]<<8|e[23]<<16|e[24]<<24;return d(1+(16383&n),1+(n>>14&16383),1==(n>>28&1))}return{kind:"unknown",width:null,height:null,alpha:!1,decodedBytes:null}}(e):{kind:"unknown",width:null,height:null,alpha:!1,decodedBytes:null}}catch{return{kind:"unknown",width:null,height:null,alpha:!1,decodedBytes:null}}}const w={"image/svg+xml":"svg","image/png":"png","image/jpeg":"jpg","image/webp":"webp"};function p(e){return new Promise((n,t)=>{const r=new FileReader;r.onerror=()=>t(r.error||new Error("read failed")),r.onload=()=>{const e=String(r.result||""),a=e.indexOf(",");a<0?t(new Error("unexpected data url")):n(e.slice(a+1))},r.readAsDataURL(e)})}async function b(e,n=1.414){const t=URL.createObjectURL(e);try{return await new Promise(e=>{const r=new Image;r.onload=()=>e(r.naturalWidth&&r.naturalHeight?r.naturalWidth/r.naturalHeight:n),r.onerror=()=>e(n),r.src=t})}finally{URL.revokeObjectURL(t)}}async function k(e,n,t){const[r,a]=await Promise.all([p(e),b(e)]);return{ext:n,b64:r,aspect:a,name:t}}async function m(e){const n=function(e){return w[e.type]||(e.name.toLowerCase().endsWith(".svg")?"svg":"")}(e);if(!n)return{kind:"reject"};if("svg"===n)return{kind:"pass",ext:n};const t=g(new Uint8Array(await e.arrayBuffer()),n);return"safe"===t.kind?{kind:"pass",ext:n}:{kind:"guard",state:{file:e,ext:n,probe:t,busy:!1}}}async function y(e){const n=createImageBitmap(e.file,{imageOrientation:"from-image"}),t=await Promise.race([n,new Promise((e,n)=>{setTimeout(()=>n(new Error("decode timeout")),globalThis.__HP_BACKDROP_TIMEOUT_MS??1e4)})]);try{const{width:n,height:r}=function(e,n,t=4096){const r=Math.min(1,t/Math.max(e,n));return{width:Math.max(1,Math.round(e*r)),height:Math.max(1,Math.round(n*r))}}(t.width,t.height,4096),a=e.probe.alpha,o=a?"image/png":"image/jpeg";let i;if("function"==typeof OffscreenCanvas){const e=new OffscreenCanvas(n,r),a=e.getContext("2d");if(!a)throw new Error("no 2d context");a.drawImage(t,0,0,n,r),i=await e.convertToBlob({type:o,quality:.9})}else{const e=document.createElement("canvas");e.width=n,e.height=r;const a=e.getContext("2d");if(!a)throw new Error("no 2d context");a.drawImage(t,0,0,n,r),i=await new Promise((n,t)=>{e.toBlob(e=>e?n(e):t(new Error("encode failed")),o,.9)})}const l=a?"png":"jpg";return{blob:i,ext:l,name:`${e.file.name.replace(/\.[^.]+$/,"")||"plan"}-reduced.${l}`}}finally{t.close()}}const _=e=>(e/1048576).toFixed(e>=10485760?0:1);function B(n,t,r,a){const o=n._backdropGuard;if(!o)return null;const{probe:i}=o,l="hard"===i.kind,d=l?n._t("backdrop.too_large_body",{w:i.width??0,h:i.height??0,limit:16384}):"unknown"===i.kind?n._t("backdrop.unknown_body"):n._t("backdrop.large_body",{w:i.width??0,h:i.height??0,fileMb:_(o.file.size),decodedMb:_(i.decodedBytes??0)}),s=()=>{n._backdropGuard?.busy||r()},u=()=>n._backdropGuard?.file===o.file;return e`<hp-dialog .hass=${a}
.title=${n._t(l?"backdrop.too_large_title":"backdrop.large_title")}
icon="mdi:image-size-select-large" dismiss-on-scrim @hp-close=${()=>s()}>
<div class="body"><p>${d}</p></div>
<div class="row" slot="footer">
<button class="btn ghost" ?disabled=${o.busy} @click=${()=>s()}>
${n._t("btn.cancel")}</button>
<span class="spacer"></span>
${l?null:e`
<button class="btn ghost" ?disabled=${o.busy} @click=${()=>(async()=>{if(n._backdropGuard?.busy)return;const e=await k(o.file,o.ext,o.file.name);u()&&(t(e),r())})()}>
${n._t("backdrop.keep_original")}</button>
<button class="btn on" ?disabled=${o.busy} @click=${()=>(async()=>{if(!n._backdropGuard?.busy){n._backdropGuard={...o,busy:!0},n.requestUpdate?.();try{const e=await y(o),n=await k(e.blob,e.ext,e.name);if(!u())return;t(n),r()}catch{if(!u())return;r(),n._showToast(n._t("backdrop.downscale_failed"))}}})()}>
${o.busy?n._t("backdrop.reducing"):n._t("backdrop.use_downscaled")}
</button>`}
</div>
</hp-dialog>`}export{n as a,o as b,m as c,k as e,t as i,B as r,r as s,a as t};