@lionrapid/core 0.6.6 → 0.7.0

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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/dist/browser/auto-init.d.ts +6 -0
  3. package/dist/browser/auto-init.d.ts.map +1 -1
  4. package/dist/browser/auto-init.js +36 -6
  5. package/dist/browser/auto-init.js.map +1 -1
  6. package/dist/browser/language-switcher.d.ts +12 -0
  7. package/dist/browser/language-switcher.d.ts.map +1 -1
  8. package/dist/browser/language-switcher.js +58 -0
  9. package/dist/browser/language-switcher.js.map +1 -1
  10. package/dist/browser/lionrapid.auto.dev.js +163 -14
  11. package/dist/browser/lionrapid.auto.dev.js.map +3 -3
  12. package/dist/browser/lionrapid.auto.js +163 -14
  13. package/dist/browser/lionrapid.auto.js.map +3 -3
  14. package/dist/browser/lionrapid.auto.min.js +5 -5
  15. package/dist/browser/lionrapid.auto.min.js.map +4 -4
  16. package/dist/browser/lionrapid.esm.js +103 -10
  17. package/dist/browser/lionrapid.esm.js.map +2 -2
  18. package/dist/browser/lionrapid.esm.min.js +4 -4
  19. package/dist/browser/lionrapid.esm.min.js.map +3 -3
  20. package/dist/browser/lionrapid.iife.dev.js +103 -10
  21. package/dist/browser/lionrapid.iife.dev.js.map +2 -2
  22. package/dist/browser/lionrapid.iife.js +103 -10
  23. package/dist/browser/lionrapid.iife.js.map +2 -2
  24. package/dist/browser/lionrapid.iife.min.js +4 -4
  25. package/dist/browser/lionrapid.iife.min.js.map +3 -3
  26. package/dist/browser/lionrapid.umd.js +103 -10
  27. package/dist/browser/lionrapid.umd.js.map +2 -2
  28. package/dist/browser/lionrapid.umd.min.js +4 -4
  29. package/dist/browser/lionrapid.umd.min.js.map +3 -3
  30. package/dist/browser/storage/cache-storage-repository.d.ts +9 -0
  31. package/dist/browser/storage/cache-storage-repository.d.ts.map +1 -1
  32. package/dist/browser/storage/cache-storage-repository.js +14 -10
  33. package/dist/browser/storage/cache-storage-repository.js.map +1 -1
  34. package/dist/browser/storage/scope.d.ts +12 -0
  35. package/dist/browser/storage/scope.d.ts.map +1 -0
  36. package/dist/browser/storage/scope.js +15 -0
  37. package/dist/browser/storage/scope.js.map +1 -0
  38. package/dist/builder.d.ts.map +1 -1
  39. package/dist/builder.js +9 -0
  40. package/dist/builder.js.map +1 -1
  41. package/dist/handlers/async-storage-handler.d.ts +9 -0
  42. package/dist/handlers/async-storage-handler.d.ts.map +1 -1
  43. package/dist/handlers/async-storage-handler.js +42 -0
  44. package/dist/handlers/async-storage-handler.js.map +1 -1
  45. package/dist/handlers/handler-chain.d.ts +2 -0
  46. package/dist/handlers/handler-chain.d.ts.map +1 -1
  47. package/dist/handlers/handler-chain.js +29 -0
  48. package/dist/handlers/handler-chain.js.map +1 -1
  49. package/dist/handlers/sync-storage-handler.d.ts +4 -0
  50. package/dist/handlers/sync-storage-handler.d.ts.map +1 -1
  51. package/dist/handlers/sync-storage-handler.js +35 -0
  52. package/dist/handlers/sync-storage-handler.js.map +1 -1
  53. package/dist/index.d.ts +1 -1
  54. package/dist/index.js +1 -1
  55. package/dist/interfaces/handler.interface.d.ts +4 -0
  56. package/dist/interfaces/handler.interface.d.ts.map +1 -1
  57. package/package.json +3 -3
