PageSourceSearch

https://www.anagram.world/assets/TranslationsAdmin-CFJs7uLG.js

js anagram.world collected 2026-10-09 21:27:20 UTC 10,884 bytes, 1 lines download raw bytes

1import{r as s,j as e}from"./react-DlBquRhq.js";import{s as E}from"./index-CVeKy3HG.js";import{useToast as z}from"./AdminLayout-BBBM0EpE.js";import{t as f,c as O}from"./theme-DpdrwuOw.js";import{B as G,I as W,T as te}from"./Select-DEo2NNmK.js";import{C as se}from"./Card-BLbAeVwm.js";import{E as M}from"./EmptyState-DtB-HTut.js";import{P as U}from"./PageHeader-CJBBT1Jp.js";import{S as H}from"./Section-BqUs22VL.js";import{L as I}from"./Spinner-DamV4FPX.js";import"./supabase-CpRTRdvQ.js";import"./animation-B0WpSdC-.js";const P=200,V={hotels:"name",rooms:"name",amenities:"name",bar_sections:"name",drinks:"name",penthouse_events:"name",faq_topics:"name",faq_questions:"question",blog_articles:"title",about_pages:"title",shop_products:"title",seo_meta:"route",room_types:"name",services:"name",room_images:"alt",shop_product_images:"alt",blog_sections:"id",room_overlays:"id"};function ye(){const[r,d]=s.useState("ui"),[m,i]=s.useState([]),[j,a]=s.useState(""),[T,h]=s.useState(!0);s.useEffect(()=>{(async()=>{const{data:t}=await E.from("locales").select("*").order("sort_order"),x=t??[];i(x),a(x.find(N=>!N.is_default)?.code??""),h(!1)})()},[]);const g=m.find(t=>t.is_default)?.code??"en";return T?e.jsx(I,{}):m.length?e.jsxs("div",{children:[e.jsx(U,{title:"Translations",description:"Every word guests see, in every language. English is the source; a missing translation falls back to it, so the site is never blank."}),e.jsxs("div",{style:{display:"flex",gap:12,alignItems:"center",flexWrap:"wrap",marginBottom:20},children:[e.jsx("div",{style:{display:"flex",gap:4},children:["ui","content"].map(t=>e.jsx(G,{variant:r===t?"primary":"ghost",onClick:()=>d(t),children:t==="ui"?"Interface strings":"Content"},t))}),e.jsxs("label",{style:{marginLeft:"auto",display:"flex",alignItems:"center",gap:8,...f.bodySm},children:["Translating into",e.jsx("select",{value:j,onChange:t=>a(t.target.value),style:{padding:"6px 10px",borderRadius:6,border:`1px solid ${O.border10}`},children:m.filter(t=>!t.is_default).map(t=>e.jsxs("option",{value:t.code,children:[t.label," (",t.code,")"]},t.code))})]})]}),j?r==="ui"?e.jsx(ne,{target:j,defaultLocale:g}):e.jsx(ae,{target:j,defaultLocale:g}):e.jsx(M,{title:"Only the default language exists",description:"Add a row to the locales table — for example German — and it appears here. No deploy needed."})]}):e.jsxs("div",{children:[e.jsx(U,{title:"Translations"}),e.jsx(M,{title:"No languages configured",description:"Apply migration 035_i18n.sql, then add a language in the locales table."})]})}function ne({target:r,defaultLocale:d}){const{showToast:m}=z(),[i,j]=s.useState([]),[a,T]=s.useState({}),[h,g]=s.useState(!0),[t,x]=s.useState(""),[N,L]=s.useState(!0),[_,q]=s.useState(null),p=s.useCallback(async()=>{g(!0);const[{data:n},{data:b}]=await Promise.all([E.from("ui_string_keys").select("*").order("key"),E.from("ui_strings").select("key, locale, value").in("locale",[d,r])]),w={};for(const u of b??[])(w[u.key]??={})[u.locale]=u.value;j(n??[]),T(w),g(!1)},[r,d]);s.useEffect(()=>{p()},[p]);async function S(n,b){q(n);const w=b.trim(),{error:u}=w?await E.from("ui_strings").upsert({key:n,locale:r,value:w},{onConflict:"key,locale"}):await E.from("ui_strings").delete().eq("key",n).eq("locale",r);if(q(null),u){m(u.message,"error");return}T(A=>({...A,[n]:{...A[n],[r]:w}}))}const $=s.useMemo(()=>{const n=t.trim().toLowerCase();return i.filter(b=>{const w=a[b.key]?.[d]??"",u=a[b.key]?.[r]??"";return N&&u?!1:n?b.key.toLowerCase().includes(n)||w.toLowerCase().includes(n):!0})},[i,a,t,N,r,d]),R=i.filter(n=>a[n.key]?.[r]).length;return h?e.jsx(I,{}):i.length?e.jsxs(H,{children:[e.jsx(Z,{done:R,total:i.length}),e.jsxs("div",{style:{display:"flex",gap:12,flexWrap:"wrap",margin:"16px 0"},children:[e.jsx(W,{placeholder:"Search keys or English text…",value:t,onChange:n=>x(n.target.value),style:{flex:"1 1 260px"}}),e.jsxs("label",{style:{display:"flex",alignItems:"center",gap:8,...f.bodySm},children:[e.jsx("input",{type:"checkbox",checked:N,onChange:n=>L(n.target.checked)}),"Only untranslated"]})]}),$.length?e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:10},children:$.map(n=>e.jsx(Q,{label:n.key,context:n.context,maxLength:n.max_length,source:a[n.key]?.[d]??"",value:a[n.key]?.[r]??"",busy:_===n.key,onSave:b=>S(n.key,b)},n.key))}):e.jsx(M,{title:"Nothing left",description:"Every key matching this filter has been translated."})]}):e.jsx(M,{title:"No interface keys yet",description:"Keys are created by the code that uses them, then seeded into ui_string_keys. Run the seed script after the string extraction."})}function ae({target:r,defaultLocale:d}){const{showToast:m}=z(),[i,j]=s.useState([]),[a,T]=s.useState(""),[h,g]=s.useState([]),[t,x]=s.useState({}),[N,L]=s.useState(!0),[_,q]=s.useState(!1),[p,S]=s.useState(null),[$,R]=s.useState(""),[n,b]=s.useState(!0);s.useEffect(()=>{(async()=>{const{data:o}=await E.from("translatable_fields").select("*").order("table_name").order("sort_order"),y=o??[];j(y),T(y[0]?.table_name??""),L(!1)})()},[]);const w=s.useMemo(()=>[...new Set(i.map(o=>o.table_name))],[i]),u=s.useMemo(()=>i.filter(o=>o.table_name===a),[i,a]),A=s.useCallback(async()=>
1{if(!a||!u.length)return;g([]),x({}),q(!0);const o=V[a]??"id",y=[...new Set(["id",o,...u.map(c=>c.column_name)])].join(","),v=u.find(c=>c.list_source)?.list_source??a,[{data:l},{data:k}]=await Promise.all([E.from(v).select(y).limit(P),E.from("content_translations").select("row_id, column_name, value").eq("table_name",a).eq("locale",r)]),C={};for(const c of k??[])C[`${c.row_id}|${c.column_name}`]=c.value;g(l??[]),x(C),q(!1)},[a,u,r]);s.useEffect(()=>{A()},[A]);async function X(o,y,v){const l=`${o}|${y}`;S(l);const k=v.trim(),{error:C}=k?await E.from("content_translations").upsert({table_name:a,row_id:o,column_name:y,locale:r,value:k},{onConflict:"table_name,row_id,column_name,locale"}):await E.from("content_translations").delete().eq("table_name",a).eq("row_id",o).eq("column_name",y).eq("locale",r);if(S(null),C){m(C.message,"error");return}x(c=>({...c,[l]:k}))}const J=s.useMemo(()=>{const o=$.trim().toLowerCase(),y=V[a]??"id";return h.map(v=>{const l=String(v.id),k=String(v[y]??l),C=u.flatMap(c=>{const F=v[c.column_name],B=c.kind==="text"?String(F??""):F==null?"":JSON.stringify(F,null,2);if(!B||B==="[]"||B==="{}")return[];const K=`${l}|${c.column_name}`;return n&&t[K]?[]:o&&![k,B,c.label].some(ee=>ee.toLowerCase().includes(o))?[]:[{field:c,source:B,cacheKey:K}]});return{id:l,title:k,entries:C}}).filter(v=>v.entries.length>0)},[h,u,a,t,$,n]);if(N)return e.jsx(I,{});const D=h.length*u.length,Y=Object.values(t).filter(Boolean).length;return e.jsxs(H,{children:[e.jsx("div",{style:{display:"flex",gap:8,flexWrap:"wrap",marginBottom:16},children:w.map(o=>e.jsx(G,{variant:a===o?"primary":"ghost",onClick:()=>T(o),children:o.replace(/_/g," ")},o))}),D>0&&e.jsx(Z,{done:Y,total:D}),e.jsxs("div",{style:{display:"flex",gap:12,flexWrap:"wrap",margin:"16px 0"},children:[e.jsx(W,{placeholder:"Search titles, fields or English text…",value:$,onChange:o=>R(o.target.value),style:{flex:"1 1 260px"}}),e.jsxs("label",{style:{display:"flex",alignItems:"center",gap:8,...f.bodySm},children:[e.jsx("input",{type:"checkbox",checked:n,onChange:o=>b(o.target.checked)}),"Only untranslated"]})]}),h.length>0&&e.jsxs("div",{style:{...f.bodySm,color:O.text55},children:[J.length," of ",h.length," ",a.replace(/_/g," ")," rows shown",h.length>=P&&` · list capped at the first ${P} rows, later rows are not on this screen`]}),_?e.jsx(I,{}):h.length?J.length?e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:22,marginTop:16},children:J.map(({id:o,title:y,entries:v})=>e.jsxs(se,{children:[e.jsx("div",{style:{...f.h3,marginBottom:10},children:y}),e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:10},children:v.map(({field:l,source:k,cacheKey:C})=>e.jsx(Q,{label:l.kind==="text"?l.label:`${l.label} · ${l.kind}`,longform:l.is_longform||l.kind!=="text",kind:l.kind,source:k,value:t[C]??"",busy:p===C,onSave:c=>X(o,l.column_name,c)},l.column_name))})]},o))}):e.jsx(M,{title:"Nothing left",description:"Every field matching this filter has been translated."}):e.jsx(M,{title:"Nothing to translate here",description:`No rows in ${a}.`}),e.jsxs("p",{style:{...f.bodySm,color:O.text55,marginTop:20},children:["List and block fields (room features, amenity bullets and sections, article bodies) are edited as JSON — keep the same shape as the English or the site ignores the value. Source language: ",d,"."]})]})}function Z({done:r,total:d}){const m=d?Math.round(r/d*100):0;return e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:12},children:[e.jsx("div",{style:{flex:1,height:6,background:O.border10,borderRadius:3,overflow:"hidden"},children:e.jsx("div",{style:{width:`${m}%`,height:"100%",background:O.accent}})}),e.jsxs("span",{style:{...f.bodySm,color:O.text55,fontVariantNumeric:"tabular-nums"},children:[r," / ",d," translated (",m,"%)"]})]})}function Q({label:r,context:d,source:m,value:i,onSave:j,busy:a,longform:T,maxLength:h,kind:g="text"}){const[t,x]=s.useState(i);s.useEffect(()=>{x(i)},[i]);const N=T||m.length>90?te:W,L=h!=null&&t.length>h,_=(()=>{if(g==="text"||!t.trim())return null;let p;try{p=JSON.parse(t)}catch{return"Not valid JSON — this will be ignored on the site."}if(g==="text[]"){if(!Array.isArray(p))return'Must be a JSON array, e.g. ["Erstens", "Zweitens"].';if(!p.every(S=>typeof S=="string"))return"Every item must be a string.";try{const S=JSON.parse(m);
1if(Array.isArray(S)&&S.length!==p.length)return`English has ${S.length} items, this has ${p.length} — they must match.`}catch{}}return g==="jsonb"&&Array.isArray(p)!==Array.isArray(JSON.parse(m||"null"))?"Shape must match the English (array vs object) or it will be ignored.":null})(),q=!!_;return e.jsxs("div",{style:{display:"grid",gridTemplateColumns:"minmax(0,1fr) minmax(0,1fr)",gap:12,alignItems:"start"},children:[e.jsxs("div",{children:[e.jsx("div",{style:{...f.bodySm,color:O.text55,marginBottom:4,fontFamily:"ui-monospace, monospace"},children:r}),e.jsx("div",{style:{...f.body,whiteSpace:"pre-wrap"},children:m}),d&&e.jsx("div",{style:{...f.bodySm,color:O.text55,marginTop:4},children:d})]}),e.jsxs("div",{children:[e.jsx(N,{value:t,onChange:p=>x(p.target.value),onBlur:()=>{t!==i&&!q&&j(t)},placeholder:"Not translated — falls back to English",disabled:a,style:_?{borderColor:"#b42318"}:L?{borderColor:"#b45309"}:g!=="text"?{fontFamily:"ui-monospace, monospace",fontSize:12}:void 0}),_&&e.jsx("div",{style:{...f.bodySm,color:"#b42318",marginTop:4},children:_}),!_&&L&&e.jsxs("div",{style:{...f.bodySm,color:"#b45309",marginTop:4},children:[t.length,"/",h," characters — may not fit the layout."]}),!_&&g==="text[]"&&e.jsx("div",{style:{...f.bodySm,color:O.text55,marginTop:4},children:"JSON array — keep the same number of items as the English."})]})]})}export{ye as default};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.