@ollang-dev/sdk 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # @ollang-dev/sdk
2
+
3
+ Official TypeScript/Node.js SDK for the Ollang API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @ollang-dev/sdk
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import Ollang from '@ollang-dev/sdk';
15
+
16
+ const ollang = new Ollang({
17
+ apiKey: 'your-api-key',
18
+ });
19
+
20
+ // Create a project
21
+ const project = await ollang.projects.create({ name: 'My Project' });
22
+
23
+ // Upload a file
24
+ const upload = await ollang.uploads.upload(project.id, './video.mp4');
25
+
26
+ // Create an order
27
+ const order = await ollang.orders.create({
28
+ projectId: project.id,
29
+ sourceLanguage: 'en',
30
+ targetLanguages: ['fr', 'de', 'es'],
31
+ });
32
+
33
+ // Check order status
34
+ const status = await ollang.orders.get(order.id);
35
+ ```
36
+
37
+ ## Ollang Translation System
38
+
39
+ Launch the built-in Ollang dashboard to scan and manage translatable content in your project:
40
+
41
+ ```bash
42
+ npx @ollang-dev/sdk start
43
+ ```
44
+
45
+ ## Resources
46
+
47
+ | Resource | Description |
48
+ | --------------------------- | -------------------------------------- |
49
+ | `ollang.projects` | Create and manage projects |
50
+ | `ollang.uploads` | Upload files (video, audio, documents) |
51
+ | `ollang.orders` | Create and track translation orders |
52
+ | `ollang.revisions` | Request revisions on completed orders |
53
+ | `ollang.customInstructions` | Set custom translation instructions |
54
+ | `ollang.scans` | Scan content for translatable text |
55
+ | `ollang.cms` | CMS integration |
56
+
57
+ ## Documentation
58
+
59
+ For comprehensive API documentation, guides, and examples visit:
60
+
61
+ **[https://api-docs.ollang.com](https://api-docs.ollang.com/)**
62
+
63
+ ## License
64
+
65
+ MIT
@@ -1,16 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Translation Management System CLI
4
+ * Ollang Translation System CLI
5
5
  *
6
- * Usage: npx @ollang/sdk tms
6
+ * Usage: npx @ollang-dev/sdk start
7
7
  */
8
8
 
9
9
  const { spawn } = require('child_process');
10
10
  const path = require('path');
11
11
  const fs = require('fs');
12
12
 
13
- console.log('🚀 Translation Management System başlatılıyor...\n');
13
+ console.log('🚀 Ollang Translation System starting...\n');
14
14
 
15
15
  const serverPath = path.join(__dirname, '..', 'dist', 'tms', 'server', 'index.js');
16
16
 
@@ -29,19 +29,19 @@ const server = spawn('node', [serverPath], {
29
29
  });
30
30
 
31
31
  process.on('SIGINT', () => {
32
- console.log('\n\n👋 TMS kapatılıyor...');
32
+ console.log('\n\n👋 Ollang shutting down...');
33
33
  server.kill('SIGINT');
34
34
  process.exit(0);
35
35
  });
36
36
 
37
37
  server.on('error', (error) => {
38
- console.error('❌ Server başlatılamadı:', error.message);
38
+ console.error('❌ Failed to start server:', error.message);
39
39
  process.exit(1);
40
40
  });
41
41
 
42
42
  server.on('exit', (code) => {
43
43
  if (code !== 0 && code !== null) {
44
- console.error(`❌ Server hata ile kapandı (kod: ${code})`);
44
+ console.error(`❌ Server closed with error (code: ${code})`);
45
45
  process.exit(code);
46
46
  }
47
47
  });
@@ -130,7 +130,7 @@ export declare class OllangBrowser {
130
130
  private updateFolderOptions;
131
131
  private showNewFolderDialog;
132
132
  private fetchStrapiSchemaInPanel;
133
- private pushToTMS;
133
+ private pushToOllang;
134
134
  }
135
135
  declare global {
136
136
  interface Window {
@@ -1201,7 +1201,7 @@ class OllangBrowser {
1201
1201
  </div>
1202
1202
  <div id="ollang-strapi-schema-block" style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #e2e8f0;">
1203
1203
  <span style="font-size: 11px; font-weight: 500; color: #64748b;">Strapi schema (optional)</span>
1204
- <p style="margin: 4px 0 8px 0; font-size: 11px; color: #94a3b8;">Fetch schema here so Push uses Content-Type Builder fields. Use your Strapi API token (not the Ollang TMS API token).</p>
1204
+ <p style="margin: 4px 0 8px 0; font-size: 11px; color: #94a3b8;">Fetch schema here so Push uses Content-Type Builder fields. Use your Strapi API token (not the Ollang API token).</p>
1205
1205
  <div style="display: flex; flex-direction: column; gap: 6px;">
1206
1206
  <input type="text" id="ollang-strapi-url" placeholder="Strapi URL (e.g. https://api.example.com)" style="width: 100%; padding: 6px 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; box-sizing: border-box;" />
1207
1207
  <input type="password" id="ollang-strapi-jwt" placeholder="Strapi Admin JWT token" style="width: 100%; padding: 6px 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; box-sizing: border-box;" />
@@ -1263,7 +1263,9 @@ class OllangBrowser {
1263
1263
  this.getCmsContent().forEach((c) => this.selectedContentIds.add(c.id));
1264
1264
  this.showContent(contentList);
1265
1265
  });
1266
- document.getElementById('ollang-push-tms')?.addEventListener('click', () => this.pushToTMS());
1266
+ document
1267
+ .getElementById('ollang-push-tms')
1268
+ ?.addEventListener('click', () => this.pushToOllang());
1267
1269
  document
1268
1270
  .getElementById('ollang-fetch-schema')
1269
1271
  ?.addEventListener('click', () => this.fetchStrapiSchemaInPanel());
@@ -1929,7 +1931,7 @@ class OllangBrowser {
1929
1931
  async fetchStrapiSchemaInPanel() {
1930
1932
  const baseUrl = (this.config.baseUrl || '').replace(/\/$/, '');
1931
1933
  if (!baseUrl) {
1932
- this.showStatus('Missing TMS baseUrl', 'error');
1934
+ this.showStatus('Missing Ollang baseUrl', 'error');
1933
1935
  return;
1934
1936
  }
1935
1937
  const urlInput = document.getElementById('ollang-strapi-url');
@@ -1967,7 +1969,7 @@ class OllangBrowser {
1967
1969
  btn.disabled = false;
1968
1970
  }
1969
1971
  }
1970
- async pushToTMS() {
1972
+ async pushToOllang() {
1971
1973
  if (this.selectedContentIds.size === 0) {
1972
1974
  this.showStatus('Please select at least one item', 'error');
1973
1975
  return;
@@ -1977,7 +1979,7 @@ class OllangBrowser {
1977
1979
  return;
1978
1980
  }
1979
1981
  if (!this.config.apiKey) {
1980
- this.showStatus('Please enter TMS API token first', 'error');
1982
+ this.showStatus('Please enter Ollang API token first', 'error');
1981
1983
  return;
1982
1984
  }
1983
1985
  const baseUrl = (this.config.baseUrl || '').replace(/\/$/, '');
@@ -1 +1 @@
1
- !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Ollang=e():t.Ollang=e()}(this,()=>(()=>{"use strict";var t={243(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.OllangBrowser=void 0;class n{constructor(t){if(this.observer=null,this.capturedContent=new Map,this.i18nTexts=new Set,this.i18nNormalized=new Set,this.excludedTexts=new Set,this.selectedContentIds=new Set,this.folders=[],this.selectedFolder="",this.strapiContentMap=new Map,this.strapiMediaMap=new Map,this.detectedStrapiUrls=new Set,this.strapiEntries=new Map,this.strapiLongTextMap=new Map,this.capturedTexts=new Map,this.apiKeyStorageKey="ollang_browser_api_key",this.config={baseUrl:"http://localhost:5972",autoDetectCMS:!0,captureSelectors:["h1","h2","h3","h4","h5","h6","p","span","div","a","button","li","td","th","label"],excludeSelectors:["script","style","noscript",".no-translate","#ollang-debug-panel",".ollang-debug-panel",'[id^="ollang-"]','[class*="ollang-"]'],captureAttributes:["data-cms-field","data-cms-id","data-field-id"],debounceMs:2e3,onContentDetected:()=>{},...t},!this.config.apiKey&&"undefined"!=typeof window){const t=this.getStoredApiKey();t&&(this.config.apiKey=t)}this.selectedFolder=t.selectedFolder||"",this.init()}getStoredApiKey(){try{if("undefined"==typeof window)return null;const t=window.localStorage.getItem(this.apiKeyStorageKey);return t&&t.trim()?t:null}catch{return null}}saveApiKey(t){try{if("undefined"==typeof window)return;window.localStorage.setItem(this.apiKeyStorageKey,t)}catch{}}init(){if("undefined"==typeof window)throw new Error("OllangBrowser can only be used in browser environment");this.interceptApiCalls(),this.loadI18nFiles().then(()=>{console.log(`✅ Loaded ${this.i18nTexts.size} i18n texts from files`),this.detectFrameworkI18n(),setTimeout(()=>{console.log(`🔍 Starting capture with ${this.i18nTexts.size} i18n texts and ${this.strapiContentMap.size} Strapi contents tracked`),this.startCapture()},3e3)})}interceptApiCalls(){this.interceptFetch(),this.interceptXHR()}interceptFetch(){const t=this,e=window.fetch.bind(window);window.fetch=function(n,i){const s="string"==typeof n?n:n?.url||String(n);return e(n,i).then(e=>(t.isStrapiApiUrl(s)&&e.clone().json().then(e=>{t.processStrapiResponse(s,e)}).catch(()=>{}),e))}}interceptXHR(){const t=this,e=XMLHttpRequest.prototype.open,n=XMLHttpRequest.prototype.send;XMLHttpRequest.prototype.open=function(t,n,i,s,o){return this._ollangUrl="string"==typeof n?n:String(n),e.call(this,t,n,i??!0,s??null,o??null)},XMLHttpRequest.prototype.send=function(e){return this.addEventListener("load",function(){if(t.isStrapiApiUrl(this._ollangUrl))try{const e=JSON.parse(this.responseText);t.processStrapiResponse(this._ollangUrl,e)}catch(t){}}),n.call(this,e)}}isStrapiApiUrl(t){return!!t&&(!(!this.config.strapiUrl||!t.includes(this.config.strapiUrl))||[/\/api\/([\w-]+)(\?|\/|$)/,/cms\./,/strapi/i].some(e=>e.test(t)))}extractContentTypeFromUrl(t){const e=t.match(/\/api\/([\w-]+)/);return e?e[1]:null}processStrapiResponse(t,e){const n=this.extractContentTypeFromUrl(t);if(!n)return;try{const e=new URL(t,window.location.origin),n=e.origin!==window.location.origin?e.origin:"";n&&this.detectedStrapiUrls.add(n)}catch(t){}const i=e?.data;if(!i)return;const s=Array.isArray(i)?i:[i];for(const e of s){if(!e||!e.attributes)continue;const i=e.id;this.strapiEntries.set(`${n}:${i}`,{contentType:n,entryId:i,attributes:e.attributes,url:t}),this.extractStrapiFields(e.attributes,n,i,"")}console.log(`📦 Strapi [${n}]: captured ${s.length} entries, total tracked: ${this.strapiContentMap.size}`)}extractStrapiFields(t,e,i,s,o=0){if(t&&"object"==typeof t&&!(o>n.MAX_RECURSION_DEPTH))for(const a of Object.keys(t)){const r=t[a],l=s?`${s}.${a}`:a;if(!n.SKIP_RELATION_KEYS.has(a))if("string"==typeof r&&r.trim().length>=2){if(this.isNonTranslatableField(a,r))continue;const t=this.normalizeText(r);if(t.length<2)continue;if(t.length>n.MAX_FIELD_LENGTH||n.LONG_TEXT_FIELDS.has(a)){const n=`${e}:${i}:${l}`;this.strapiLongTextMap.set(n,{contentType:e,entryId:i,field:l,rawText:r}),this.extractParagraphsFromHtml(r,e,i,l),this.config.debug&&console.log(`📝 Stored long field ${l} (${t.length} chars) for API capture`);continue}this.strapiContentMap.set(t,{contentType:e,entryId:i,field:l,rawText:r})}else if(Array.isArray(r))r.forEach((t,s)=>{if("object"==typeof t&&null!==t)this.extractStrapiFields(t,e,i,`${l}[${s}]`,o+1);else if("string"==typeof t&&t.trim().length>=2){const o=this.normalizeText(t);o.length>=2&&o.length<=n.MAX_FIELD_LENGTH&&this.strapiContentMap.set(o,{contentType:e,entryId:i,field:`${l}[${s}]`,rawText:t})}});else if("object"==typeof r&&null!==r){if("formats"===a||"provider_metadata"===a)continue;if(this.isStrapiMediaObject(r)){this.extractStrapiMedia(r,e,i,l);continue}if("data"===a)continue;this.extractStrapiFields(r,e,i,l,o+1)}}}isStrapiMediaObject(t){if(!t||"object"!=typeof t)return!1;const e=t.data;if(!e)return!1;const n=Array.isArray(e)?e[0]:e;return!(!n?.attributes?.url||!n?.attributes?.mime)}extractStrapiMedia(t,e,n,i){const s=this.config.strapiUrl||[...this.detectedStrapiUrls][0]||"",o=Array.isArray(t.data)?t.data:[t.data];for(const t of o){if(!t?.attributes?.url)continue;const o=t.attributes,a=o.url,r=o.mime||"",l=r.startsWith("image/")||/\.(jpg|jpeg|png|gif|svg|webp|avif)(\?|$)/i.test(a),d=r.startsWith("video/")||/\.(mp4|webm|ogg|mov)(\?|$)/i.test(a);if(!l&&!d)continue;const c=a.startsWith("http")?a:`${s}${a}`,p={contentType:e,entryId:n,field:i,url:c,mime:r,alt:o.alternativeText||o.caption||o.name||void 0};this.strapiMediaMap.set(c,p),a.startsWith("http")||this.strapiMediaMap.set(a,p)}}extractParagraphsFromHtml(t,e,i,s){const o=t.split(/<\/p>|<br\s*\/?>|<\/h[1-6]>|<\/li>|<\/div>/i).map(t=>this.normalizeText(t)).filter(t=>t.length>=10);for(const t of o)t.length>n.MAX_FIELD_LENGTH||this.strapiContentMap.has(t)||this.strapiContentMap.set(t,{contentType:e,entryId:i,field:s,rawText:t})}isNonTranslatableField(t,e){return!!(n.SKIP_KEYS.has(t)||/^https?:\/\//.test(e)||/^\d{4}-\d{2}-\d{2}T/.test(e)||/^[a-f0-9]{16,}$/.test(e)||/^\.(jpg|jpeg|png|gif|svg|webp)$/i.test(e)||/^#[0-9a-fA-F]{3,8}$/.test(e)||"fullName"===t||"firstName"===t||"lastName"===t||/^image\//.test(e)||/^video\//.test(e))}normalizeText(t){return t?t.replace(/<[^>]*>/g," ").replace(/&nbsp;/g," ").replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&quot;/g,'"').replace(/&#39;/g,"'").replace(/\s+/g," ").trim():""}findStrapiMatch(t){if(!t||0===this.strapiContentMap.size)return null;const e=this.normalizeText(t);if(e.length<3)return null;const n=this.strapiContentMap.get(e);if(n)return n;let i=null,s=0;for(const[t,n]of this.strapiContentMap){const o=e.length,a=t.length;if(!(a<10||o<10)){if(o>=a&&e.includes(t)){const t=a/o;t>s&&t>.6&&(s=t,i=n)}if(a>=o&&t.includes(e)){const t=o/a;t>s&&t>.5&&(s=t,i=n)}if(o>30&&a>30){const r=Math.min(o,a,200);if(e.substring(0,r)===t.substring(0,r)){const t=r/Math.max(o,a);t>s&&t>.5&&(s=t,i=n)}}}}return i&&s>.5?i:null}isI18nText(t){return!!this.i18nTexts.has(t)||!!this.i18nTexts.has(t.trim())||this.i18nNormalized.has(this.normalizeText(t))}detectFrameworkI18n(){if(setTimeout(()=>{try{const t=this.getAngularTranslations();t&&(this.extractTextsFromObject(t),console.log("✅ Loaded Angular translations from runtime"))}catch(t){console.warn("Could not auto-detect Angular translations:",t)}},1500),window.i18next){const t=window.i18next;if(t.store&&t.language){const e=t.store.data[t.language];e&&(this.extractTextsFromObject(e),console.log("✅ Loaded React i18next translations"))}}if(window.__VUE_I18N__){const t=window.__VUE_I18N__;t.messages&&(Object.values(t.messages).forEach(t=>{this.extractTextsFromObject(t)}),console.log("✅ Loaded Vue i18n translations"))}window.__NEXT_DATA__?.props?.pageProps?.messages&&(this.extractTextsFromObject(window.__NEXT_DATA__.props.pageProps.messages),console.log("✅ Loaded Next.js translations"))}getAngularTranslations(){if(window.ng&&window.ng.probe){const t=document.querySelector("app-root");if(t)try{const e=window.ng.probe(t);if(e?.injector){const t=e.injector.get("TranslateService");if(t?.translations)return t.translations}}catch(t){}}if(window.__ANGULAR_TRANSLATIONS__)return window.__ANGULAR_TRANSLATIONS__;try{const t=localStorage.getItem("translations")||sessionStorage.getItem("translations");if(t)return JSON.parse(t)}catch(t){}return null}async loadI18nFiles(){if(!this.config.i18nFiles||0===this.config.i18nFiles.length){const t=["/assets/i18n/","/locales/","/i18n/","/translations/","/messages/"],e=["en","tr","de","es","fr","it","pt","ru","ja","zh","ko","ar","kr","da","fi","nb","nl","sv"];for(const n of t)for(const t of e){const e=`${n}${t}.json`;try{const t=await fetch(e);if(t.ok){const n=await t.json();this.extractTextsFromObject(n),console.log(`✅ Auto-loaded i18n: ${e}`)}}catch(t){}}return}for(const t of this.config.i18nFiles)try{const e=await fetch(t),n=await e.json();this.extractTextsFromObject(n)}catch(e){console.warn(`Failed to load i18n file: ${t}`,e)}}extractTextsFromObject(t){for(const e in t){const n=t[e];"string"==typeof n?(this.i18nTexts.add(n),this.i18nTexts.add(n.trim()),this.i18nNormalized.add(this.normalizeText(n))):"object"==typeof n&&null!==n&&this.extractTextsFromObject(n)}}startCapture(){this.config.autoDetectCMS&&this.detectCMS(),this.startObserving(),this.scanPage()}detectCMS(){(window.__CONTENTFUL_SPACE_ID__||document.querySelector("[data-contentful-entry-id]"))&&(this.config.cmsType="contentful"),(window.__STRAPI__||document.querySelector("[data-strapi-field]"))&&(this.config.cmsType="strapi"),(window.__SANITY__||document.querySelector("[data-sanity]"))&&(this.config.cmsType="sanity"),(document.body.classList.contains("wordpress")||window.wp)&&(this.config.cmsType="wordpress"),!this.config.cmsType&&this.strapiContentMap.size>0&&(this.config.cmsType="strapi"),this.detectedStrapiUrls.size>0&&(this.config.cmsType="strapi",this.config.strapiUrl||(this.config.strapiUrl=[...this.detectedStrapiUrls][0]))}startObserving(){this.observer=new MutationObserver(t=>{this.handleMutations(t)}),this.observer.observe(document.body,{childList:!0,subtree:!0,characterData:!0,attributes:!0,attributeFilter:this.config.captureAttributes})}handleMutations(t){const e=new Set;t.forEach(t=>{"childList"===t.type?t.addedNodes.forEach(t=>e.add(t)):"characterData"!==t.type&&"attributes"!==t.type||e.add(t.target)}),e.forEach(t=>{if(t.nodeType===Node.ELEMENT_NODE){const e=t;this.scanElement(e),e.querySelectorAll&&this.config.captureSelectors.forEach(t=>{try{e.querySelectorAll(t).forEach(t=>this.scanElement(t))}catch(t){}})}})}captureApiContent(){for(const[,t]of this.strapiLongTextMap){const e=this.generateId(`api:${t.contentType}:${t.entryId}`,t.field);if(this.capturedContent.has(e))continue;const n=`${t.contentType}:${t.entryId}`,i=this.strapiEntries.get(n),s=i?.attributes?.route,o={id:e,text:t.rawText,type:"cms",selector:`api://${t.contentType}/${t.entryId}/${t.field}`,xpath:"",tagName:"richtext",attributes:{},url:window.location.href,timestamp:Date.now(),cmsType:"strapi",cmsField:t.field,cmsId:String(t.entryId),strapiContentType:t.contentType,strapiEntryId:t.entryId,strapiField:t.field,strapiRoute:s};this.capturedContent.set(e,o),this.config.onContentDetected([o])}for(const[,t]of this.strapiEntries){const e=t.attributes?.route;if(e)for(const[,n]of this.capturedContent)n.strapiContentType!==t.contentType||n.strapiEntryId!==t.entryId||n.strapiRoute||(n.strapiRoute=e)}}scanPage(){this.config.captureSelectors.forEach(t=>{try{document.querySelectorAll(t).forEach(t=>this.scanElement(t))}catch(t){}}),this.scanMediaElements(),this.captureApiContent()}scanMediaElements(){if(0===this.strapiMediaMap.size)return;const t=["img[src]","video[src]","source[src]","video[poster]"];for(const e of t)document.querySelectorAll(e).forEach(t=>{if(t.closest("#ollang-debug-panel"))return;const n=e.endsWith("[poster]")?t.poster:t.src||t.getAttribute("src")||"";if(!n)return;let i=this.strapiMediaMap.get(n);if(!i)try{const t=new URL(n).pathname;i=this.strapiMediaMap.get(t)}catch{i=this.strapiMediaMap.get(n)}if(!i)return;const s=this.generateId(n,i.contentType+i.entryId);if(this.capturedContent.has(s))return;const o=(i.mime||"").startsWith("video/")||/\.(mp4|webm|ogg|mov)/i.test(n)?"video":"image",a={id:s,text:i.alt||i.field,type:"cms",selector:this.generateSelector(t),xpath:this.generateXPath(t),tagName:t.tagName.toLowerCase(),attributes:this.extractAttributes(t),url:window.location.href,timestamp:Date.now(),cmsType:"strapi",cmsField:i.field,cmsId:String(i.entryId),strapiContentType:i.contentType,strapiEntryId:i.entryId,strapiField:i.field,mediaUrl:i.url,mediaType:o,mediaAlt:i.alt};this.capturedContent.set(s,a),this.config.onContentDetected([a])})}scanElement(t){if(this.config.excludeSelectors.some(e=>{try{return t.matches(e)}catch(t){return!1}}))return;if(t.closest("#ollang-debug-panel"))return;const e=this.getDirectText(t);if(!e||e.trim().length<3)return;const n=e.trim();if(/^\d+$/.test(n))return;if(n.length<5&&!n.includes(" "))return;if(this.excludedTexts.has(n))return;const i=this.normalizeText(n);if(this.capturedTexts.has(i))return;const s=this.findStrapiMatch(n);if(s){if("description"===s.field)return void(this.config.debug&&console.log(`ℹ️ Skipping DOM capture for long field ${s.contentType}/${s.entryId}/${s.field}`));const e=this.createCapturedContent(t,n,s);return void(this.capturedContent.has(e.id)||(this.capturedContent.set(e.id,e),this.capturedTexts.set(i,e.id),this.config.onContentDetected([e]),this.config.debug&&console.log(`✅ CMS [${s.contentType}/${s.entryId}/${s.field}]: "${n.substring(0,60)}..."`)))}if(t.hasAttribute("data-cms")||t.hasAttribute("data-cms-field")||t.hasAttribute("data-strapi-field")||t.hasAttribute("data-strapi-component")||t.hasAttribute("data-contentful-entry-id")||t.hasAttribute("data-contentful-field-id")||t.hasAttribute("data-sanity")||t.hasAttribute("data-sanity-edit-target")){const e=this.createCapturedContent(t,n,null);return void(this.capturedContent.has(e.id)||(this.capturedContent.set(e.id,e),this.capturedTexts.set(i,e.id),this.config.onContentDetected([e])))}if(this.i18nTexts.size>0&&this.strapiContentMap.size>0&&!this.isI18nText(n)){if(this.isLikelyStaticContent(t,n))return;const e=this.createCapturedContent(t,n,null);e.type="dynamic-unmatched",this.capturedContent.has(e.id)||(this.capturedContent.set(e.id,e),this.capturedTexts.set(i,e.id),this.config.onContentDetected([e]))}}getDirectText(t){let e="";for(const n of Array.from(t.childNodes))n.nodeType===Node.TEXT_NODE&&(e+=n.textContent);return e.trim()?e.trim():0===t.children.length?(t.textContent||"").trim():""}isLikelyStaticContent(t,e){return"A"===t.tagName&&e.length<30||"BUTTON"===t.tagName&&e.length<30||!(!t.closest("footer")&&!t.closest("header, nav"))||!!/^(©|\||\+|→|←|×|✓|✗|▶|•)/.test(e)||!!/©\s*\d{4}/.test(e)}createCapturedContent(t,e,n){const i=this.generateSelector(t),s=this.generateXPath(t),o=this.extractCMSField(t),a=this.extractCMSId(t),r={id:this.generateId(i,e),text:e,type:n||o||a?"cms":"dynamic",selector:i,xpath:s,tagName:t.tagName.toLowerCase(),attributes:this.extractAttributes(t),url:window.location.href,timestamp:Date.now()};return this.config.cmsType&&(r.cmsType=this.config.cmsType),n?(r.cmsType="strapi",r.strapiContentType=n.contentType,r.strapiEntryId=n.entryId,r.strapiField=n.field,r.cmsField=n.field,r.cmsId=String(n.entryId)):(o&&(r.cmsField=o),a&&(r.cmsId=a)),r}extractCMSField(t){for(const e of this.config.captureAttributes){const n=t.getAttribute(e);if(n)return n}return t.getAttribute("data-cms-field")||t.getAttribute("data-contentful-field-id")||t.getAttribute("data-strapi-field")||t.getAttribute("data-sanity-edit-target")||void 0}extractCMSId(t){return t.getAttribute("data-cms-id")||t.getAttribute("data-contentful-entry-id")||t.getAttribute("data-strapi-id")||t.getAttribute("data-sanity-document-id")||void 0}extractAttributes(t){const e={};return Array.from(t.attributes).forEach(t=>{t.name.startsWith("data-")&&(e[t.name]=t.value)}),e}generateSelector(t){if(t.id)return`#${t.id}`;const e=[];let n=t;for(;n&&n!==document.body;){let t=n.tagName.toLowerCase();if(n.className){const e=Array.from(n.classList).filter(t=>!t.startsWith("ng-")&&!t.startsWith("tw-")).slice(0,2);e.length&&(t+="."+e.join("."))}e.unshift(t),n=n.parentElement}return e.join(" > ")}generateXPath(t){if(t.id)return`//*[@id="${t.id}"]`;const e=[];let n=t;for(;n&&n!==document.body;){let t=1,i=n.previousElementSibling;for(;i;)i.tagName===n.tagName&&t++,i=i.previousElementSibling;e.unshift(`${n.tagName.toLowerCase()}[${t}]`),n=n.parentElement}return"/"+e.join("/")}generateId(t,e){const n=t+e;let i=0;for(let t=0;t<n.length;t++)i=(i<<5)-i+n.charCodeAt(t),i&=i;return Math.abs(i).toString(36)}capture(){return this.scanPage(),this.groupCmsEntries(),Array.from(this.capturedContent.values())}groupCmsEntries(){const t=new Map,e=[];for(const[,n]of this.capturedContent){const i=n.strapiContentType&&null!=n.strapiEntryId,s=!!n.mediaUrl;if(i&&!s){const e=`${n.strapiContentType}:${n.strapiEntryId}`;t.has(e)||t.set(e,{items:[],entryData:this.strapiEntries.get(e)}),t.get(e).items.push(n)}else e.push(n)}for(const[e,n]of t){const[t,i]=e.split(":"),s=Number(i);if(!n.items.some(t=>"description"===t.strapiField)){const e=`${t}:${s}:description`,i=this.strapiLongTextMap.get(e);i&&n.items.push({id:`api-desc-${t}-${s}`,text:i.rawText,type:"cms",selector:`api://${t}/${s}/description`,xpath:"",tagName:"richtext",attributes:{},url:window.location.href,timestamp:Date.now(),cmsType:"strapi",cmsField:"description",cmsId:String(s),strapiContentType:t,strapiEntryId:s,strapiField:"description"})}}this.capturedContent.clear();for(const[e,n]of t){const[t,i]=e.split(":"),s=Number(i),o=n.items.find(t=>t.strapiField?.includes("title"))||n.items[0],a=n.entryData?.attributes?.route||n.items.find(t=>t.strapiRoute)?.strapiRoute||void 0,r={};for(const t of n.items)t.strapiField&&(r[t.strapiField]=t.text);const l=`cms-entry-${t}-${s}`,d=Object.keys(r).length,c={...o,id:l,text:o.text,tagName:`entry:${d} fields`,strapiRoute:a,cmsFields:r};this.capturedContent.set(l,c)}for(const t of e)this.capturedContent.set(t.id,t)}getCapturedContent(){return Array.from(this.capturedContent.values())}getCmsContent(){return Array.from(this.capturedContent.values()).filter(t=>"cms"===t.type)}getStrapiMetadata(){return{trackedTexts:this.strapiContentMap.size,entries:this.strapiEntries.size,detectedUrls:[...this.detectedStrapiUrls],cmsType:this.config.cmsType}}clear(){this.capturedContent.clear(),this.capturedTexts.clear()}destroy(){this.observer&&(this.observer.disconnect(),this.observer=null);const t=document.getElementById("ollang-debug-panel");t&&t.remove()}addI18nTexts(t){const e=this.i18nTexts.size;Array.isArray(t)?t.forEach(t=>{this.i18nTexts.add(t),this.i18nTexts.add(t.trim()),this.i18nNormalized.add(this.normalizeText(t))}):this.extractTextsFromObject(t);const n=this.i18nTexts.size-e;console.log(`✅ Added ${n} new i18n texts (total: ${this.i18nTexts.size})`),n>0&&this.capturedContent.size>0&&(this.clear(),this.scanPage())}getI18nTextsCount(){return this.i18nTexts.size}getEntryRoutes(){const t={};for(const[e,n]of this.strapiEntries)n.attributes?.route&&(t[e]=n.attributes.route);return t}async showDebugPanel(){if(document.getElementById("ollang-debug-panel"))return;const t=this.createDebugPanel();if(document.body.appendChild(t),this.config.apiKey)try{if(!await this.validateApiKey())return void this.showApiKeyFormInPanel();this.showPanelContent()}catch(t){console.error("Failed to validate API key:",t),this.showApiKeyFormInPanel()}else this.showApiKeyFormInPanel()}async validateApiKey(){try{const t=(this.config.baseUrl||"").replace(/\/$/,"");if(!t)return!1;const e=await fetch(`${t}/scans/folders`,{headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey}});if(!e.ok)return!1;const n=await e.json(),i=Array.isArray(n)?n:n.folders;return i&&Array.isArray(i)&&(this.folders=i,!this.selectedFolder&&i.length>0&&(this.selectedFolder=i[0].name)),!0}catch(t){return!1}}showApiKeyFormInPanel(){const t=document.getElementById("ollang-panel-content");if(!t)return;t.innerHTML='\n <div style="padding: 20px;">\n <h3 style="margin: 0 0 15px 0; font-size: 16px; color: #333;">Ollang API Key Required</h3>\n <p style="margin: 0 0 20px 0; color: #666; font-size: 13px; line-height: 1.5;">\n Enter your Ollang API key for this app. Not the same as any Strapi token.\n </p>\n <div style="margin-bottom: 15px;">\n <label style="display: block; margin-bottom: 6px; font-weight: 500; color: #333; font-size: 13px;">Ollang API Key</label>\n <input type="text" id="ollang-apikey-input" placeholder="Ollang API key"\n style="width: 100%; padding: 8px 10px; border: 2px solid #ddd; border-radius: 4px; font-size: 13px; box-sizing: border-box; font-family: monospace;" />\n </div>\n <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 15px;">\n <div id="ollang-status-indicator" style="width: 10px; height: 10px; border-radius: 50%; background: #ccc; transition: background-color 0.3s;"></div>\n <span id="ollang-status-text" style="font-size: 12px; color: #666;">Not validated</span>\n </div>\n <button id="ollang-validate-btn" class="ollang-btn" style="width: 100%;">Validate & Continue</button>\n </div>\n ';const e=document.getElementById("ollang-apikey-input"),n=document.getElementById("ollang-validate-btn"),i=document.getElementById("ollang-status-indicator"),s=document.getElementById("ollang-status-text"),o=async()=>{const t=e.value.trim();if(!t)return void this.showStatus("Please enter Ollang API key","error");n.disabled=!0,n.textContent="Validating...",i.style.background="#ffc107",s.textContent="Validating...",s.style.color="#ffc107";const o=this.config.apiKey;this.config.apiKey=t;try{await this.validateApiKey()?(this.saveApiKey(t),i.style.background="#28a745",s.textContent="Valid API key ✓",s.style.color="#28a745",await new Promise(t=>setTimeout(t,500)),this.showPanelContent()):(i.style.background="#dc3545",s.textContent="Invalid API key ✗",s.style.color="#dc3545",this.config.apiKey=o,n.disabled=!1,n.textContent="Validate & Continue")}catch(t){i.style.background="#dc3545",s.textContent="Validation failed ✗",s.style.color="#dc3545",this.config.apiKey=o,n.disabled=!1,n.textContent="Validate & Continue"}};n.addEventListener("click",o),e.addEventListener("keypress",t=>{"Enter"===t.key&&o()}),setTimeout(()=>e.focus(),100)}showPanelContent(){const t=document.getElementById("ollang-panel-content");if(!t)return;t.innerHTML="";const e=document.createElement("div");e.id="ollang-stats",e.style.cssText="padding: 15px; border-bottom: 1px solid #ddd; background: #f8f9fa;",this.updateStats(e);const n=document.createElement("div");n.id="ollang-status",n.style.cssText="padding: 10px 15px; border-bottom: 1px solid #ddd; background: #e7f3ff; font-size: 12px; display: none;",n.innerHTML='<span id="ollang-status-text">Ready</span><button id="ollang-status-close" style="float: right; background: none; border: none; cursor: pointer; font-size: 14px;">&times;</button>';const i=document.createElement("div");i.style.cssText="padding: 12px 16px; border-bottom: 1px solid #e2e8f0; display: flex; flex-direction: column; gap: 10px;",i.innerHTML='\n <div style="display: flex; gap: 8px;">\n <button id="ollang-capture" class="ollang-btn">Capture</button>\n <button id="ollang-clear" class="ollang-btn ollang-btn-ghost">Clear</button>\n </div>\n <div style="display: flex; align-items: flex-end; justify-content: space-between; gap: 12px;">\n <div style="display: flex; flex-direction: column; gap: 4px; flex: 1;">\n <span style="font-size: 11px; font-weight: 500; color: #64748b;">Folder</span>\n <div style="display: flex; gap: 6px; align-items: center;">\n <div id="ollang-folder-dropdown" class="ollang-folder-dropdown">\n <button id="ollang-folder-trigger" type="button" class="ollang-folder-trigger">\n <span id="ollang-folder-label" class="ollang-folder-label">Select folder...</span>\n <span class="ollang-folder-arrow">▾</span>\n </button>\n <div id="ollang-folder-menu" class="ollang-folder-menu"></div>\n </div>\n <button id="ollang-new-folder" class="ollang-btn-sm">+ New</button>\n </div>\n </div>\n <button id="ollang-push-tms" class="ollang-btn ollang-btn-primary">Push to Ollang</button>\n </div>\n <div id="ollang-strapi-schema-block" style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #e2e8f0;">\n <span style="font-size: 11px; font-weight: 500; color: #64748b;">Strapi schema (optional)</span>\n <p style="margin: 4px 0 8px 0; font-size: 11px; color: #94a3b8;">Fetch schema here so Push uses Content-Type Builder fields. Use your Strapi API token (not the Ollang TMS API token).</p>\n <div style="display: flex; flex-direction: column; gap: 6px;">\n <input type="text" id="ollang-strapi-url" placeholder="Strapi URL (e.g. https://api.example.com)" style="width: 100%; padding: 6px 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; box-sizing: border-box;" />\n <input type="password" id="ollang-strapi-jwt" placeholder="Strapi Admin JWT token" style="width: 100%; padding: 6px 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; box-sizing: border-box;" />\n <button id="ollang-fetch-schema" class="ollang-btn-sm">Fetch schema</button>\n </div>\n </div>\n ';const s=document.createElement("div");s.id="ollang-selection-info",s.style.cssText="padding: 8px 16px; border-bottom: 1px solid #e2e8f0; background: #f8fafc; font-size: 12px; display: none;",s.innerHTML='\n <div class="ollang-selection-bar">\n <div class="ollang-selection-count">\n <span class="ollang-selection-dot"></span>\n <span id="ollang-selected-count">0</span>\n <span class="ollang-selection-label">items selected</span>\n </div>\n <div class="ollang-selection-actions">\n <button id="ollang-select-all" class="ollang-btn ollang-btn-ghost">Select All</button>\n <button id="ollang-deselect-all" class="ollang-btn ollang-btn-ghost">Deselect All</button>\n <button id="ollang-select-cms-only" class="ollang-btn ollang-selection-cms">Select CMS Only</button>\n </div>\n </div>\n ';const o=document.createElement("div");o.id="ollang-content-list",o.style.cssText="flex: 1; overflow-y: auto; padding: 15px;",this.loadFolders(),t.appendChild(e),t.appendChild(n),t.appendChild(i),t.appendChild(s),t.appendChild(o),setTimeout(()=>{document.getElementById("ollang-capture")?.addEventListener("click",()=>{this.capture(),this.updateStats(e),this.showContent(o),this.showStatus(`Captured ${this.capturedContent.size} items (${this.getCmsContent().length} CMS)`,"success")}),document.getElementById("ollang-clear")?.addEventListener("click",()=>{this.clear(),this.updateStats(e),o.innerHTML='<p style="color: #999; text-align: center;">No content captured</p>',this.showStatus("Cleared all content","success")}),document.getElementById("ollang-select-all")?.addEventListener("click",()=>{Array.from(this.capturedContent.values()).forEach(t=>this.selectedContentIds.add(t.id)),this.showContent(o)}),document.getElementById("ollang-deselect-all")?.addEventListener("click",()=>{this.selectedContentIds.clear(),this.showContent(o)}),document.getElementById("ollang-select-cms-only")?.addEventListener("click",()=>{this.selectedContentIds.clear(),this.getCmsContent().forEach(t=>this.selectedContentIds.add(t.id)),this.showContent(o)}),document.getElementById("ollang-push-tms")?.addEventListener("click",()=>this.pushToTMS()),document.getElementById("ollang-fetch-schema")?.addEventListener("click",()=>this.fetchStrapiSchemaInPanel());const t=document.getElementById("ollang-strapi-url");t&&!t.value&&(t.value=this.config.strapiUrl||[...this.detectedStrapiUrls][0]||""),this.updateFolderOptions();const n=document.getElementById("ollang-folder-dropdown"),i=document.getElementById("ollang-folder-trigger"),s=document.getElementById("ollang-folder-menu");if(i&&s&&n){const t=t=>{t??"true"!==s.getAttribute("data-open")?(s.style.display="block",s.setAttribute("data-open","true")):(s.style.display="none",s.setAttribute("data-open","false"))};i.addEventListener("click",e=>{e.stopPropagation(),t()}),document.addEventListener("click",e=>{n.contains(e.target)||t(!1)})}document.getElementById("ollang-new-folder")?.addEventListener("click",()=>this.showNewFolderDialog()),document.getElementById("ollang-status-close")?.addEventListener("click",()=>this.hideStatus())},0),setInterval(()=>this.updateStats(e),2e3)}createDebugPanel(){const t=document.createElement("div");t.id="ollang-debug-panel",t.style.cssText=["position: fixed","right: 20px","left: auto","transform: none","bottom: 0","width: min(540px, 100% - 40px)","min-height: 320px","max-height: 90vh","background: #ffffff","border-radius: 12px 12px 0 0","border: 1px solid rgba(15, 23, 42, 0.12)","box-shadow: 0 18px 45px rgba(15, 23, 42, 0.25)","z-index: 999999",'font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',"display: flex","flex-direction: column","overflow: hidden","backdrop-filter: blur(10px)","-webkit-backdrop-filter: blur(10px)","background-clip: padding-box"].join("; ")+";";const e=document.createElement("div");e.style.cssText=["height: 6px","cursor: ns-resize","display: flex","align-items: center","justify-content: center","background: transparent"].join("; ")+";";const n=document.createElement("div");n.style.cssText="width: 36px; height: 3px; border-radius: 999px; background: rgba(148, 163, 184, 0.95);",e.appendChild(n);let i=!1,s=0,o=0;const a=e=>{if(!i)return;const n=s-e.clientY,a=Math.min(Math.max(o+n,140),Math.round(.7*window.innerHeight));t.style.height=`${a}px`},r=()=>{i&&(i=!1,document.removeEventListener("mousemove",a),document.removeEventListener("mouseup",r))};e.addEventListener("mousedown",e=>{i=!0,s=e.clientY,o=t.getBoundingClientRect().height,document.addEventListener("mousemove",a),document.addEventListener("mouseup",r)});const l=document.createElement("div");l.style.cssText=["padding: 10px 16px","border-bottom: 1px solid rgba(148, 163, 184, 0.25)","display: flex","justify-content: space-between","align-items: center","background: #ffffff","border-radius: 12px 12px 0 0","color: #0f172a","box-shadow: 0 1px 0 rgba(15, 23, 42, 0.04)"].join("; ")+";",l.innerHTML='\n <div style="display: flex; align-items: center; gap: 10px;">\n <div style="width: 32px; height: 32px; border-radius: 999px; background: #ffffff; display: flex; align-items: center; justify-content: center; box-shadow: 0 0 0 1px rgba(148, 163, 184, 0.45); padding: 4px;">\n <svg viewBox="0 0 37 32" xmlns="http://www.w3.org/2000/svg" style="width: 26px; height: 22px; display: block;">\n <path d="M35.8246 10.862C34.5972 11.5165 33.2999 12.0249 31.9585 12.3772C30.4527 5.10884 24.8838 0.0517578 18.3428 0.0517578H18.2347C15.3756 0.184498 13.2599 1.58635 12.4149 3.89149C11.2871 6.96393 12.7167 11.1501 15.9666 14.3132C18.6573 16.9259 22.7585 18.4605 26.9677 18.4378C26.2857 21.1303 24.6634 23.4766 22.405 25.037C20.1466 26.5973 17.4072 27.2645 14.7005 26.9134C11.9939 26.5622 9.50584 25.2168 7.70306 23.1296C5.90027 21.0423 4.90653 18.3565 4.90817 15.5759C4.90817 12.9858 6.04543 9.13633 9.25081 6.75996L9.56849 6.52687V0.699269L9.28261 0.854665C8.27975 1.42954 7.30632 2.0563 6.36626 2.73246C1.67098 6.21284 0.0126953 11.6552 0.0126953 15.592C0.0174583 19.7867 1.59692 23.8205 4.427 26.8662C7.25707 29.9119 11.1233 31.7386 15.2329 31.9718C19.3424 32.2049 23.3837 30.8267 26.528 28.12C29.6723 25.4132 31.6812 21.583 32.1427 17.4148C32.5049 17.282 33.0036 17.1428 33.5278 16.9939C34.4967 16.7187 35.4973 16.4338 36.0247 16.1133L36.1168 16.0583V10.7325L35.8246 10.862ZM27.1297 13.4326C24.7312 13.4746 21.4972 12.7851 19.3529 10.6968C17.504 8.89676 16.6495 6.63372 17.0085 5.64626C17.1705 5.21243 17.8598 5.08294 18.3999 5.05056C21.9642 5.0797 26.1639 8.21686 27.1297 13.4326Z" fill="#6148f9" />\n </svg>\n </div>\n <div style="display: flex; flex-direction: column;">\n <span style="font-size: 13px; font-weight: 600; color: #0f172a; letter-spacing: 0.02em;">Ollang</span>\n <span style="font-size: 11px; font-weight: 500; color: #64748b;">CMS Detect</span>\n </div>\n </div>\n <button id="ollang-close"\n style="background: #f8fafc; border-radius: 999px; border: 1px solid rgba(148, 163, 184, 0.6); width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; color: #0f172a; cursor: pointer; font-size: 18px; line-height: 1; padding: 0;">\n ×\n </button>\n ';const d=document.createElement("div");d.id="ollang-panel-content",d.style.cssText="flex: 1; overflow-y: auto; display: flex; flex-direction: column;";const c=document.createElement("style");return c.textContent="\n .ollang-btn {\n padding: 6px 12px;\n border-radius: 6px;\n border: 1px solid #e2e8f0;\n background: #ffffff;\n color: #0f172a;\n cursor: pointer;\n font-size: 12px;\n font-weight: 500;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 4px;\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);\n }\n .ollang-btn:hover {\n background: #f8fafc;\n border-color: #cbd5f5;\n }\n .ollang-btn:disabled {\n background: #f8fafc;\n border-color: #e2e8f0;\n color: #94a3b8;\n cursor: not-allowed;\n box-shadow: none;\n }\n .ollang-btn-primary {\n background: #1d4ed8;\n border-color: #1d4ed8;\n color: #ffffff;\n }\n .ollang-btn-primary:hover {\n background: #1e40af;\n border-color: #1e40af;\n }\n .ollang-btn-ghost {\n background: #f8fafc;\n border-color: #e2e8f0;\n color: #0f172a;\n }\n .ollang-btn-link {\n background: none;\n border: none;\n color: #2563eb;\n cursor: pointer;\n font-size: 11px;\n text-decoration: underline;\n padding: 0;\n }\n .ollang-btn-sm {\n padding: 4px 10px;\n background: #0f172a;\n color: #ffffff;\n border-radius: 999px;\n border: none;\n cursor: pointer;\n font-size: 11px;\n font-weight: 500;\n }\n .ollang-btn-sm:hover {\n background: #020617;\n }\n .ollang-selection-bar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n }\n .ollang-selection-count {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n color: #0f172a;\n }\n .ollang-selection-dot {\n width: 8px;\n height: 8px;\n border-radius: 999px;\n background: #22c55e;\n box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.25);\n }\n .ollang-selection-label {\n font-size: 11px;\n color: #64748b;\n }\n .ollang-selection-actions {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n }\n .ollang-selection-cms {\n color: #16a34a;\n }\n .ollang-folder-dropdown {\n position: relative;\n min-width: 220px;\n }\n .ollang-folder-trigger {\n width: 100%;\n padding: 6px 10px;\n border-radius: 999px;\n border: 1px solid #e2e8f0;\n background: #ffffff;\n color: #0f172a;\n font-size: 12px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n cursor: pointer;\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);\n }\n .ollang-folder-trigger:hover {\n border-color: #cbd5f5;\n background: #f8fafc;\n }\n .ollang-folder-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .ollang-folder-arrow {\n font-size: 10px;\n color: #94a3b8;\n margin-left: 6px;\n }\n .ollang-folder-menu {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n right: 0;\n max-height: 200px;\n overflow-y: auto;\n background: #ffffff;\n border-radius: 12px;\n border: 1px solid #e2e8f0;\n box-shadow: 0 10px 25px rgba(15, 23, 42, 0.15);\n padding: 4px;\n display: none;\n z-index: 10;\n }\n .ollang-folder-option {\n width: 100%;\n text-align: left;\n padding: 6px 8px;\n border-radius: 8px;\n border: none;\n background: transparent;\n font-size: 12px;\n color: #0f172a;\n cursor: pointer;\n }\n .ollang-folder-option:hover {\n background: #eff6ff;\n }\n .ollang-folder-option-active {\n background: #1d4ed8;\n color: #ffffff;\n }\n .ollang-content-item {\n background: #f8fafc;\n padding: 10px;\n margin: 6px 0;\n border-radius: 8px;\n font-size: 12px;\n border: 1px solid #e2e8f0;\n display: flex;\n gap: 10px;\n align-items: flex-start;\n }\n .ollang-content-item.cms-matched {\n border-color: #22c55e;\n background: #f0fdf4;\n }\n .ollang-content-item.selected {\n background: #eff6ff;\n border-color: #2563eb;\n }\n .ollang-content-checkbox {\n margin-top: 2px;\n cursor: pointer;\n width: 16px;\n height: 16px;\n }\n .ollang-content-body {\n flex: 1;\n }\n .ollang-content-text {\n font-weight: 600;\n margin-bottom: 5px;\n color: #0f172a;\n }\n .ollang-content-meta {\n color: #64748b;\n font-size: 11px;\n }\n .ollang-cms-badge {\n display: inline-block;\n padding: 1px 6px;\n border-radius: 999px;\n font-size: 10px;\n font-weight: 600;\n }\n .ollang-badge-cms {\n background: #dcfce7;\n color: #15803d;\n }\n .ollang-badge-dynamic {\n background: #fef9c3;\n color: #854d0e;\n }\n .ollang-badge-unmatched {\n background: #fee2e2;\n color: #b91c1c;\n }\n #ollang-apikey-input:focus {\n outline: none;\n border-color: #2563eb;\n }\n .ollang-badge-image {\n background: #ede9fe;\n color: #6d28d9;\n }\n .ollang-badge-video {\n background: #fce7f3;\n color: #be185d;\n }\n .ollang-media-item {\n align-items: center;\n }\n .ollang-media-preview {\n width: 52px;\n height: 40px;\n border-radius: 6px;\n object-fit: cover;\n flex-shrink: 0;\n border: 1px solid #e2e8f0;\n background: #f1f5f9;\n }\n .ollang-media-video-thumb {\n width: 52px;\n height: 40px;\n border-radius: 6px;\n background: #0f172a;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n border: 1px solid #e2e8f0;\n }\n .ollang-media-play {\n color: #ffffff;\n font-size: 14px;\n opacity: 0.9;\n }\n ",t.appendChild(c),t.appendChild(e),t.appendChild(l),t.appendChild(d),setTimeout(()=>{document.getElementById("ollang-close")?.addEventListener("click",()=>t.remove())},0),t}updateStats(t){if(!t)return;const e=this.capturedContent.size,n=this.getCmsContent().length,i=Array.from(this.capturedContent.values()).filter(t=>!!t.mediaUrl),s=i.filter(t=>"image"===t.mediaType).length,o=i.filter(t=>"video"===t.mediaType).length;t.innerHTML=`\n <div style="display: flex; flex-direction: column; gap: 2px;">\n <div style="font-size: 12px; font-weight: 600; color: #0f172a;">\n ${e} captured\n <span style="font-weight: 400; color: #64748b; margin-left: 4px;">(${n} text · ${s} image · ${o} video)</span>\n </div>\n <div style="font-size: 11px; color: #94a3b8;">\n ${this.config.cmsType||"Auto-detect"}${this.config.strapiUrl?" · "+this.config.strapiUrl:""}\n · tracked: ${this.strapiContentMap.size} texts, ${this.strapiMediaMap.size} media\n </div>\n </div>\n `}showContent(t){const e=Array.from(this.capturedContent.values());if(0===e.length)return t.innerHTML='<p style="color: #999; text-align: center;">No content captured yet</p>',void this.updateSelectionInfo();e.sort((t,e)=>"cms"===t.type&&"cms"!==e.type?-1:"cms"!==t.type&&"cms"===e.type?1:e.text.length-t.text.length),t.innerHTML=e.map(t=>{const e=this.selectedContentIds.has(t.id),n="cms"===t.type,i=n?"ollang-badge-cms":"dynamic-unmatched"===t.type?"ollang-badge-unmatched":"ollang-badge-dynamic",s=n?"CMS":"dynamic-unmatched"===t.type?"Unmatched":"Dynamic";if(t.mediaUrl){const i="video"===t.mediaType,o=i?"Video":"Image",a=i?"ollang-badge-video":"ollang-badge-image",r=i?'<div class="ollang-media-preview ollang-media-video-thumb">\n <span class="ollang-media-play">▶</span>\n </div>':`<img class="ollang-media-preview" src="${this.escapeHtml(t.mediaUrl)}" alt="${this.escapeHtml(t.mediaAlt||"")}" loading="lazy" />`;return`<div class="ollang-content-item ${n?"cms-matched":""} ollang-media-item ${e?"selected":""}" data-id="${t.id}">\n <input type="checkbox" class="ollang-content-checkbox" data-id="${t.id}" ${e?"checked":""}>\n ${r}\n <div class="ollang-content-body">\n <div class="ollang-content-text">${this.escapeHtml((t.mediaAlt||t.strapiField||t.mediaUrl).substring(0,80))}</div>\n <div class="ollang-content-meta">\n <span class="ollang-cms-badge ollang-badge-cms">${s}</span>\n <span class="ollang-cms-badge ${a}">${o}</span>\n ${t.strapiContentType?" "+t.strapiContentType:""}${t.strapiEntryId?"#"+t.strapiEntryId:""}${t.strapiField?" → "+t.strapiField:""}\n </div>\n </div>\n </div>`}const o=t.cmsFields?`<strong>${t.strapiContentType}#${t.strapiEntryId}</strong> (${Object.keys(t.cmsFields).join(", ")})`:`&lt;${t.tagName}&gt;${t.strapiContentType?" | "+t.strapiContentType:""}${t.strapiEntryId?"#"+t.strapiEntryId:""}${t.strapiField?" → "+t.strapiField:""}`;return`<div class="ollang-content-item ${n?"cms-matched":""} ${e?"selected":""}" data-id="${t.id}">\n <input type="checkbox" class="ollang-content-checkbox" data-id="${t.id}" ${e?"checked":""}>\n <div class="ollang-content-body">\n <div class="ollang-content-text">${this.escapeHtml(t.text.substring(0,80))}${t.text.length>80?"...":""}</div>\n <div class="ollang-content-meta"><span class="ollang-cms-badge ${i}">${s}</span> ${o}</div>\n </div></div>`}).join(""),t.querySelectorAll(".ollang-content-checkbox").forEach(e=>{e.addEventListener("change",e=>{const n=e.target,i=n.dataset.id,s=t.querySelector(`[data-id="${i}"]`);n.checked?(this.selectedContentIds.add(i),s?.classList.add("selected")):(this.selectedContentIds.delete(i),s?.classList.remove("selected")),this.updateSelectionInfo()})}),this.updateSelectionInfo()}updateSelectionInfo(){const t=document.getElementById("ollang-selection-info"),e=document.getElementById("ollang-selected-count"),n=document.getElementById("ollang-push-tms");t&&e&&(this.selectedContentIds.size>0?(t.style.display="block",e.textContent=String(this.selectedContentIds.size)):t.style.display="none"),n&&(n.disabled=0===this.selectedContentIds.size)}exportContent(){const t=JSON.stringify(Array.from(this.capturedContent.values()),null,2),e=new Blob([t],{type:"application/json"}),n=URL.createObjectURL(e),i=document.createElement("a");i.href=n,i.download=`ollang-captured-${Date.now()}.json`,i.click(),URL.revokeObjectURL(n)}escapeHtml(t){const e=document.createElement("div");return e.textContent=t,e.innerHTML}showStatus(t,e="info"){const n=document.getElementById("ollang-status"),i=document.getElementById("ollang-status-text");n&&i&&(i.textContent=t,n.style.backgroundColor={success:"#d4edda",error:"#f8d7da",info:"#e7f3ff"}[e],n.style.display="block","error"!==e&&setTimeout(()=>{n&&(n.style.display="none")},5e3))}hideStatus(){const t=document.getElementById("ollang-status");t&&(t.style.display="none")}async loadFolders(){try{if(this.folders.length>0)return void this.updateFolderOptions();if(!this.config.apiKey)return;const t=(this.config.baseUrl||"").replace(/\/$/,"");if(!t)return;const e=await fetch(`${t}/scans/folders`,{headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey}});if(e.ok){const t=await e.json(),n=Array.isArray(t)?t:t.folders;n?.length>0&&(this.folders=n,this.selectedFolder||(this.selectedFolder=n[0].name),this.updateFolderOptions())}}catch(t){console.warn("Failed to load folders:",t)}}updateFolderOptions(){const t=document.getElementById("ollang-folder-label"),e=document.getElementById("ollang-folder-menu");if(!t||!e)return;const n=this.selectedFolder||(this.folders[0]?.name??"");n?(this.selectedFolder=n,t.textContent=n):t.textContent="Select folder...",e.innerHTML="",this.folders.forEach(n=>{const i=document.createElement("button");i.type="button",i.className="ollang-folder-option"+(n.name===this.selectedFolder?" ollang-folder-option-active":""),i.textContent=n.name,i.addEventListener("click",()=>{this.selectedFolder=n.name,t.textContent=n.name,e.querySelectorAll(".ollang-folder-option").forEach(t=>t.classList.remove("ollang-folder-option-active")),i.classList.add("ollang-folder-option-active"),e.setAttribute("data-open","false"),e.style.display="none"}),e.appendChild(i)})}showNewFolderDialog(){if(document.getElementById("ollang-new-folder-container"))return;const t=document.querySelector("#ollang-debug-panel #ollang-push-tms")?.parentElement;if(!t)return;const e=document.createElement("div");e.id="ollang-new-folder-container",e.style.cssText="width: 100%; padding: 8px 15px 0 15px; display: flex; gap: 6px; align-items: center;";const n=document.createElement("input");n.type="text",n.placeholder="Enter folder name",n.style.cssText="flex: 1; padding: 4px 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px;";const i=document.createElement("button");i.textContent="Create",i.className="ollang-btn-sm";const s=document.createElement("button");s.textContent="Cancel",s.className="ollang-btn-link";const o=()=>{e.parentElement&&e.remove()};i.addEventListener("click",()=>{const t=n.value.trim();t?(this.selectedFolder=t,this.folders.find(e=>e.name===t)||this.folders.push({id:t,name:t}),this.updateFolderOptions(),this.showStatus(`Folder "${t}" selected.`,"success"),o()):this.showStatus("Please enter a folder name","error")}),s.addEventListener("click",o),e.appendChild(n),e.appendChild(i),e.appendChild(s),t.parentElement?.insertBefore(e,t.nextSibling),setTimeout(()=>n.focus(),0)}async fetchStrapiSchemaInPanel(){const t=(this.config.baseUrl||"").replace(/\/$/,"");if(!t)return void this.showStatus("Missing TMS baseUrl","error");const e=document.getElementById("ollang-strapi-url"),n=document.getElementById("ollang-strapi-jwt"),i=document.getElementById("ollang-fetch-schema"),s=e?.value?.trim(),o=n?.value?.trim();if(s&&o){i&&(i.disabled=!0),this.showStatus("Fetching Strapi schema...","info");try{const e=await fetch(`${t}/api/strapi-schema`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({strapiUrl:s,strapiToken:o})}),n=await e.json().catch(()=>({}));if(e.ok&&n.success){const t=n.contentTypes?.length??0;this.showStatus(`Schema loaded: ${t} content-type(s)`,"success")}else this.showStatus(n.error||`Failed (${e.status})`,"error")}catch(t){this.showStatus("Network error: "+(t instanceof Error?t.message:String(t)),"error")}finally{i&&(i.disabled=!1)}}else this.showStatus("Enter Strapi URL and Strapi API token","error")}async pushToTMS(){if(0===this.selectedContentIds.size)return void this.showStatus("Please select at least one item","error");if(!this.selectedFolder)return void this.showStatus("Please select a folder first","error");if(!this.config.apiKey)return void this.showStatus("Please enter TMS API token first","error");const t=(this.config.baseUrl||"").replace(/\/$/,"");if(!t)return void this.showStatus("Missing baseUrl","error");const e=Array.from(this.capturedContent.values()).filter(t=>this.selectedContentIds.has(t.id)),n=e.filter(t=>!(!t.mediaUrl||"cms"!==t.type&&"strapi"!==t.cmsType&&!t.strapiContentType)),i=e.filter(t=>!t.mediaUrl||!("cms"===t.type||"strapi"===t.cmsType||t.strapiContentType));if(0!==i.length||0!==n.length){this.showStatus(`Pushing ${i.length} text and ${n.length} media items to Ollang...`,"info");try{const s=i.some(t=>"cms"===t.type||"strapi"===t.cmsType||!!t.strapiContentType)||n.length>0;let o={};const a=this.config.strapiUrl||[...this.detectedStrapiUrls][0]||"";if(s&&a&&t)try{const e=`${t}/api/strapi-field-config?strapiUrl=${encodeURIComponent(a.replace(/\/$/,""))}`,n=await fetch(e,{headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey}});if(n.ok){const t=await n.json();t.fieldsByContentType&&Object.keys(t.fieldsByContentType).length>0&&(o=t.fieldsByContentType,this.config.debug&&console.log("[Ollang] Using dynamic Strapi field config:",o))}}catch(t){this.config.debug&&console.warn("[Ollang] Could not fetch Strapi field config:",t)}const r=(t,e)=>{if(t===e)return!0;if(t.includes("[]")){const n=t.replace(/\./g,"\\.").replace(/\[\]/g,"\\.\\d+");return new RegExp(`^${n}$`).test(e)}return!1},l=(t,e)=>{if(!t||!e)return;const n=e.split(".");let i=t;for(const t of n){if(null==i)return;i=i[t]}return i},d=t=>{if(null!=t){if("string"==typeof t||"number"==typeof t)return String(t);if("object"==typeof t){const e=t?.data?.attributes?.url??t?.url;if("string"==typeof e)return e;try{return JSON.stringify(t)}catch{return}}return String(t)}},c=new Map,p=[];for(const t of i)if(t.strapiContentType&&null!=t.strapiEntryId){const e=`${t.strapiContentType}:${t.strapiEntryId}`;c.has(e)||c.set(e,{items:[],entryData:this.strapiEntries.get(e)}),c.get(e).items.push(t)}else p.push(t);for(const[t,e]of c){const[n,i]=t.split(":"),s=Number(i);if(!e.items.some(t=>"description"===t.strapiField)){const t=`${n}:${s}:description`,i=this.strapiLongTextMap.get(t);i&&e.items.push({id:`api-desc-${n}-${s}`,text:i.rawText,type:"cms",selector:`api://${n}/${s}/description`,xpath:"",tagName:"richtext",attributes:{},url:window.location.href,timestamp:Date.now(),cmsType:"strapi",cmsField:"description",cmsId:String(s),strapiContentType:n,strapiEntryId:s,strapiField:"description"})}}const g=Array.from(c.entries()).map(([t,e])=>{const[n,i]=t.split(":"),s=Number(i),a=e.items.find(t=>t.strapiField?.includes("title"))||e.items[0],c=e.entryData?.attributes?.route||e.items.find(t=>t.strapiRoute)?.strapiRoute||null,p={};for(const t of e.items)t.strapiField&&(p[t.strapiField]=t.text);const g=o[n]??(n.endsWith("s")?o[n.slice(0,-1)]:void 0);if(g&&g.length>0){const t={};for(const[e,n]of Object.entries(p))g.some(t=>r(t,e))&&(t[e]=n);Object.keys(p).forEach(t=>delete p[t]),Object.assign(p,t);const n=e.entryData?.attributes;if(n)for(const t of g){const e=t.replace(/\[\]/g,".0");if(Object.keys(p).find(e=>r(t,e)))continue;const i=l(n,e),s=d(i);void 0!==s&&""!==s&&(p[t]=s)}}return{id:`cms-entry-${n}-${s}`,text:a.text,type:"cms",source:{file:a.selector||"browser-dom",line:0,column:0,context:a.xpath||""},strapiContentType:n,strapiEntryId:s,strapiField:a.strapiField||"header.title",strapiRoute:c,cmsFields:p,selected:!1,status:"scanned"}}),u=p.map(t=>({id:`cms-${t.id}`,text:t.text,type:"cms"===t.type?"cms":"dynamic",source:{file:t.selector||"browser-dom",line:0,column:0,context:t.xpath||""},selected:!1,status:"scanned"})),h=Array.from(this.strapiEntries.values()).map(t=>({contentType:t.contentType,entryId:t.entryId,route:t.attributes?.route||null,locale:t.attributes?.locale||null,title:t.attributes?.header?.title||t.attributes?.title||null})),f={texts:[...g,...u],media:n.map(t=>({id:`media-${t.id}`,mediaUrl:t.mediaUrl,mediaType:t.mediaType,alt:t.mediaAlt,type:"cms-media",source:{file:t.selector||"browser-dom",line:0,column:0,context:t.xpath||""},metadata:{selector:t.selector,xpath:t.xpath,tagName:t.tagName,attributes:t.attributes,cmsType:t.cmsType,cmsField:t.cmsField,cmsId:t.cmsId,strapiContentType:t.strapiContentType,strapiEntryId:t.strapiEntryId,strapiField:t.strapiField,strapiRoute:t.strapiRoute},selected:!1,status:"scanned"})),isCms:s,cms:{strapi:{entries:h}},routes:this.getEntryRoutes(),timestamp:(new Date).toISOString(),projectRoot:window.location.origin,sourceLanguage:"en",targetLanguages:[],projectId:this.config.projectId||"unknown",folderName:this.selectedFolder};let m=null;try{const e=await fetch(`${t}/scans`,{headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey}});if(e.ok){const t=await e.json();if(Array.isArray(t)){const e=window.location.href;let n=null;for(const i of t){const t="string"==typeof i.scanData?JSON.parse(i.scanData):i.scanData;if(t?.folderName!==this.selectedFolder)continue;const s=i.url===e||i.originalUrl===e||t?.projectRoot===window.location.origin;if((!n||s)&&(n=i,s))break}n&&(m=n.id||n._id)}}}catch(t){console.warn("Failed to list scans:",t)}const y={url:window.location.href,scanData:f,originalFilename:`cms-scan-${Date.now()}.json`,folderName:this.selectedFolder},b=m?`${t}/scans/${m}`:`${t}/scans`,x=m?"PATCH":"POST",w=await fetch(b,{method:x,headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey},body:JSON.stringify(y)});if(!w.ok){const t=await w.json();throw new Error(t.message||`Failed: ${w.statusText}`)}const v=await w.json();this.showStatus(`✅ Pushed ${e.length} items to Ollang! Scan ID: ${v.id||"N/A"}`,"success"),this.selectedContentIds.clear();const C=document.getElementById("ollang-content-list");C&&this.showContent(C)}catch(t){console.error("Push to Ollang error:",t),this.showStatus(`❌ Failed to push to Ollang: ${t.message}`,"error")}}else this.showStatus("Nothing to push. Please select at least one text or media item.","info")}}e.OllangBrowser=n,n.MAX_FIELD_LENGTH=500,n.LONG_TEXT_FIELDS=new Set(["description","content","body","html","markdown","richText","text"]),n.MAX_RECURSION_DEPTH=4,n.SKIP_RELATION_KEYS=new Set(["author","editor","localizations","category","categories"]),n.SKIP_KEYS=new Set(["id","createdAt","updatedAt","publishedAt","publishedDate","locale","route","url","path","slug","hash","ext","mime","provider","previewUrl","provider_metadata","background","name","alternativeText","caption","isInvisible","views","size","width","height","isStory","summary"])}},e={};function n(i){var s=e[i];if(void 0!==s)return s.exports;var o=e[i]={exports:{}};return t[i](o,o.exports,n),o.exports}var i={};return(()=>{var t=i;Object.defineProperty(t,"__esModule",{value:!0});const e=n(243);!function(){if("undefined"==typeof window)return;const t=document.currentScript;let n;if(window.ollangConfig)n=window.ollangConfig;else{if(!t)return void console.warn("Ollang: No configuration found. Provide window.ollangConfig or use data attributes.");n={apiKey:t.dataset.apiKey||"",projectId:t.dataset.projectId,baseUrl:t.dataset.baseUrl,strapiUrl:t.dataset.strapiUrl||"",autoDetectCMS:"false"!==t.dataset.autoDetectCms,cmsType:t.dataset.cmsType,debounceMs:parseInt(t.dataset.debounceMs||"1000"),debug:"true"===t.dataset.debug}}function i(){window.Ollang=e.OllangBrowser;const t=new e.OllangBrowser(n);window.ollangInstance=t,window.ollang=t,console.log("✅ Ollang Browser SDK initialized (v2 - API Interception)"),"true"===new URLSearchParams(window.location.search).get("ollang-localize")&&setTimeout(()=>{t.showDebugPanel().catch(t=>{console.error("Failed to show debug panel:",t)})},1e3)}n.apiKey||console.log("Ollang: API key not provided. User will be prompted to enter it."),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",i):i()}()})(),i})());
1
+ !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Ollang=e():t.Ollang=e()}(this,()=>(()=>{"use strict";var t={243(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.OllangBrowser=void 0;class n{constructor(t){if(this.observer=null,this.capturedContent=new Map,this.i18nTexts=new Set,this.i18nNormalized=new Set,this.excludedTexts=new Set,this.selectedContentIds=new Set,this.folders=[],this.selectedFolder="",this.strapiContentMap=new Map,this.strapiMediaMap=new Map,this.detectedStrapiUrls=new Set,this.strapiEntries=new Map,this.strapiLongTextMap=new Map,this.capturedTexts=new Map,this.apiKeyStorageKey="ollang_browser_api_key",this.config={baseUrl:"http://localhost:5972",autoDetectCMS:!0,captureSelectors:["h1","h2","h3","h4","h5","h6","p","span","div","a","button","li","td","th","label"],excludeSelectors:["script","style","noscript",".no-translate","#ollang-debug-panel",".ollang-debug-panel",'[id^="ollang-"]','[class*="ollang-"]'],captureAttributes:["data-cms-field","data-cms-id","data-field-id"],debounceMs:2e3,onContentDetected:()=>{},...t},!this.config.apiKey&&"undefined"!=typeof window){const t=this.getStoredApiKey();t&&(this.config.apiKey=t)}this.selectedFolder=t.selectedFolder||"",this.init()}getStoredApiKey(){try{if("undefined"==typeof window)return null;const t=window.localStorage.getItem(this.apiKeyStorageKey);return t&&t.trim()?t:null}catch{return null}}saveApiKey(t){try{if("undefined"==typeof window)return;window.localStorage.setItem(this.apiKeyStorageKey,t)}catch{}}init(){if("undefined"==typeof window)throw new Error("OllangBrowser can only be used in browser environment");this.interceptApiCalls(),this.loadI18nFiles().then(()=>{console.log(`✅ Loaded ${this.i18nTexts.size} i18n texts from files`),this.detectFrameworkI18n(),setTimeout(()=>{console.log(`🔍 Starting capture with ${this.i18nTexts.size} i18n texts and ${this.strapiContentMap.size} Strapi contents tracked`),this.startCapture()},3e3)})}interceptApiCalls(){this.interceptFetch(),this.interceptXHR()}interceptFetch(){const t=this,e=window.fetch.bind(window);window.fetch=function(n,i){const s="string"==typeof n?n:n?.url||String(n);return e(n,i).then(e=>(t.isStrapiApiUrl(s)&&e.clone().json().then(e=>{t.processStrapiResponse(s,e)}).catch(()=>{}),e))}}interceptXHR(){const t=this,e=XMLHttpRequest.prototype.open,n=XMLHttpRequest.prototype.send;XMLHttpRequest.prototype.open=function(t,n,i,s,o){return this._ollangUrl="string"==typeof n?n:String(n),e.call(this,t,n,i??!0,s??null,o??null)},XMLHttpRequest.prototype.send=function(e){return this.addEventListener("load",function(){if(t.isStrapiApiUrl(this._ollangUrl))try{const e=JSON.parse(this.responseText);t.processStrapiResponse(this._ollangUrl,e)}catch(t){}}),n.call(this,e)}}isStrapiApiUrl(t){return!!t&&(!(!this.config.strapiUrl||!t.includes(this.config.strapiUrl))||[/\/api\/([\w-]+)(\?|\/|$)/,/cms\./,/strapi/i].some(e=>e.test(t)))}extractContentTypeFromUrl(t){const e=t.match(/\/api\/([\w-]+)/);return e?e[1]:null}processStrapiResponse(t,e){const n=this.extractContentTypeFromUrl(t);if(!n)return;try{const e=new URL(t,window.location.origin),n=e.origin!==window.location.origin?e.origin:"";n&&this.detectedStrapiUrls.add(n)}catch(t){}const i=e?.data;if(!i)return;const s=Array.isArray(i)?i:[i];for(const e of s){if(!e||!e.attributes)continue;const i=e.id;this.strapiEntries.set(`${n}:${i}`,{contentType:n,entryId:i,attributes:e.attributes,url:t}),this.extractStrapiFields(e.attributes,n,i,"")}console.log(`📦 Strapi [${n}]: captured ${s.length} entries, total tracked: ${this.strapiContentMap.size}`)}extractStrapiFields(t,e,i,s,o=0){if(t&&"object"==typeof t&&!(o>n.MAX_RECURSION_DEPTH))for(const a of Object.keys(t)){const r=t[a],l=s?`${s}.${a}`:a;if(!n.SKIP_RELATION_KEYS.has(a))if("string"==typeof r&&r.trim().length>=2){if(this.isNonTranslatableField(a,r))continue;const t=this.normalizeText(r);if(t.length<2)continue;if(t.length>n.MAX_FIELD_LENGTH||n.LONG_TEXT_FIELDS.has(a)){const n=`${e}:${i}:${l}`;this.strapiLongTextMap.set(n,{contentType:e,entryId:i,field:l,rawText:r}),this.extractParagraphsFromHtml(r,e,i,l),this.config.debug&&console.log(`📝 Stored long field ${l} (${t.length} chars) for API capture`);continue}this.strapiContentMap.set(t,{contentType:e,entryId:i,field:l,rawText:r})}else if(Array.isArray(r))r.forEach((t,s)=>{if("object"==typeof t&&null!==t)this.extractStrapiFields(t,e,i,`${l}[${s}]`,o+1);else if("string"==typeof t&&t.trim().length>=2){const o=this.normalizeText(t);o.length>=2&&o.length<=n.MAX_FIELD_LENGTH&&this.strapiContentMap.set(o,{contentType:e,entryId:i,field:`${l}[${s}]`,rawText:t})}});else if("object"==typeof r&&null!==r){if("formats"===a||"provider_metadata"===a)continue;if(this.isStrapiMediaObject(r)){this.extractStrapiMedia(r,e,i,l);continue}if("data"===a)continue;this.extractStrapiFields(r,e,i,l,o+1)}}}isStrapiMediaObject(t){if(!t||"object"!=typeof t)return!1;const e=t.data;if(!e)return!1;const n=Array.isArray(e)?e[0]:e;return!(!n?.attributes?.url||!n?.attributes?.mime)}extractStrapiMedia(t,e,n,i){const s=this.config.strapiUrl||[...this.detectedStrapiUrls][0]||"",o=Array.isArray(t.data)?t.data:[t.data];for(const t of o){if(!t?.attributes?.url)continue;const o=t.attributes,a=o.url,r=o.mime||"",l=r.startsWith("image/")||/\.(jpg|jpeg|png|gif|svg|webp|avif)(\?|$)/i.test(a),d=r.startsWith("video/")||/\.(mp4|webm|ogg|mov)(\?|$)/i.test(a);if(!l&&!d)continue;const c=a.startsWith("http")?a:`${s}${a}`,p={contentType:e,entryId:n,field:i,url:c,mime:r,alt:o.alternativeText||o.caption||o.name||void 0};this.strapiMediaMap.set(c,p),a.startsWith("http")||this.strapiMediaMap.set(a,p)}}extractParagraphsFromHtml(t,e,i,s){const o=t.split(/<\/p>|<br\s*\/?>|<\/h[1-6]>|<\/li>|<\/div>/i).map(t=>this.normalizeText(t)).filter(t=>t.length>=10);for(const t of o)t.length>n.MAX_FIELD_LENGTH||this.strapiContentMap.has(t)||this.strapiContentMap.set(t,{contentType:e,entryId:i,field:s,rawText:t})}isNonTranslatableField(t,e){return!!(n.SKIP_KEYS.has(t)||/^https?:\/\//.test(e)||/^\d{4}-\d{2}-\d{2}T/.test(e)||/^[a-f0-9]{16,}$/.test(e)||/^\.(jpg|jpeg|png|gif|svg|webp)$/i.test(e)||/^#[0-9a-fA-F]{3,8}$/.test(e)||"fullName"===t||"firstName"===t||"lastName"===t||/^image\//.test(e)||/^video\//.test(e))}normalizeText(t){return t?t.replace(/<[^>]*>/g," ").replace(/&nbsp;/g," ").replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&quot;/g,'"').replace(/&#39;/g,"'").replace(/\s+/g," ").trim():""}findStrapiMatch(t){if(!t||0===this.strapiContentMap.size)return null;const e=this.normalizeText(t);if(e.length<3)return null;const n=this.strapiContentMap.get(e);if(n)return n;let i=null,s=0;for(const[t,n]of this.strapiContentMap){const o=e.length,a=t.length;if(!(a<10||o<10)){if(o>=a&&e.includes(t)){const t=a/o;t>s&&t>.6&&(s=t,i=n)}if(a>=o&&t.includes(e)){const t=o/a;t>s&&t>.5&&(s=t,i=n)}if(o>30&&a>30){const r=Math.min(o,a,200);if(e.substring(0,r)===t.substring(0,r)){const t=r/Math.max(o,a);t>s&&t>.5&&(s=t,i=n)}}}}return i&&s>.5?i:null}isI18nText(t){return!!this.i18nTexts.has(t)||!!this.i18nTexts.has(t.trim())||this.i18nNormalized.has(this.normalizeText(t))}detectFrameworkI18n(){if(setTimeout(()=>{try{const t=this.getAngularTranslations();t&&(this.extractTextsFromObject(t),console.log("✅ Loaded Angular translations from runtime"))}catch(t){console.warn("Could not auto-detect Angular translations:",t)}},1500),window.i18next){const t=window.i18next;if(t.store&&t.language){const e=t.store.data[t.language];e&&(this.extractTextsFromObject(e),console.log("✅ Loaded React i18next translations"))}}if(window.__VUE_I18N__){const t=window.__VUE_I18N__;t.messages&&(Object.values(t.messages).forEach(t=>{this.extractTextsFromObject(t)}),console.log("✅ Loaded Vue i18n translations"))}window.__NEXT_DATA__?.props?.pageProps?.messages&&(this.extractTextsFromObject(window.__NEXT_DATA__.props.pageProps.messages),console.log("✅ Loaded Next.js translations"))}getAngularTranslations(){if(window.ng&&window.ng.probe){const t=document.querySelector("app-root");if(t)try{const e=window.ng.probe(t);if(e?.injector){const t=e.injector.get("TranslateService");if(t?.translations)return t.translations}}catch(t){}}if(window.__ANGULAR_TRANSLATIONS__)return window.__ANGULAR_TRANSLATIONS__;try{const t=localStorage.getItem("translations")||sessionStorage.getItem("translations");if(t)return JSON.parse(t)}catch(t){}return null}async loadI18nFiles(){if(!this.config.i18nFiles||0===this.config.i18nFiles.length){const t=["/assets/i18n/","/locales/","/i18n/","/translations/","/messages/"],e=["en","tr","de","es","fr","it","pt","ru","ja","zh","ko","ar","kr","da","fi","nb","nl","sv"];for(const n of t)for(const t of e){const e=`${n}${t}.json`;try{const t=await fetch(e);if(t.ok){const n=await t.json();this.extractTextsFromObject(n),console.log(`✅ Auto-loaded i18n: ${e}`)}}catch(t){}}return}for(const t of this.config.i18nFiles)try{const e=await fetch(t),n=await e.json();this.extractTextsFromObject(n)}catch(e){console.warn(`Failed to load i18n file: ${t}`,e)}}extractTextsFromObject(t){for(const e in t){const n=t[e];"string"==typeof n?(this.i18nTexts.add(n),this.i18nTexts.add(n.trim()),this.i18nNormalized.add(this.normalizeText(n))):"object"==typeof n&&null!==n&&this.extractTextsFromObject(n)}}startCapture(){this.config.autoDetectCMS&&this.detectCMS(),this.startObserving(),this.scanPage()}detectCMS(){(window.__CONTENTFUL_SPACE_ID__||document.querySelector("[data-contentful-entry-id]"))&&(this.config.cmsType="contentful"),(window.__STRAPI__||document.querySelector("[data-strapi-field]"))&&(this.config.cmsType="strapi"),(window.__SANITY__||document.querySelector("[data-sanity]"))&&(this.config.cmsType="sanity"),(document.body.classList.contains("wordpress")||window.wp)&&(this.config.cmsType="wordpress"),!this.config.cmsType&&this.strapiContentMap.size>0&&(this.config.cmsType="strapi"),this.detectedStrapiUrls.size>0&&(this.config.cmsType="strapi",this.config.strapiUrl||(this.config.strapiUrl=[...this.detectedStrapiUrls][0]))}startObserving(){this.observer=new MutationObserver(t=>{this.handleMutations(t)}),this.observer.observe(document.body,{childList:!0,subtree:!0,characterData:!0,attributes:!0,attributeFilter:this.config.captureAttributes})}handleMutations(t){const e=new Set;t.forEach(t=>{"childList"===t.type?t.addedNodes.forEach(t=>e.add(t)):"characterData"!==t.type&&"attributes"!==t.type||e.add(t.target)}),e.forEach(t=>{if(t.nodeType===Node.ELEMENT_NODE){const e=t;this.scanElement(e),e.querySelectorAll&&this.config.captureSelectors.forEach(t=>{try{e.querySelectorAll(t).forEach(t=>this.scanElement(t))}catch(t){}})}})}captureApiContent(){for(const[,t]of this.strapiLongTextMap){const e=this.generateId(`api:${t.contentType}:${t.entryId}`,t.field);if(this.capturedContent.has(e))continue;const n=`${t.contentType}:${t.entryId}`,i=this.strapiEntries.get(n),s=i?.attributes?.route,o={id:e,text:t.rawText,type:"cms",selector:`api://${t.contentType}/${t.entryId}/${t.field}`,xpath:"",tagName:"richtext",attributes:{},url:window.location.href,timestamp:Date.now(),cmsType:"strapi",cmsField:t.field,cmsId:String(t.entryId),strapiContentType:t.contentType,strapiEntryId:t.entryId,strapiField:t.field,strapiRoute:s};this.capturedContent.set(e,o),this.config.onContentDetected([o])}for(const[,t]of this.strapiEntries){const e=t.attributes?.route;if(e)for(const[,n]of this.capturedContent)n.strapiContentType!==t.contentType||n.strapiEntryId!==t.entryId||n.strapiRoute||(n.strapiRoute=e)}}scanPage(){this.config.captureSelectors.forEach(t=>{try{document.querySelectorAll(t).forEach(t=>this.scanElement(t))}catch(t){}}),this.scanMediaElements(),this.captureApiContent()}scanMediaElements(){if(0===this.strapiMediaMap.size)return;const t=["img[src]","video[src]","source[src]","video[poster]"];for(const e of t)document.querySelectorAll(e).forEach(t=>{if(t.closest("#ollang-debug-panel"))return;const n=e.endsWith("[poster]")?t.poster:t.src||t.getAttribute("src")||"";if(!n)return;let i=this.strapiMediaMap.get(n);if(!i)try{const t=new URL(n).pathname;i=this.strapiMediaMap.get(t)}catch{i=this.strapiMediaMap.get(n)}if(!i)return;const s=this.generateId(n,i.contentType+i.entryId);if(this.capturedContent.has(s))return;const o=(i.mime||"").startsWith("video/")||/\.(mp4|webm|ogg|mov)/i.test(n)?"video":"image",a={id:s,text:i.alt||i.field,type:"cms",selector:this.generateSelector(t),xpath:this.generateXPath(t),tagName:t.tagName.toLowerCase(),attributes:this.extractAttributes(t),url:window.location.href,timestamp:Date.now(),cmsType:"strapi",cmsField:i.field,cmsId:String(i.entryId),strapiContentType:i.contentType,strapiEntryId:i.entryId,strapiField:i.field,mediaUrl:i.url,mediaType:o,mediaAlt:i.alt};this.capturedContent.set(s,a),this.config.onContentDetected([a])})}scanElement(t){if(this.config.excludeSelectors.some(e=>{try{return t.matches(e)}catch(t){return!1}}))return;if(t.closest("#ollang-debug-panel"))return;const e=this.getDirectText(t);if(!e||e.trim().length<3)return;const n=e.trim();if(/^\d+$/.test(n))return;if(n.length<5&&!n.includes(" "))return;if(this.excludedTexts.has(n))return;const i=this.normalizeText(n);if(this.capturedTexts.has(i))return;const s=this.findStrapiMatch(n);if(s){if("description"===s.field)return void(this.config.debug&&console.log(`ℹ️ Skipping DOM capture for long field ${s.contentType}/${s.entryId}/${s.field}`));const e=this.createCapturedContent(t,n,s);return void(this.capturedContent.has(e.id)||(this.capturedContent.set(e.id,e),this.capturedTexts.set(i,e.id),this.config.onContentDetected([e]),this.config.debug&&console.log(`✅ CMS [${s.contentType}/${s.entryId}/${s.field}]: "${n.substring(0,60)}..."`)))}if(t.hasAttribute("data-cms")||t.hasAttribute("data-cms-field")||t.hasAttribute("data-strapi-field")||t.hasAttribute("data-strapi-component")||t.hasAttribute("data-contentful-entry-id")||t.hasAttribute("data-contentful-field-id")||t.hasAttribute("data-sanity")||t.hasAttribute("data-sanity-edit-target")){const e=this.createCapturedContent(t,n,null);return void(this.capturedContent.has(e.id)||(this.capturedContent.set(e.id,e),this.capturedTexts.set(i,e.id),this.config.onContentDetected([e])))}if(this.i18nTexts.size>0&&this.strapiContentMap.size>0&&!this.isI18nText(n)){if(this.isLikelyStaticContent(t,n))return;const e=this.createCapturedContent(t,n,null);e.type="dynamic-unmatched",this.capturedContent.has(e.id)||(this.capturedContent.set(e.id,e),this.capturedTexts.set(i,e.id),this.config.onContentDetected([e]))}}getDirectText(t){let e="";for(const n of Array.from(t.childNodes))n.nodeType===Node.TEXT_NODE&&(e+=n.textContent);return e.trim()?e.trim():0===t.children.length?(t.textContent||"").trim():""}isLikelyStaticContent(t,e){return"A"===t.tagName&&e.length<30||"BUTTON"===t.tagName&&e.length<30||!(!t.closest("footer")&&!t.closest("header, nav"))||!!/^(©|\||\+|→|←|×|✓|✗|▶|•)/.test(e)||!!/©\s*\d{4}/.test(e)}createCapturedContent(t,e,n){const i=this.generateSelector(t),s=this.generateXPath(t),o=this.extractCMSField(t),a=this.extractCMSId(t),r={id:this.generateId(i,e),text:e,type:n||o||a?"cms":"dynamic",selector:i,xpath:s,tagName:t.tagName.toLowerCase(),attributes:this.extractAttributes(t),url:window.location.href,timestamp:Date.now()};return this.config.cmsType&&(r.cmsType=this.config.cmsType),n?(r.cmsType="strapi",r.strapiContentType=n.contentType,r.strapiEntryId=n.entryId,r.strapiField=n.field,r.cmsField=n.field,r.cmsId=String(n.entryId)):(o&&(r.cmsField=o),a&&(r.cmsId=a)),r}extractCMSField(t){for(const e of this.config.captureAttributes){const n=t.getAttribute(e);if(n)return n}return t.getAttribute("data-cms-field")||t.getAttribute("data-contentful-field-id")||t.getAttribute("data-strapi-field")||t.getAttribute("data-sanity-edit-target")||void 0}extractCMSId(t){return t.getAttribute("data-cms-id")||t.getAttribute("data-contentful-entry-id")||t.getAttribute("data-strapi-id")||t.getAttribute("data-sanity-document-id")||void 0}extractAttributes(t){const e={};return Array.from(t.attributes).forEach(t=>{t.name.startsWith("data-")&&(e[t.name]=t.value)}),e}generateSelector(t){if(t.id)return`#${t.id}`;const e=[];let n=t;for(;n&&n!==document.body;){let t=n.tagName.toLowerCase();if(n.className){const e=Array.from(n.classList).filter(t=>!t.startsWith("ng-")&&!t.startsWith("tw-")).slice(0,2);e.length&&(t+="."+e.join("."))}e.unshift(t),n=n.parentElement}return e.join(" > ")}generateXPath(t){if(t.id)return`//*[@id="${t.id}"]`;const e=[];let n=t;for(;n&&n!==document.body;){let t=1,i=n.previousElementSibling;for(;i;)i.tagName===n.tagName&&t++,i=i.previousElementSibling;e.unshift(`${n.tagName.toLowerCase()}[${t}]`),n=n.parentElement}return"/"+e.join("/")}generateId(t,e){const n=t+e;let i=0;for(let t=0;t<n.length;t++)i=(i<<5)-i+n.charCodeAt(t),i&=i;return Math.abs(i).toString(36)}capture(){return this.scanPage(),this.groupCmsEntries(),Array.from(this.capturedContent.values())}groupCmsEntries(){const t=new Map,e=[];for(const[,n]of this.capturedContent){const i=n.strapiContentType&&null!=n.strapiEntryId,s=!!n.mediaUrl;if(i&&!s){const e=`${n.strapiContentType}:${n.strapiEntryId}`;t.has(e)||t.set(e,{items:[],entryData:this.strapiEntries.get(e)}),t.get(e).items.push(n)}else e.push(n)}for(const[e,n]of t){const[t,i]=e.split(":"),s=Number(i);if(!n.items.some(t=>"description"===t.strapiField)){const e=`${t}:${s}:description`,i=this.strapiLongTextMap.get(e);i&&n.items.push({id:`api-desc-${t}-${s}`,text:i.rawText,type:"cms",selector:`api://${t}/${s}/description`,xpath:"",tagName:"richtext",attributes:{},url:window.location.href,timestamp:Date.now(),cmsType:"strapi",cmsField:"description",cmsId:String(s),strapiContentType:t,strapiEntryId:s,strapiField:"description"})}}this.capturedContent.clear();for(const[e,n]of t){const[t,i]=e.split(":"),s=Number(i),o=n.items.find(t=>t.strapiField?.includes("title"))||n.items[0],a=n.entryData?.attributes?.route||n.items.find(t=>t.strapiRoute)?.strapiRoute||void 0,r={};for(const t of n.items)t.strapiField&&(r[t.strapiField]=t.text);const l=`cms-entry-${t}-${s}`,d=Object.keys(r).length,c={...o,id:l,text:o.text,tagName:`entry:${d} fields`,strapiRoute:a,cmsFields:r};this.capturedContent.set(l,c)}for(const t of e)this.capturedContent.set(t.id,t)}getCapturedContent(){return Array.from(this.capturedContent.values())}getCmsContent(){return Array.from(this.capturedContent.values()).filter(t=>"cms"===t.type)}getStrapiMetadata(){return{trackedTexts:this.strapiContentMap.size,entries:this.strapiEntries.size,detectedUrls:[...this.detectedStrapiUrls],cmsType:this.config.cmsType}}clear(){this.capturedContent.clear(),this.capturedTexts.clear()}destroy(){this.observer&&(this.observer.disconnect(),this.observer=null);const t=document.getElementById("ollang-debug-panel");t&&t.remove()}addI18nTexts(t){const e=this.i18nTexts.size;Array.isArray(t)?t.forEach(t=>{this.i18nTexts.add(t),this.i18nTexts.add(t.trim()),this.i18nNormalized.add(this.normalizeText(t))}):this.extractTextsFromObject(t);const n=this.i18nTexts.size-e;console.log(`✅ Added ${n} new i18n texts (total: ${this.i18nTexts.size})`),n>0&&this.capturedContent.size>0&&(this.clear(),this.scanPage())}getI18nTextsCount(){return this.i18nTexts.size}getEntryRoutes(){const t={};for(const[e,n]of this.strapiEntries)n.attributes?.route&&(t[e]=n.attributes.route);return t}async showDebugPanel(){if(document.getElementById("ollang-debug-panel"))return;const t=this.createDebugPanel();if(document.body.appendChild(t),this.config.apiKey)try{if(!await this.validateApiKey())return void this.showApiKeyFormInPanel();this.showPanelContent()}catch(t){console.error("Failed to validate API key:",t),this.showApiKeyFormInPanel()}else this.showApiKeyFormInPanel()}async validateApiKey(){try{const t=(this.config.baseUrl||"").replace(/\/$/,"");if(!t)return!1;const e=await fetch(`${t}/scans/folders`,{headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey}});if(!e.ok)return!1;const n=await e.json(),i=Array.isArray(n)?n:n.folders;return i&&Array.isArray(i)&&(this.folders=i,!this.selectedFolder&&i.length>0&&(this.selectedFolder=i[0].name)),!0}catch(t){return!1}}showApiKeyFormInPanel(){const t=document.getElementById("ollang-panel-content");if(!t)return;t.innerHTML='\n <div style="padding: 20px;">\n <h3 style="margin: 0 0 15px 0; font-size: 16px; color: #333;">Ollang API Key Required</h3>\n <p style="margin: 0 0 20px 0; color: #666; font-size: 13px; line-height: 1.5;">\n Enter your Ollang API key for this app. Not the same as any Strapi token.\n </p>\n <div style="margin-bottom: 15px;">\n <label style="display: block; margin-bottom: 6px; font-weight: 500; color: #333; font-size: 13px;">Ollang API Key</label>\n <input type="text" id="ollang-apikey-input" placeholder="Ollang API key"\n style="width: 100%; padding: 8px 10px; border: 2px solid #ddd; border-radius: 4px; font-size: 13px; box-sizing: border-box; font-family: monospace;" />\n </div>\n <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 15px;">\n <div id="ollang-status-indicator" style="width: 10px; height: 10px; border-radius: 50%; background: #ccc; transition: background-color 0.3s;"></div>\n <span id="ollang-status-text" style="font-size: 12px; color: #666;">Not validated</span>\n </div>\n <button id="ollang-validate-btn" class="ollang-btn" style="width: 100%;">Validate & Continue</button>\n </div>\n ';const e=document.getElementById("ollang-apikey-input"),n=document.getElementById("ollang-validate-btn"),i=document.getElementById("ollang-status-indicator"),s=document.getElementById("ollang-status-text"),o=async()=>{const t=e.value.trim();if(!t)return void this.showStatus("Please enter Ollang API key","error");n.disabled=!0,n.textContent="Validating...",i.style.background="#ffc107",s.textContent="Validating...",s.style.color="#ffc107";const o=this.config.apiKey;this.config.apiKey=t;try{await this.validateApiKey()?(this.saveApiKey(t),i.style.background="#28a745",s.textContent="Valid API key ✓",s.style.color="#28a745",await new Promise(t=>setTimeout(t,500)),this.showPanelContent()):(i.style.background="#dc3545",s.textContent="Invalid API key ✗",s.style.color="#dc3545",this.config.apiKey=o,n.disabled=!1,n.textContent="Validate & Continue")}catch(t){i.style.background="#dc3545",s.textContent="Validation failed ✗",s.style.color="#dc3545",this.config.apiKey=o,n.disabled=!1,n.textContent="Validate & Continue"}};n.addEventListener("click",o),e.addEventListener("keypress",t=>{"Enter"===t.key&&o()}),setTimeout(()=>e.focus(),100)}showPanelContent(){const t=document.getElementById("ollang-panel-content");if(!t)return;t.innerHTML="";const e=document.createElement("div");e.id="ollang-stats",e.style.cssText="padding: 15px; border-bottom: 1px solid #ddd; background: #f8f9fa;",this.updateStats(e);const n=document.createElement("div");n.id="ollang-status",n.style.cssText="padding: 10px 15px; border-bottom: 1px solid #ddd; background: #e7f3ff; font-size: 12px; display: none;",n.innerHTML='<span id="ollang-status-text">Ready</span><button id="ollang-status-close" style="float: right; background: none; border: none; cursor: pointer; font-size: 14px;">&times;</button>';const i=document.createElement("div");i.style.cssText="padding: 12px 16px; border-bottom: 1px solid #e2e8f0; display: flex; flex-direction: column; gap: 10px;",i.innerHTML='\n <div style="display: flex; gap: 8px;">\n <button id="ollang-capture" class="ollang-btn">Capture</button>\n <button id="ollang-clear" class="ollang-btn ollang-btn-ghost">Clear</button>\n </div>\n <div style="display: flex; align-items: flex-end; justify-content: space-between; gap: 12px;">\n <div style="display: flex; flex-direction: column; gap: 4px; flex: 1;">\n <span style="font-size: 11px; font-weight: 500; color: #64748b;">Folder</span>\n <div style="display: flex; gap: 6px; align-items: center;">\n <div id="ollang-folder-dropdown" class="ollang-folder-dropdown">\n <button id="ollang-folder-trigger" type="button" class="ollang-folder-trigger">\n <span id="ollang-folder-label" class="ollang-folder-label">Select folder...</span>\n <span class="ollang-folder-arrow">▾</span>\n </button>\n <div id="ollang-folder-menu" class="ollang-folder-menu"></div>\n </div>\n <button id="ollang-new-folder" class="ollang-btn-sm">+ New</button>\n </div>\n </div>\n <button id="ollang-push-tms" class="ollang-btn ollang-btn-primary">Push to Ollang</button>\n </div>\n <div id="ollang-strapi-schema-block" style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #e2e8f0;">\n <span style="font-size: 11px; font-weight: 500; color: #64748b;">Strapi schema (optional)</span>\n <p style="margin: 4px 0 8px 0; font-size: 11px; color: #94a3b8;">Fetch schema here so Push uses Content-Type Builder fields. Use your Strapi API token (not the Ollang API token).</p>\n <div style="display: flex; flex-direction: column; gap: 6px;">\n <input type="text" id="ollang-strapi-url" placeholder="Strapi URL (e.g. https://api.example.com)" style="width: 100%; padding: 6px 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; box-sizing: border-box;" />\n <input type="password" id="ollang-strapi-jwt" placeholder="Strapi Admin JWT token" style="width: 100%; padding: 6px 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; box-sizing: border-box;" />\n <button id="ollang-fetch-schema" class="ollang-btn-sm">Fetch schema</button>\n </div>\n </div>\n ';const s=document.createElement("div");s.id="ollang-selection-info",s.style.cssText="padding: 8px 16px; border-bottom: 1px solid #e2e8f0; background: #f8fafc; font-size: 12px; display: none;",s.innerHTML='\n <div class="ollang-selection-bar">\n <div class="ollang-selection-count">\n <span class="ollang-selection-dot"></span>\n <span id="ollang-selected-count">0</span>\n <span class="ollang-selection-label">items selected</span>\n </div>\n <div class="ollang-selection-actions">\n <button id="ollang-select-all" class="ollang-btn ollang-btn-ghost">Select All</button>\n <button id="ollang-deselect-all" class="ollang-btn ollang-btn-ghost">Deselect All</button>\n <button id="ollang-select-cms-only" class="ollang-btn ollang-selection-cms">Select CMS Only</button>\n </div>\n </div>\n ';const o=document.createElement("div");o.id="ollang-content-list",o.style.cssText="flex: 1; overflow-y: auto; padding: 15px;",this.loadFolders(),t.appendChild(e),t.appendChild(n),t.appendChild(i),t.appendChild(s),t.appendChild(o),setTimeout(()=>{document.getElementById("ollang-capture")?.addEventListener("click",()=>{this.capture(),this.updateStats(e),this.showContent(o),this.showStatus(`Captured ${this.capturedContent.size} items (${this.getCmsContent().length} CMS)`,"success")}),document.getElementById("ollang-clear")?.addEventListener("click",()=>{this.clear(),this.updateStats(e),o.innerHTML='<p style="color: #999; text-align: center;">No content captured</p>',this.showStatus("Cleared all content","success")}),document.getElementById("ollang-select-all")?.addEventListener("click",()=>{Array.from(this.capturedContent.values()).forEach(t=>this.selectedContentIds.add(t.id)),this.showContent(o)}),document.getElementById("ollang-deselect-all")?.addEventListener("click",()=>{this.selectedContentIds.clear(),this.showContent(o)}),document.getElementById("ollang-select-cms-only")?.addEventListener("click",()=>{this.selectedContentIds.clear(),this.getCmsContent().forEach(t=>this.selectedContentIds.add(t.id)),this.showContent(o)}),document.getElementById("ollang-push-tms")?.addEventListener("click",()=>this.pushToOllang()),document.getElementById("ollang-fetch-schema")?.addEventListener("click",()=>this.fetchStrapiSchemaInPanel());const t=document.getElementById("ollang-strapi-url");t&&!t.value&&(t.value=this.config.strapiUrl||[...this.detectedStrapiUrls][0]||""),this.updateFolderOptions();const n=document.getElementById("ollang-folder-dropdown"),i=document.getElementById("ollang-folder-trigger"),s=document.getElementById("ollang-folder-menu");if(i&&s&&n){const t=t=>{t??"true"!==s.getAttribute("data-open")?(s.style.display="block",s.setAttribute("data-open","true")):(s.style.display="none",s.setAttribute("data-open","false"))};i.addEventListener("click",e=>{e.stopPropagation(),t()}),document.addEventListener("click",e=>{n.contains(e.target)||t(!1)})}document.getElementById("ollang-new-folder")?.addEventListener("click",()=>this.showNewFolderDialog()),document.getElementById("ollang-status-close")?.addEventListener("click",()=>this.hideStatus())},0),setInterval(()=>this.updateStats(e),2e3)}createDebugPanel(){const t=document.createElement("div");t.id="ollang-debug-panel",t.style.cssText=["position: fixed","right: 20px","left: auto","transform: none","bottom: 0","width: min(540px, 100% - 40px)","min-height: 320px","max-height: 90vh","background: #ffffff","border-radius: 12px 12px 0 0","border: 1px solid rgba(15, 23, 42, 0.12)","box-shadow: 0 18px 45px rgba(15, 23, 42, 0.25)","z-index: 999999",'font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',"display: flex","flex-direction: column","overflow: hidden","backdrop-filter: blur(10px)","-webkit-backdrop-filter: blur(10px)","background-clip: padding-box"].join("; ")+";";const e=document.createElement("div");e.style.cssText=["height: 6px","cursor: ns-resize","display: flex","align-items: center","justify-content: center","background: transparent"].join("; ")+";";const n=document.createElement("div");n.style.cssText="width: 36px; height: 3px; border-radius: 999px; background: rgba(148, 163, 184, 0.95);",e.appendChild(n);let i=!1,s=0,o=0;const a=e=>{if(!i)return;const n=s-e.clientY,a=Math.min(Math.max(o+n,140),Math.round(.7*window.innerHeight));t.style.height=`${a}px`},r=()=>{i&&(i=!1,document.removeEventListener("mousemove",a),document.removeEventListener("mouseup",r))};e.addEventListener("mousedown",e=>{i=!0,s=e.clientY,o=t.getBoundingClientRect().height,document.addEventListener("mousemove",a),document.addEventListener("mouseup",r)});const l=document.createElement("div");l.style.cssText=["padding: 10px 16px","border-bottom: 1px solid rgba(148, 163, 184, 0.25)","display: flex","justify-content: space-between","align-items: center","background: #ffffff","border-radius: 12px 12px 0 0","color: #0f172a","box-shadow: 0 1px 0 rgba(15, 23, 42, 0.04)"].join("; ")+";",l.innerHTML='\n <div style="display: flex; align-items: center; gap: 10px;">\n <div style="width: 32px; height: 32px; border-radius: 999px; background: #ffffff; display: flex; align-items: center; justify-content: center; box-shadow: 0 0 0 1px rgba(148, 163, 184, 0.45); padding: 4px;">\n <svg viewBox="0 0 37 32" xmlns="http://www.w3.org/2000/svg" style="width: 26px; height: 22px; display: block;">\n <path d="M35.8246 10.862C34.5972 11.5165 33.2999 12.0249 31.9585 12.3772C30.4527 5.10884 24.8838 0.0517578 18.3428 0.0517578H18.2347C15.3756 0.184498 13.2599 1.58635 12.4149 3.89149C11.2871 6.96393 12.7167 11.1501 15.9666 14.3132C18.6573 16.9259 22.7585 18.4605 26.9677 18.4378C26.2857 21.1303 24.6634 23.4766 22.405 25.037C20.1466 26.5973 17.4072 27.2645 14.7005 26.9134C11.9939 26.5622 9.50584 25.2168 7.70306 23.1296C5.90027 21.0423 4.90653 18.3565 4.90817 15.5759C4.90817 12.9858 6.04543 9.13633 9.25081 6.75996L9.56849 6.52687V0.699269L9.28261 0.854665C8.27975 1.42954 7.30632 2.0563 6.36626 2.73246C1.67098 6.21284 0.0126953 11.6552 0.0126953 15.592C0.0174583 19.7867 1.59692 23.8205 4.427 26.8662C7.25707 29.9119 11.1233 31.7386 15.2329 31.9718C19.3424 32.2049 23.3837 30.8267 26.528 28.12C29.6723 25.4132 31.6812 21.583 32.1427 17.4148C32.5049 17.282 33.0036 17.1428 33.5278 16.9939C34.4967 16.7187 35.4973 16.4338 36.0247 16.1133L36.1168 16.0583V10.7325L35.8246 10.862ZM27.1297 13.4326C24.7312 13.4746 21.4972 12.7851 19.3529 10.6968C17.504 8.89676 16.6495 6.63372 17.0085 5.64626C17.1705 5.21243 17.8598 5.08294 18.3999 5.05056C21.9642 5.0797 26.1639 8.21686 27.1297 13.4326Z" fill="#6148f9" />\n </svg>\n </div>\n <div style="display: flex; flex-direction: column;">\n <span style="font-size: 13px; font-weight: 600; color: #0f172a; letter-spacing: 0.02em;">Ollang</span>\n <span style="font-size: 11px; font-weight: 500; color: #64748b;">CMS Detect</span>\n </div>\n </div>\n <button id="ollang-close"\n style="background: #f8fafc; border-radius: 999px; border: 1px solid rgba(148, 163, 184, 0.6); width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; color: #0f172a; cursor: pointer; font-size: 18px; line-height: 1; padding: 0;">\n ×\n </button>\n ';const d=document.createElement("div");d.id="ollang-panel-content",d.style.cssText="flex: 1; overflow-y: auto; display: flex; flex-direction: column;";const c=document.createElement("style");return c.textContent="\n .ollang-btn {\n padding: 6px 12px;\n border-radius: 6px;\n border: 1px solid #e2e8f0;\n background: #ffffff;\n color: #0f172a;\n cursor: pointer;\n font-size: 12px;\n font-weight: 500;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 4px;\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);\n }\n .ollang-btn:hover {\n background: #f8fafc;\n border-color: #cbd5f5;\n }\n .ollang-btn:disabled {\n background: #f8fafc;\n border-color: #e2e8f0;\n color: #94a3b8;\n cursor: not-allowed;\n box-shadow: none;\n }\n .ollang-btn-primary {\n background: #1d4ed8;\n border-color: #1d4ed8;\n color: #ffffff;\n }\n .ollang-btn-primary:hover {\n background: #1e40af;\n border-color: #1e40af;\n }\n .ollang-btn-ghost {\n background: #f8fafc;\n border-color: #e2e8f0;\n color: #0f172a;\n }\n .ollang-btn-link {\n background: none;\n border: none;\n color: #2563eb;\n cursor: pointer;\n font-size: 11px;\n text-decoration: underline;\n padding: 0;\n }\n .ollang-btn-sm {\n padding: 4px 10px;\n background: #0f172a;\n color: #ffffff;\n border-radius: 999px;\n border: none;\n cursor: pointer;\n font-size: 11px;\n font-weight: 500;\n }\n .ollang-btn-sm:hover {\n background: #020617;\n }\n .ollang-selection-bar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n }\n .ollang-selection-count {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n color: #0f172a;\n }\n .ollang-selection-dot {\n width: 8px;\n height: 8px;\n border-radius: 999px;\n background: #22c55e;\n box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.25);\n }\n .ollang-selection-label {\n font-size: 11px;\n color: #64748b;\n }\n .ollang-selection-actions {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n }\n .ollang-selection-cms {\n color: #16a34a;\n }\n .ollang-folder-dropdown {\n position: relative;\n min-width: 220px;\n }\n .ollang-folder-trigger {\n width: 100%;\n padding: 6px 10px;\n border-radius: 999px;\n border: 1px solid #e2e8f0;\n background: #ffffff;\n color: #0f172a;\n font-size: 12px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n cursor: pointer;\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);\n }\n .ollang-folder-trigger:hover {\n border-color: #cbd5f5;\n background: #f8fafc;\n }\n .ollang-folder-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .ollang-folder-arrow {\n font-size: 10px;\n color: #94a3b8;\n margin-left: 6px;\n }\n .ollang-folder-menu {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n right: 0;\n max-height: 200px;\n overflow-y: auto;\n background: #ffffff;\n border-radius: 12px;\n border: 1px solid #e2e8f0;\n box-shadow: 0 10px 25px rgba(15, 23, 42, 0.15);\n padding: 4px;\n display: none;\n z-index: 10;\n }\n .ollang-folder-option {\n width: 100%;\n text-align: left;\n padding: 6px 8px;\n border-radius: 8px;\n border: none;\n background: transparent;\n font-size: 12px;\n color: #0f172a;\n cursor: pointer;\n }\n .ollang-folder-option:hover {\n background: #eff6ff;\n }\n .ollang-folder-option-active {\n background: #1d4ed8;\n color: #ffffff;\n }\n .ollang-content-item {\n background: #f8fafc;\n padding: 10px;\n margin: 6px 0;\n border-radius: 8px;\n font-size: 12px;\n border: 1px solid #e2e8f0;\n display: flex;\n gap: 10px;\n align-items: flex-start;\n }\n .ollang-content-item.cms-matched {\n border-color: #22c55e;\n background: #f0fdf4;\n }\n .ollang-content-item.selected {\n background: #eff6ff;\n border-color: #2563eb;\n }\n .ollang-content-checkbox {\n margin-top: 2px;\n cursor: pointer;\n width: 16px;\n height: 16px;\n }\n .ollang-content-body {\n flex: 1;\n }\n .ollang-content-text {\n font-weight: 600;\n margin-bottom: 5px;\n color: #0f172a;\n }\n .ollang-content-meta {\n color: #64748b;\n font-size: 11px;\n }\n .ollang-cms-badge {\n display: inline-block;\n padding: 1px 6px;\n border-radius: 999px;\n font-size: 10px;\n font-weight: 600;\n }\n .ollang-badge-cms {\n background: #dcfce7;\n color: #15803d;\n }\n .ollang-badge-dynamic {\n background: #fef9c3;\n color: #854d0e;\n }\n .ollang-badge-unmatched {\n background: #fee2e2;\n color: #b91c1c;\n }\n #ollang-apikey-input:focus {\n outline: none;\n border-color: #2563eb;\n }\n .ollang-badge-image {\n background: #ede9fe;\n color: #6d28d9;\n }\n .ollang-badge-video {\n background: #fce7f3;\n color: #be185d;\n }\n .ollang-media-item {\n align-items: center;\n }\n .ollang-media-preview {\n width: 52px;\n height: 40px;\n border-radius: 6px;\n object-fit: cover;\n flex-shrink: 0;\n border: 1px solid #e2e8f0;\n background: #f1f5f9;\n }\n .ollang-media-video-thumb {\n width: 52px;\n height: 40px;\n border-radius: 6px;\n background: #0f172a;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n border: 1px solid #e2e8f0;\n }\n .ollang-media-play {\n color: #ffffff;\n font-size: 14px;\n opacity: 0.9;\n }\n ",t.appendChild(c),t.appendChild(e),t.appendChild(l),t.appendChild(d),setTimeout(()=>{document.getElementById("ollang-close")?.addEventListener("click",()=>t.remove())},0),t}updateStats(t){if(!t)return;const e=this.capturedContent.size,n=this.getCmsContent().length,i=Array.from(this.capturedContent.values()).filter(t=>!!t.mediaUrl),s=i.filter(t=>"image"===t.mediaType).length,o=i.filter(t=>"video"===t.mediaType).length;t.innerHTML=`\n <div style="display: flex; flex-direction: column; gap: 2px;">\n <div style="font-size: 12px; font-weight: 600; color: #0f172a;">\n ${e} captured\n <span style="font-weight: 400; color: #64748b; margin-left: 4px;">(${n} text · ${s} image · ${o} video)</span>\n </div>\n <div style="font-size: 11px; color: #94a3b8;">\n ${this.config.cmsType||"Auto-detect"}${this.config.strapiUrl?" · "+this.config.strapiUrl:""}\n · tracked: ${this.strapiContentMap.size} texts, ${this.strapiMediaMap.size} media\n </div>\n </div>\n `}showContent(t){const e=Array.from(this.capturedContent.values());if(0===e.length)return t.innerHTML='<p style="color: #999; text-align: center;">No content captured yet</p>',void this.updateSelectionInfo();e.sort((t,e)=>"cms"===t.type&&"cms"!==e.type?-1:"cms"!==t.type&&"cms"===e.type?1:e.text.length-t.text.length),t.innerHTML=e.map(t=>{const e=this.selectedContentIds.has(t.id),n="cms"===t.type,i=n?"ollang-badge-cms":"dynamic-unmatched"===t.type?"ollang-badge-unmatched":"ollang-badge-dynamic",s=n?"CMS":"dynamic-unmatched"===t.type?"Unmatched":"Dynamic";if(t.mediaUrl){const i="video"===t.mediaType,o=i?"Video":"Image",a=i?"ollang-badge-video":"ollang-badge-image",r=i?'<div class="ollang-media-preview ollang-media-video-thumb">\n <span class="ollang-media-play">▶</span>\n </div>':`<img class="ollang-media-preview" src="${this.escapeHtml(t.mediaUrl)}" alt="${this.escapeHtml(t.mediaAlt||"")}" loading="lazy" />`;return`<div class="ollang-content-item ${n?"cms-matched":""} ollang-media-item ${e?"selected":""}" data-id="${t.id}">\n <input type="checkbox" class="ollang-content-checkbox" data-id="${t.id}" ${e?"checked":""}>\n ${r}\n <div class="ollang-content-body">\n <div class="ollang-content-text">${this.escapeHtml((t.mediaAlt||t.strapiField||t.mediaUrl).substring(0,80))}</div>\n <div class="ollang-content-meta">\n <span class="ollang-cms-badge ollang-badge-cms">${s}</span>\n <span class="ollang-cms-badge ${a}">${o}</span>\n ${t.strapiContentType?" "+t.strapiContentType:""}${t.strapiEntryId?"#"+t.strapiEntryId:""}${t.strapiField?" → "+t.strapiField:""}\n </div>\n </div>\n </div>`}const o=t.cmsFields?`<strong>${t.strapiContentType}#${t.strapiEntryId}</strong> (${Object.keys(t.cmsFields).join(", ")})`:`&lt;${t.tagName}&gt;${t.strapiContentType?" | "+t.strapiContentType:""}${t.strapiEntryId?"#"+t.strapiEntryId:""}${t.strapiField?" → "+t.strapiField:""}`;return`<div class="ollang-content-item ${n?"cms-matched":""} ${e?"selected":""}" data-id="${t.id}">\n <input type="checkbox" class="ollang-content-checkbox" data-id="${t.id}" ${e?"checked":""}>\n <div class="ollang-content-body">\n <div class="ollang-content-text">${this.escapeHtml(t.text.substring(0,80))}${t.text.length>80?"...":""}</div>\n <div class="ollang-content-meta"><span class="ollang-cms-badge ${i}">${s}</span> ${o}</div>\n </div></div>`}).join(""),t.querySelectorAll(".ollang-content-checkbox").forEach(e=>{e.addEventListener("change",e=>{const n=e.target,i=n.dataset.id,s=t.querySelector(`[data-id="${i}"]`);n.checked?(this.selectedContentIds.add(i),s?.classList.add("selected")):(this.selectedContentIds.delete(i),s?.classList.remove("selected")),this.updateSelectionInfo()})}),this.updateSelectionInfo()}updateSelectionInfo(){const t=document.getElementById("ollang-selection-info"),e=document.getElementById("ollang-selected-count"),n=document.getElementById("ollang-push-tms");t&&e&&(this.selectedContentIds.size>0?(t.style.display="block",e.textContent=String(this.selectedContentIds.size)):t.style.display="none"),n&&(n.disabled=0===this.selectedContentIds.size)}exportContent(){const t=JSON.stringify(Array.from(this.capturedContent.values()),null,2),e=new Blob([t],{type:"application/json"}),n=URL.createObjectURL(e),i=document.createElement("a");i.href=n,i.download=`ollang-captured-${Date.now()}.json`,i.click(),URL.revokeObjectURL(n)}escapeHtml(t){const e=document.createElement("div");return e.textContent=t,e.innerHTML}showStatus(t,e="info"){const n=document.getElementById("ollang-status"),i=document.getElementById("ollang-status-text");n&&i&&(i.textContent=t,n.style.backgroundColor={success:"#d4edda",error:"#f8d7da",info:"#e7f3ff"}[e],n.style.display="block","error"!==e&&setTimeout(()=>{n&&(n.style.display="none")},5e3))}hideStatus(){const t=document.getElementById("ollang-status");t&&(t.style.display="none")}async loadFolders(){try{if(this.folders.length>0)return void this.updateFolderOptions();if(!this.config.apiKey)return;const t=(this.config.baseUrl||"").replace(/\/$/,"");if(!t)return;const e=await fetch(`${t}/scans/folders`,{headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey}});if(e.ok){const t=await e.json(),n=Array.isArray(t)?t:t.folders;n?.length>0&&(this.folders=n,this.selectedFolder||(this.selectedFolder=n[0].name),this.updateFolderOptions())}}catch(t){console.warn("Failed to load folders:",t)}}updateFolderOptions(){const t=document.getElementById("ollang-folder-label"),e=document.getElementById("ollang-folder-menu");if(!t||!e)return;const n=this.selectedFolder||(this.folders[0]?.name??"");n?(this.selectedFolder=n,t.textContent=n):t.textContent="Select folder...",e.innerHTML="",this.folders.forEach(n=>{const i=document.createElement("button");i.type="button",i.className="ollang-folder-option"+(n.name===this.selectedFolder?" ollang-folder-option-active":""),i.textContent=n.name,i.addEventListener("click",()=>{this.selectedFolder=n.name,t.textContent=n.name,e.querySelectorAll(".ollang-folder-option").forEach(t=>t.classList.remove("ollang-folder-option-active")),i.classList.add("ollang-folder-option-active"),e.setAttribute("data-open","false"),e.style.display="none"}),e.appendChild(i)})}showNewFolderDialog(){if(document.getElementById("ollang-new-folder-container"))return;const t=document.querySelector("#ollang-debug-panel #ollang-push-tms")?.parentElement;if(!t)return;const e=document.createElement("div");e.id="ollang-new-folder-container",e.style.cssText="width: 100%; padding: 8px 15px 0 15px; display: flex; gap: 6px; align-items: center;";const n=document.createElement("input");n.type="text",n.placeholder="Enter folder name",n.style.cssText="flex: 1; padding: 4px 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px;";const i=document.createElement("button");i.textContent="Create",i.className="ollang-btn-sm";const s=document.createElement("button");s.textContent="Cancel",s.className="ollang-btn-link";const o=()=>{e.parentElement&&e.remove()};i.addEventListener("click",()=>{const t=n.value.trim();t?(this.selectedFolder=t,this.folders.find(e=>e.name===t)||this.folders.push({id:t,name:t}),this.updateFolderOptions(),this.showStatus(`Folder "${t}" selected.`,"success"),o()):this.showStatus("Please enter a folder name","error")}),s.addEventListener("click",o),e.appendChild(n),e.appendChild(i),e.appendChild(s),t.parentElement?.insertBefore(e,t.nextSibling),setTimeout(()=>n.focus(),0)}async fetchStrapiSchemaInPanel(){const t=(this.config.baseUrl||"").replace(/\/$/,"");if(!t)return void this.showStatus("Missing Ollang baseUrl","error");const e=document.getElementById("ollang-strapi-url"),n=document.getElementById("ollang-strapi-jwt"),i=document.getElementById("ollang-fetch-schema"),s=e?.value?.trim(),o=n?.value?.trim();if(s&&o){i&&(i.disabled=!0),this.showStatus("Fetching Strapi schema...","info");try{const e=await fetch(`${t}/api/strapi-schema`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({strapiUrl:s,strapiToken:o})}),n=await e.json().catch(()=>({}));if(e.ok&&n.success){const t=n.contentTypes?.length??0;this.showStatus(`Schema loaded: ${t} content-type(s)`,"success")}else this.showStatus(n.error||`Failed (${e.status})`,"error")}catch(t){this.showStatus("Network error: "+(t instanceof Error?t.message:String(t)),"error")}finally{i&&(i.disabled=!1)}}else this.showStatus("Enter Strapi URL and Strapi API token","error")}async pushToOllang(){if(0===this.selectedContentIds.size)return void this.showStatus("Please select at least one item","error");if(!this.selectedFolder)return void this.showStatus("Please select a folder first","error");if(!this.config.apiKey)return void this.showStatus("Please enter Ollang API token first","error");const t=(this.config.baseUrl||"").replace(/\/$/,"");if(!t)return void this.showStatus("Missing baseUrl","error");const e=Array.from(this.capturedContent.values()).filter(t=>this.selectedContentIds.has(t.id)),n=e.filter(t=>!(!t.mediaUrl||"cms"!==t.type&&"strapi"!==t.cmsType&&!t.strapiContentType)),i=e.filter(t=>!t.mediaUrl||!("cms"===t.type||"strapi"===t.cmsType||t.strapiContentType));if(0!==i.length||0!==n.length){this.showStatus(`Pushing ${i.length} text and ${n.length} media items to Ollang...`,"info");try{const s=i.some(t=>"cms"===t.type||"strapi"===t.cmsType||!!t.strapiContentType)||n.length>0;let o={};const a=this.config.strapiUrl||[...this.detectedStrapiUrls][0]||"";if(s&&a&&t)try{const e=`${t}/api/strapi-field-config?strapiUrl=${encodeURIComponent(a.replace(/\/$/,""))}`,n=await fetch(e,{headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey}});if(n.ok){const t=await n.json();t.fieldsByContentType&&Object.keys(t.fieldsByContentType).length>0&&(o=t.fieldsByContentType,this.config.debug&&console.log("[Ollang] Using dynamic Strapi field config:",o))}}catch(t){this.config.debug&&console.warn("[Ollang] Could not fetch Strapi field config:",t)}const r=(t,e)=>{if(t===e)return!0;if(t.includes("[]")){const n=t.replace(/\./g,"\\.").replace(/\[\]/g,"\\.\\d+");return new RegExp(`^${n}$`).test(e)}return!1},l=(t,e)=>{if(!t||!e)return;const n=e.split(".");let i=t;for(const t of n){if(null==i)return;i=i[t]}return i},d=t=>{if(null!=t){if("string"==typeof t||"number"==typeof t)return String(t);if("object"==typeof t){const e=t?.data?.attributes?.url??t?.url;if("string"==typeof e)return e;try{return JSON.stringify(t)}catch{return}}return String(t)}},c=new Map,p=[];for(const t of i)if(t.strapiContentType&&null!=t.strapiEntryId){const e=`${t.strapiContentType}:${t.strapiEntryId}`;c.has(e)||c.set(e,{items:[],entryData:this.strapiEntries.get(e)}),c.get(e).items.push(t)}else p.push(t);for(const[t,e]of c){const[n,i]=t.split(":"),s=Number(i);if(!e.items.some(t=>"description"===t.strapiField)){const t=`${n}:${s}:description`,i=this.strapiLongTextMap.get(t);i&&e.items.push({id:`api-desc-${n}-${s}`,text:i.rawText,type:"cms",selector:`api://${n}/${s}/description`,xpath:"",tagName:"richtext",attributes:{},url:window.location.href,timestamp:Date.now(),cmsType:"strapi",cmsField:"description",cmsId:String(s),strapiContentType:n,strapiEntryId:s,strapiField:"description"})}}const g=Array.from(c.entries()).map(([t,e])=>{const[n,i]=t.split(":"),s=Number(i),a=e.items.find(t=>t.strapiField?.includes("title"))||e.items[0],c=e.entryData?.attributes?.route||e.items.find(t=>t.strapiRoute)?.strapiRoute||null,p={};for(const t of e.items)t.strapiField&&(p[t.strapiField]=t.text);const g=o[n]??(n.endsWith("s")?o[n.slice(0,-1)]:void 0);if(g&&g.length>0){const t={};for(const[e,n]of Object.entries(p))g.some(t=>r(t,e))&&(t[e]=n);Object.keys(p).forEach(t=>delete p[t]),Object.assign(p,t);const n=e.entryData?.attributes;if(n)for(const t of g){const e=t.replace(/\[\]/g,".0");if(Object.keys(p).find(e=>r(t,e)))continue;const i=l(n,e),s=d(i);void 0!==s&&""!==s&&(p[t]=s)}}return{id:`cms-entry-${n}-${s}`,text:a.text,type:"cms",source:{file:a.selector||"browser-dom",line:0,column:0,context:a.xpath||""},strapiContentType:n,strapiEntryId:s,strapiField:a.strapiField||"header.title",strapiRoute:c,cmsFields:p,selected:!1,status:"scanned"}}),u=p.map(t=>({id:`cms-${t.id}`,text:t.text,type:"cms"===t.type?"cms":"dynamic",source:{file:t.selector||"browser-dom",line:0,column:0,context:t.xpath||""},selected:!1,status:"scanned"})),h=Array.from(this.strapiEntries.values()).map(t=>({contentType:t.contentType,entryId:t.entryId,route:t.attributes?.route||null,locale:t.attributes?.locale||null,title:t.attributes?.header?.title||t.attributes?.title||null})),f={texts:[...g,...u],media:n.map(t=>({id:`media-${t.id}`,mediaUrl:t.mediaUrl,mediaType:t.mediaType,alt:t.mediaAlt,type:"cms-media",source:{file:t.selector||"browser-dom",line:0,column:0,context:t.xpath||""},metadata:{selector:t.selector,xpath:t.xpath,tagName:t.tagName,attributes:t.attributes,cmsType:t.cmsType,cmsField:t.cmsField,cmsId:t.cmsId,strapiContentType:t.strapiContentType,strapiEntryId:t.strapiEntryId,strapiField:t.strapiField,strapiRoute:t.strapiRoute},selected:!1,status:"scanned"})),isCms:s,cms:{strapi:{entries:h}},routes:this.getEntryRoutes(),timestamp:(new Date).toISOString(),projectRoot:window.location.origin,sourceLanguage:"en",targetLanguages:[],projectId:this.config.projectId||"unknown",folderName:this.selectedFolder};let m=null;try{const e=await fetch(`${t}/scans`,{headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey}});if(e.ok){const t=await e.json();if(Array.isArray(t)){const e=window.location.href;let n=null;for(const i of t){const t="string"==typeof i.scanData?JSON.parse(i.scanData):i.scanData;if(t?.folderName!==this.selectedFolder)continue;const s=i.url===e||i.originalUrl===e||t?.projectRoot===window.location.origin;if((!n||s)&&(n=i,s))break}n&&(m=n.id||n._id)}}}catch(t){console.warn("Failed to list scans:",t)}const y={url:window.location.href,scanData:f,originalFilename:`cms-scan-${Date.now()}.json`,folderName:this.selectedFolder},b=m?`${t}/scans/${m}`:`${t}/scans`,x=m?"PATCH":"POST",w=await fetch(b,{method:x,headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey},body:JSON.stringify(y)});if(!w.ok){const t=await w.json();throw new Error(t.message||`Failed: ${w.statusText}`)}const v=await w.json();this.showStatus(`✅ Pushed ${e.length} items to Ollang! Scan ID: ${v.id||"N/A"}`,"success"),this.selectedContentIds.clear();const C=document.getElementById("ollang-content-list");C&&this.showContent(C)}catch(t){console.error("Push to Ollang error:",t),this.showStatus(`❌ Failed to push to Ollang: ${t.message}`,"error")}}else this.showStatus("Nothing to push. Please select at least one text or media item.","info")}}e.OllangBrowser=n,n.MAX_FIELD_LENGTH=500,n.LONG_TEXT_FIELDS=new Set(["description","content","body","html","markdown","richText","text"]),n.MAX_RECURSION_DEPTH=4,n.SKIP_RELATION_KEYS=new Set(["author","editor","localizations","category","categories"]),n.SKIP_KEYS=new Set(["id","createdAt","updatedAt","publishedAt","publishedDate","locale","route","url","path","slug","hash","ext","mime","provider","previewUrl","provider_metadata","background","name","alternativeText","caption","isInvisible","views","size","width","height","isStory","summary"])}},e={};function n(i){var s=e[i];if(void 0!==s)return s.exports;var o=e[i]={exports:{}};return t[i](o,o.exports,n),o.exports}var i={};return(()=>{var t=i;Object.defineProperty(t,"__esModule",{value:!0});const e=n(243);!function(){if("undefined"==typeof window)return;const t=document.currentScript;let n;if(window.ollangConfig)n=window.ollangConfig;else{if(!t)return void console.warn("Ollang: No configuration found. Provide window.ollangConfig or use data attributes.");n={apiKey:t.dataset.apiKey||"",projectId:t.dataset.projectId,baseUrl:t.dataset.baseUrl,strapiUrl:t.dataset.strapiUrl||"",autoDetectCMS:"false"!==t.dataset.autoDetectCms,cmsType:t.dataset.cmsType,debounceMs:parseInt(t.dataset.debounceMs||"1000"),debug:"true"===t.dataset.debug}}function i(){window.Ollang=e.OllangBrowser;const t=new e.OllangBrowser(n);window.ollangInstance=t,window.ollang=t,console.log("✅ Ollang Browser SDK initialized (v2 - API Interception)"),"true"===new URLSearchParams(window.location.search).get("ollang-localize")&&setTimeout(()=>{t.showDebugPanel().catch(t=>{console.error("Failed to show debug panel:",t)})},1e3)}n.apiKey||console.log("Ollang: API key not provided. User will be prompted to enter it."),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",i):i()}()})(),i})());
@@ -53,11 +53,11 @@ app.use('/api', (req, res, next) => {
53
53
  if (!configuredKey)
54
54
  return next(); // first-time setup, no key configured yet
55
55
  const providedKey = req.headers['x-api-key'] ||
56
- (req.headers.authorization?.startsWith('Bearer ')
57
- ? req.headers.authorization.slice(7)
58
- : '');
56
+ (req.headers.authorization?.startsWith('Bearer ') ? req.headers.authorization.slice(7) : '');
59
57
  if (providedKey !== configuredKey) {
60
- return res.status(401).json({ success: false, error: 'Unauthorized: invalid or missing API key' });
58
+ return res
59
+ .status(401)
60
+ .json({ success: false, error: 'Unauthorized: invalid or missing API key' });
61
61
  }
62
62
  next();
63
63
  });
@@ -88,7 +88,7 @@ async function updateCurrentScan(folderName) {
88
88
  const state = getOrCreateFolderState(folderName);
89
89
  const { currentScanId, texts, videos, images, audios } = state;
90
90
  if (!tms) {
91
- console.warn('⚠️ Cannot update scan: TMS not initialized');
91
+ console.warn('⚠️ Cannot update scan: Ollang not initialized');
92
92
  return;
93
93
  }
94
94
  try {
@@ -315,7 +315,7 @@ app.post('/api/config/apikey', async (req, res) => {
315
315
  tms = await initTMS();
316
316
  return res.status(401).json({
317
317
  success: false,
318
- error: 'Invalid TMS API key. Please check your token and try again.',
318
+ error: 'Invalid Ollang API key. Please check your token and try again.',
319
319
  });
320
320
  }
321
321
  }
@@ -349,7 +349,10 @@ app.post('/api/config/update', async (req, res) => {
349
349
  return res.status(400).json({ success: false, error: 'Invalid target language format' });
350
350
  }
351
351
  if (!VALID_VIDEO_TYPES.includes(videoTranslationType)) {
352
- return res.status(400).json({ success: false, error: 'Invalid video translation type. Must be aiDubbing or subtitle.' });
352
+ return res.status(400).json({
353
+ success: false,
354
+ error: 'Invalid video translation type. Must be aiDubbing or subtitle.',
355
+ });
353
356
  }
354
357
  process.env.TMS_SOURCE_LANGUAGE = sourceLanguage;
355
358
  process.env.TMS_TARGET_LANGUAGES = targetLanguages.join(',');
@@ -584,7 +587,7 @@ app.post('/api/translate', async (req, res) => {
584
587
  if (!tms) {
585
588
  return res.status(400).json({
586
589
  success: false,
587
- error: 'TMS not initialized. Please scan first.',
590
+ error: 'Ollang not initialized. Please scan first.',
588
591
  });
589
592
  }
590
593
  const { textIds, targetLanguage, targetLanguages, level, folderName } = req.body;
@@ -612,7 +615,7 @@ app.post('/api/translate', async (req, res) => {
612
615
  if (sdk && folderName) {
613
616
  try {
614
617
  console.log(`📂 Loading latest scan for folder: ${folderName}`);
615
- // Get folderId from TMS server's /api/folders endpoint
618
+ // Get folderId from Ollang server's /api/folders endpoint
616
619
  try {
617
620
  const axios = require('axios');
618
621
  const foldersResponse = await axios.get('http://localhost:5972/api/folders');
@@ -909,7 +912,7 @@ app.post('/api/apply', async (req, res) => {
909
912
  if (!tms) {
910
913
  return res.status(400).json({
911
914
  success: false,
912
- error: 'TMS not initialized.',
915
+ error: 'Ollang not initialized.',
913
916
  });
914
917
  }
915
918
  const { targetLanguage, textIds, folderName, strapiUrl: reqStrapiUrl, strapiToken: reqStrapiToken, } = req.body;
@@ -1121,11 +1124,7 @@ function getOllangBackendBase() {
1121
1124
  backendBase = backendBase.replace(/\/$/, '');
1122
1125
  try {
1123
1126
  const parsed = new URL(backendBase);
1124
- const allowedHosts = new Set([
1125
- 'localhost',
1126
- '127.0.0.1',
1127
- 'api-integration.ollang.com',
1128
- ]);
1127
+ const allowedHosts = new Set(['localhost', '127.0.0.1', 'api-integration.ollang.com']);
1129
1128
  const extraHosts = (process.env.OLLANG_ALLOWED_HOSTS || '').split(',').filter(Boolean);
1130
1129
  extraHosts.forEach((h) => allowedHosts.add(h.trim()));
1131
1130
  if (!allowedHosts.has(parsed.hostname)) {
@@ -1413,7 +1412,7 @@ app.post('/api/strapi-schema', async (req, res) => {
1413
1412
  if (!strapiUrl || !strapiToken) {
1414
1413
  return res.status(400).json({
1415
1414
  success: false,
1416
- error: 'strapiUrl and strapiToken (Strapi API token) are required. This is not the Ollang TMS API token.',
1415
+ error: 'strapiUrl and strapiToken (Strapi API token) are required. This is not the Ollang API token.',
1417
1416
  });
1418
1417
  }
1419
1418
  const base = String(strapiUrl).replace(/\/$/, '');
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>Ollang TMS</title>
7
+ <title>Ollang</title>
8
8
  <script type="module" crossorigin src="/assets/index-HvrqZV5Z.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-5U1Hw3uo.css">
10
10
  </head>
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@ollang-dev/sdk",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Official TypeScript/Node.js SDK for Ollang API - Translation, Transcription, Dubbing, Closed Captioning, and Translation Management System",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "bin": {
8
- "ollang-tms": "./bin/tms.js"
8
+ "ollang-start": "./bin/start.js"
9
9
  },
10
10
  "exports": {
11
11
  ".": {
@@ -25,6 +25,7 @@
25
25
  ],
26
26
  "scripts": {
27
27
  "build": "npm run build:node && npm run build:ui && npm run build:browser",
28
+ "build:ci": "npm run build:node && npm run build:ui:copy",
28
29
  "build:node": "tsc",
29
30
  "build:ui": "npm run build:ui:react && npm run build:ui:copy",
30
31
  "build:ui:react": "cd src/tms/ui-react && npm install && npm run build && cd ../../..",
@@ -36,7 +37,7 @@
36
37
  "dev:ui": "cd src/tms/ui-react && npm run dev",
37
38
  "dev:browser": "npm run build:browser:watch & npm run watch:copy",
38
39
  "watch:copy": "nodemon --watch dist/browser/ollang-browser.min.js --exec 'npm run copy:browser'",
39
- "prepublishOnly": "npm run build",
40
+ "prepublishOnly": "npm run build:ci",
40
41
  "test": "jest",
41
42
  "test:tms:local": "npm run build:node && node dist/examples/test-tms-local.js",
42
43
  "test:browser": "open examples/browser-cms-integration.html",