@@ -1128,6 +1128,24 @@ var __lionrapidAutoExports = (() => {
1128
1128
  });
1129
1129
  await Promise.allSettled(savePromises);
1130
1130
  }
1131
+ /** Forget `locale:namespace` in every memory and storage handler (a 404). */
1132
+ async forgetNamespaceInHandlers(context) {
1133
+ for (const handler of this.syncHandlers) {
1134
+ try {
1135
+ handler.forgetNamespace?.(context);
1136
+ } catch (error) {
1137
+ logger.warn(`Failed to clear namespace in ${handler.name}`, error);
1138
+ }
1139
+ }
1140
+ for (const handler of this.asyncHandlers) {
1141
+ if (handler.type !== "storage" && handler.type !== "memory") continue;
1142
+ try {
1143
+ await handler.forgetNamespace?.(context);
1144
+ } catch (error) {
1145
+ logger.warn(`Failed to clear namespace in ${handler.name}`, error);
1146
+ }
1147
+ }
1148
+ }
1131
1149
  async saveNamespaceToHandlers(translations, context) {
1132
1150
  this.saveNamespaceToMemoryHandlers(translations, context);
1133
1151
  await this.saveNamespaceToStorageHandlers(translations, context);
@@ -1229,6 +1247,10 @@ var __lionrapidAutoExports = (() => {
1229
1247
  });
1230
1248
  return result.data;
1231
1249
  }
1250
+ if (result && result.status === 404) {
1251
+ await this.forgetNamespaceInHandlers({ namespace, locale });
1252
+ return null;
1253
+ }
1232
1254
  } catch (error) {
1233
1255
  logger.warn(
1234
1256
  `Failed to load namespace: ${locale}:${namespace} from ${handler.name}`,
@@ -1612,6 +1634,7 @@ var __lionrapidAutoExports = (() => {
1612
1634
  context.locale + ":" + context.namespace,
1613
1635
  this.name
1614
1636
  );
1637
+ const previousKeys = this.storedNamespaceKeys(context.locale, context.namespace);
1615
1638
  const namespaceCacheKey = createNamespaceCacheKey(
1616
1639
  context.locale,
1617
1640
  context.namespace
@@ -1623,11 +1646,38 @@ var __lionrapidAutoExports = (() => {
1623
1646
  context.locale,
1624
1647
  context.namespace
1625
1648
  );
1649
+ for (const key of previousKeys) {
1650
+ if (!(key in flattened)) this.removeQuietly(key);
1651
+ }
1626
1652
  for (const [flatKey, flatValue] of Object.entries(flattened)) {
1627
1653
  this.setWithTTL(flatKey, flatValue);
1628
1654
  }
1629
1655
  repoBus.setSuccess(JSON.stringify(translations));
1630
1656
  }
1657
+ /** Forget a namespace the server says does not exist (404). See AsyncStorageHandler. */
1658
+ forgetNamespace(context) {
1659
+ if (!context.namespace) return;
1660
+ for (const key of this.storedNamespaceKeys(context.locale, context.namespace)) {
1661
+ this.removeQuietly(key);
1662
+ }
1663
+ this.clearNamespace(context.locale, context.namespace);
1664
+ }
1665
+ storedNamespaceKeys(locale, namespace) {
1666
+ try {
1667
+ const blob = this.repository.get(createNamespaceCacheKey(locale, namespace));
1668
+ if (!blob) return [];
1669
+ return Object.keys(flattenTranslations(JSON.parse(blob), "", locale, namespace));
1670
+ } catch {
1671
+ return [];
1672
+ }
1673
+ }
1674
+ removeQuietly(cacheKey) {
1675
+ this.ttlMap.delete(cacheKey);
1676
+ try {
1677
+ this.repository.remove(cacheKey);
1678
+ } catch {
1679
+ }
1680
+ }
1631
1681
  /**
1632
1682
  * ✅ Strategy 1: Direct cache lookup using unified pattern
1633
1683
  * Returns parsed ContentUnit (text + styles) or null
@@ -1881,6 +1931,7 @@ var __lionrapidAutoExports = (() => {
1881
1931
  logger.debug(
1882
1932
  `Saving namespace: ${context.locale}:${context.namespace} (${keyCount} keys)`
1883
1933
  );
1934
+ const previousKeys = await this.storedNamespaceKeys(context.locale, context.namespace);
1884
1935
  const namespaceCacheKey = createNamespaceCacheKey(
1885
1936
  context.locale,
1886
1937
  context.namespace
@@ -1892,6 +1943,9 @@ var __lionrapidAutoExports = (() => {
1892
1943
  context.locale,
1893
1944
  context.namespace
1894
1945
  );
1946
+ for (const key of previousKeys) {
1947
+ if (!(key in flattened)) await this.removeQuietly(key);
1948
+ }
1895
1949
  let savedCount = 0;
1896
1950
  for (const [flatKey, flatValue] of Object.entries(flattened)) {
1897
1951
  await this.setWithTTL(flatKey, flatValue);
@@ -1900,6 +1954,35 @@ var __lionrapidAutoExports = (() => {
1900
1954
  logger.debug(`Saved: ${savedCount} keys + namespace blob`);
1901
1955
  repoBus.setSuccess(JSON.stringify(translations));
1902
1956
  }
1957
+ /**
1958
+ * Forget a namespace the server says does not exist (404): its blob and
1959
+ * every per-key entry the blob listed. A network failure never gets here,
1960
+ * so the offline floor still serves when the server is unreachable.
1961
+ */
1962
+ async forgetNamespace(context) {
1963
+ if (!context.namespace) return;
1964
+ for (const key of await this.storedNamespaceKeys(context.locale, context.namespace)) {
1965
+ await this.removeQuietly(key);
1966
+ }
1967
+ await this.removeQuietly(createNamespaceCacheKey(context.locale, context.namespace));
1968
+ }
1969
+ /** The per-key cache keys (`locale:namespace:key`) the stored blob lists. */
1970
+ async storedNamespaceKeys(locale, namespace) {
1971
+ try {
1972
+ const blob = await this.repository.get(createNamespaceCacheKey(locale, namespace));
1973
+ if (!blob) return [];
1974
+ return Object.keys(flattenTranslations(JSON.parse(blob), "", locale, namespace));
1975
+ } catch {
1976
+ return [];
1977
+ }
1978
+ }
1979
+ async removeQuietly(cacheKey) {
1980
+ this.ttlMap.delete(cacheKey);
1981
+ try {
1982
+ await this.repository.remove(cacheKey);
1983
+ } catch {
1984
+ }
1985
+ }
1903
1986
  /**
1904
1987
  * ✅ Strategy 1: Direct cache lookup using unified pattern
1905
1988
  * Returns parsed ContentUnit (text + styles) or null
@@ -4095,6 +4178,12 @@ var __lionrapidAutoExports = (() => {
4095
4178
  logger.debug(`Preloaded: ${namespaceKey}`);
4096
4179
  return;
4097
4180
  }
4181
+ if (result?.status === 404) {
4182
+ await handlerChain.forgetNamespaceInHandlers({
4183
+ locale: preloadConfig.locale,
4184
+ namespace: preloadConfig.namespace
4185
+ });
4186
+ }
4098
4187
  if (result?.error) {
4099
4188
  warnLoudlyOnce(namespaceKey, result.error);
4100
4189
  logger.warn(`Preload failed: ${namespaceKey}`, result.error);
@@ -5631,6 +5720,32 @@ var __lionrapidAutoExports = (() => {
5631
5720
  .lionrapid-btn-row button:hover { background: #f5f5f5; }
5632
5721
  .lionrapid-btn-row button.active { background: #3b82f6; color: #fff; }
5633
5722
  `;
5723
+ var HEADER_GAP_PX = 8;
5724
+ var CORNER_INSET_PX = 20;
5725
+ function topCornerOffset(doc = document, win = window) {
5726
+ const viewport = win.innerHeight || doc.documentElement.clientHeight || 0;
5727
+ const span = (el) => {
5728
+ const style = win.getComputedStyle(el);
5729
+ if (style.display === "none" || style.visibility === "hidden") return null;
5730
+ const rect = el.getBoundingClientRect();
5731
+ if (rect.height <= 0) return null;
5732
+ const pinned = style.position === "fixed" || style.position === "sticky";
5733
+ const shift = pinned ? 0 : win.scrollY;
5734
+ return [rect.top + shift, rect.bottom + shift];
5735
+ };
5736
+ const adminBar = doc.getElementById("wpadminbar");
5737
+ const barBottom = adminBar && span(adminBar)?.[1] || 0;
5738
+ let bottom = barBottom;
5739
+ doc.querySelectorAll('header, [role="banner"]').forEach((el) => {
5740
+ if (el.closest("article, main, aside")) return;
5741
+ const s = span(el);
5742
+ if (!s || s[0] > barBottom + HEADER_GAP_PX * 4) return;
5743
+ bottom = Math.max(bottom, s[1]);
5744
+ });
5745
+ if (bottom === 0) return CORNER_INSET_PX;
5746
+ const below = Math.ceil(bottom) + HEADER_GAP_PX;
5747
+ return viewport > 0 ? Math.min(below, Math.floor(viewport / 2)) : below;
5748
+ }
5634
5749
  var LanguageSwitcher = class {
5635
5750
  constructor() {
5636
5751
  __publicField(this, "core", null);
@@ -5691,6 +5806,14 @@ var __lionrapidAutoExports = (() => {
5691
5806
  const showNames = element.dataset.lionrapidSwitcherShowNames !== "false";
5692
5807
  const customCss = element.dataset.lionrapidSwitcherCustomCss || null;
5693
5808
  element.dataset.position = position;
5809
+ if (position.startsWith("top")) {
5810
+ const place = () => {
5811
+ element.style.top = `${topCornerOffset()}px`;
5812
+ };
5813
+ place();
5814
+ window.addEventListener("load", place);
5815
+ window.addEventListener("resize", place);
5816
+ }
5694
5817
  if (customCss) {
5695
5818
  const styleEl = document.createElement("style");
5696
5819
  styleEl.textContent = customCss;
@@ -6129,6 +6252,8 @@ var __lionrapidAutoExports = (() => {
6129
6252
  __publicField(this, "type", "storage");
6130
6253
  __publicField(this, "config");
6131
6254
  __publicField(this, "cacheName");
6255
+ /** CACHE_PREFIX, plus `<scope>/` when scoped. */
6256
+ __publicField(this, "entryPrefix");
6132
6257
  __publicField(this, "defaultTTL");
6133
6258
  __publicField(this, "available");
6134
6259
  this.config = {
@@ -6142,6 +6267,8 @@ var __lionrapidAutoExports = (() => {
6142
6267
  }
6143
6268
  };
6144
6269
  this.cacheName = this.config.options?.cacheName ?? CACHE_NAME;
6270
+ const scope = this.config.options?.scope;
6271
+ this.entryPrefix = scope ? `${CACHE_PREFIX}${encodeURIComponent(scope)}/` : CACHE_PREFIX;
6145
6272
  this.defaultTTL = this.config.options?.ttl ?? 7 * 24 * 60 * 60 * 1e3;
6146
6273
  this.available = this.checkAvailability();
6147
6274
  }
@@ -6162,13 +6289,13 @@ var __lionrapidAutoExports = (() => {
6162
6289
  }
6163
6290
  try {
6164
6291
  const cache = await caches.open(this.cacheName);
6165
- const response = await cache.match(CACHE_PREFIX + key);
6292
+ const response = await cache.match(this.entryPrefix + key);
6166
6293
  if (!response) {
6167
6294
  throw new TranslationNotFoundError(key);
6168
6295
  }
6169
6296
  const data = await response.json();
6170
6297
  if (data.expiry !== null && Date.now() > data.expiry) {
6171
- await cache.delete(CACHE_PREFIX + key);
6298
+ await cache.delete(this.entryPrefix + key);
6172
6299
  throw new TranslationNotFoundError(key);
6173
6300
  }
6174
6301
  return data.value;
@@ -6192,7 +6319,7 @@ var __lionrapidAutoExports = (() => {
6192
6319
  timestamp: Date.now()
6193
6320
  };
6194
6321
  await cache.put(
6195
- CACHE_PREFIX + key,
6322
+ this.entryPrefix + key,
6196
6323
  new Response(JSON.stringify(data), {
6197
6324
  headers: { "Content-Type": "application/json" }
6198
6325
  })
@@ -6207,7 +6334,7 @@ var __lionrapidAutoExports = (() => {
6207
6334
  if (!this.available) return;
6208
6335
  try {
6209
6336
  const cache = await caches.open(this.cacheName);
6210
- await cache.delete(CACHE_PREFIX + key);
6337
+ await cache.delete(this.entryPrefix + key);
6211
6338
  } catch {
6212
6339
  }
6213
6340
  }
@@ -6220,7 +6347,7 @@ var __lionrapidAutoExports = (() => {
6220
6347
  const cache = await caches.open(this.cacheName);
6221
6348
  const keys = await cache.keys();
6222
6349
  await Promise.all(
6223
- keys.filter((request) => request.url.includes(CACHE_PREFIX)).map((request) => cache.delete(request))
6350
+ keys.filter((request) => request.url.includes(this.entryPrefix)).map((request) => cache.delete(request))
6224
6351
  );
6225
6352
  } catch {
6226
6353
  }
@@ -6238,7 +6365,7 @@ var __lionrapidAutoExports = (() => {
6238
6365
  await Promise.all(
6239
6366
  keys.filter((request) => {
6240
6367
  const url = request.url;
6241
- const key = url.includes(CACHE_PREFIX) ? url.substring(url.indexOf(CACHE_PREFIX) + CACHE_PREFIX.length) : "";
6368
+ const key = url.includes(this.entryPrefix) ? url.substring(url.indexOf(this.entryPrefix) + this.entryPrefix.length) : "";
6242
6369
  return key === namespaceCacheKey || key.startsWith(keyPrefix);
6243
6370
  }).map((request) => cache.delete(request))
6244
6371
  );
@@ -6290,9 +6417,9 @@ var __lionrapidAutoExports = (() => {
6290
6417
  const keys = await cache.keys();
6291
6418
  for (const request of keys) {
6292
6419
  const url = request.url;
6293
- if (!url.includes(CACHE_PREFIX)) continue;
6420
+ if (!url.includes(this.entryPrefix)) continue;
6294
6421
  const key = url.substring(
6295
- url.indexOf(CACHE_PREFIX) + CACHE_PREFIX.length
6422
+ url.indexOf(this.entryPrefix) + this.entryPrefix.length
6296
6423
  );
6297
6424
  try {
6298
6425
  const value = await this.get(key);
@@ -6314,7 +6441,7 @@ var __lionrapidAutoExports = (() => {
6314
6441
  try {
6315
6442
  const cache = await caches.open(this.cacheName);
6316
6443
  const keys = await cache.keys();
6317
- const size = keys.filter((r) => r.url.includes(CACHE_PREFIX)).length;
6444
+ const size = keys.filter((r) => r.url.includes(this.entryPrefix)).length;
6318
6445
  return { size, cacheName: this.cacheName };
6319
6446
  } catch {
6320
6447
  return { size: 0, cacheName: this.cacheName };
@@ -6325,6 +6452,11 @@ var __lionrapidAutoExports = (() => {
6325
6452
  // src/config/api.config.ts
6326
6453
  var DEFAULT_API_BASE_URL = "https://app.lionrapid.com";
6327
6454
 
6455
+ // src/browser/storage/scope.ts
6456
+ function storageScope(apiKey) {
6457
+ return apiKey ? md5(apiKey).slice(0, 8) : "none";
6458
+ }
6459
+
6328
6460
  // src/browser/auto-init.ts
6329
6461
  function resolveWritePolicy(apiKey) {
6330
6462
  const isPublishable = apiKey.startsWith("pk_");
@@ -6454,6 +6586,12 @@ var __lionrapidAutoExports = (() => {
6454
6586
  document.documentElement.dir = localeDir(locale);
6455
6587
  }
6456
6588
  }
6589
+ function applyLocaleAttrs(locale) {
6590
+ if (typeof document !== "undefined" && locale) {
6591
+ document.documentElement.lang = locale;
6592
+ }
6593
+ applyLocaleDir(locale);
6594
+ }
6457
6595
  function findConfigScript() {
6458
6596
  const current = document.currentScript;
6459
6597
  if (current instanceof HTMLScriptElement && current.dataset.apiKey) {
@@ -6486,7 +6624,11 @@ var __lionrapidAutoExports = (() => {
6486
6624
  );
6487
6625
  }
6488
6626
  const initialLocale = resolveInitialLocale(config);
6489
- applyLocaleDir(initialLocale);
6627
+ if (config.mode === "dynamic-only") {
6628
+ applyLocaleDir(initialLocale);
6629
+ } else {
6630
+ applyLocaleAttrs(initialLocale);
6631
+ }
6490
6632
  const languageSwitcher = new LanguageSwitcher();
6491
6633
  languageSwitcher.setLanguages(config.languages);
6492
6634
  languageSwitcher.setInitialLocale(initialLocale);
@@ -6520,18 +6662,25 @@ var __lionrapidAutoExports = (() => {
6520
6662
  navigate: config.mode === "dynamic-only" ? (locale) => navigateToLocale(locale, config.defaultLocale, config.languages) : null
6521
6663
  });
6522
6664
  languageSwitcher.setOnLanguageChange(localeHandler.onChange);
6665
+ const storeScope = storageScope(config.apiKey);
6523
6666
  try {
6524
6667
  const builder = LionRapidBuilder.init({
6525
6668
  defaultLocale: initialLocale,
6526
6669
  namespace: config.namespace
6527
- }).use(new MemoryRepository({ enabled: true })).use(new LocalStorageRepository({ enabled: true })).use(new CacheStorageRepository({ enabled: true }));
6670
+ }).use(new MemoryRepository({ enabled: true })).use(
6671
+ new LocalStorageRepository({
6672
+ enabled: true,
6673
+ options: { prefix: `lionrapid:${storeScope}:` }
6674
+ })
6675
+ ).use(new CacheStorageRepository({ enabled: true, options: { scope: storeScope } }));
6528
6676
  if (config.offline !== false) {
6529
6677
  builder.use(
6530
6678
  new CacheStorageRepository({
6531
6679
  enabled: true,
6532
6680
  options: {
6533
6681
  ttl: Number.MAX_SAFE_INTEGER,
6534
- cacheName: CORE_CACHE_NAMES.offlineFloor
6682
+ cacheName: CORE_CACHE_NAMES.offlineFloor,
6683
+ scope: storeScope
6535
6684
  }
6536
6685
  })
6537
6686
  );
@@ -6572,7 +6721,7 @@ var __lionrapidAutoExports = (() => {
6572
6721
  await core.changeLanguage(locale, {
6573
6722
  preloadNamespaces: [config.namespace]
6574
6723
  });
6575
- applyLocaleDir(locale);
6724
+ applyLocaleAttrs(locale);
6576
6725
  autoTranslator.updateLocale(locale);
6577
6726
  autoTranslator.retranslate();
6578
6727
  });
@@ -6619,7 +6768,7 @@ var __lionrapidAutoExports = (() => {
6619
6768
  await core.changeLanguage(locale, {
6620
6769
  preloadNamespaces: [config.namespace]
6621
6770
  });
6622
- applyLocaleDir(locale);
6771
+ applyLocaleAttrs(locale);
6623
6772
  autoTranslator.updateLocale(locale);
6624
6773
  autoTranslator.retranslate();
6625
6774
  languageSwitcher.initAll();