ds-one 0.2.0-alpha.3 → 0.2.5-alpha.10

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 (271) hide show
  1. package/DS1/0-face/{2025-04-23-device.ts → device.ts} +10 -6
  2. package/DS1/{utils/language.ts → 0-face/i18n.ts} +236 -92
  3. package/DS1/0-face/preferences.ts +23 -0
  4. package/DS1/0-face/pricing.ts +57 -0
  5. package/DS1/1-root/fonts/Iosevka-Regular.woff2 +0 -0
  6. package/DS1/1-root/one.css +76 -107
  7. package/DS1/2-core/ds-banner.ts +3 -0
  8. package/DS1/2-core/ds-button.ts +13 -16
  9. package/DS1/2-core/ds-cycle.ts +84 -39
  10. package/DS1/2-core/{ds-year.ts → ds-date.ts} +5 -6
  11. package/DS1/2-core/ds-icon.ts +4 -4
  12. package/DS1/2-core/ds-input.ts +1 -0
  13. package/DS1/2-core/ds-text.ts +27 -3
  14. package/DS1/2-core/ds-tooltip.ts +9 -14
  15. package/DS1/3-unit/ds-list.ts +7 -0
  16. package/DS1/3-unit/ds-row.ts +4 -5
  17. package/DS1/3-unit/ds-table.ts +5 -6
  18. package/DS1/4-page/ds-grid.ts +9 -59
  19. package/DS1/4-page/ds-layout.ts +123 -18
  20. package/DS1/index.ts +39 -37
  21. package/LICENSE +1 -1
  22. package/README.md +43 -133
  23. package/dist/0-face/{2025-04-23-device.d.ts → device.d.ts} +1 -1
  24. package/dist/0-face/device.d.ts.map +1 -0
  25. package/dist/0-face/{2025-04-23-device.js → device.js} +7 -3
  26. package/dist/{utils/language.d.ts → 0-face/i18n.d.ts} +1 -3
  27. package/dist/0-face/i18n.d.ts.map +1 -0
  28. package/dist/{utils/language.js → 0-face/i18n.js} +178 -76
  29. package/dist/0-face/preferences.d.ts +9 -0
  30. package/dist/0-face/preferences.d.ts.map +1 -0
  31. package/dist/0-face/preferences.js +14 -0
  32. package/dist/0-face/pricing.d.ts +15 -0
  33. package/dist/0-face/pricing.d.ts.map +1 -0
  34. package/dist/0-face/pricing.js +46 -0
  35. package/dist/0-face/theme.d.ts.map +1 -0
  36. package/dist/2-core/ds-banner.d.ts +1 -0
  37. package/dist/2-core/ds-banner.d.ts.map +1 -0
  38. package/dist/2-core/ds-banner.js +2 -0
  39. package/dist/2-core/ds-button.d.ts +2 -7
  40. package/dist/2-core/ds-button.d.ts.map +1 -1
  41. package/dist/2-core/ds-button.js +12 -14
  42. package/dist/2-core/ds-cycle.d.ts +2 -0
  43. package/dist/2-core/ds-cycle.d.ts.map +1 -1
  44. package/dist/2-core/ds-cycle.js +80 -34
  45. package/dist/2-core/{ds-year.d.ts → ds-date.d.ts} +4 -4
  46. package/dist/2-core/ds-date.d.ts.map +1 -0
  47. package/dist/2-core/{ds-year.js → ds-date.js} +5 -5
  48. package/dist/2-core/ds-icon.js +4 -4
  49. package/dist/2-core/ds-input.d.ts +1 -0
  50. package/dist/2-core/ds-input.d.ts.map +1 -0
  51. package/dist/2-core/ds-input.js +1 -0
  52. package/dist/2-core/ds-text.d.ts +2 -0
  53. package/dist/2-core/ds-text.d.ts.map +1 -1
  54. package/dist/2-core/ds-text.js +26 -3
  55. package/dist/2-core/ds-tooltip.d.ts +1 -1
  56. package/dist/2-core/ds-tooltip.d.ts.map +1 -1
  57. package/dist/2-core/ds-tooltip.js +9 -13
  58. package/dist/3-unit/ds-list.d.ts.map +1 -1
  59. package/dist/3-unit/ds-list.js +3 -0
  60. package/dist/3-unit/{ds-doublenav.d.ts → ds-portfolio-doublenav.d.ts} +4 -4
  61. package/dist/3-unit/ds-portfolio-doublenav.d.ts.map +1 -0
  62. package/dist/3-unit/{ds-doublenav.js → ds-portfolio-doublenav.js} +4 -4
  63. package/dist/3-unit/{ds-panel.d.ts → ds-portfolio-panel.d.ts} +3 -3
  64. package/dist/3-unit/ds-portfolio-panel.d.ts.map +1 -0
  65. package/dist/3-unit/{ds-panel.js → ds-portfolio-panel.js} +3 -3
  66. package/dist/3-unit/{ds-singlenav.d.ts → ds-portfolio-singlenav.d.ts} +4 -4
  67. package/dist/3-unit/ds-portfolio-singlenav.d.ts.map +1 -0
  68. package/dist/3-unit/{ds-singlenav.js → ds-portfolio-singlenav.js} +7 -7
  69. package/dist/3-unit/ds-row.js +4 -4
  70. package/dist/3-unit/ds-table.d.ts.map +1 -1
  71. package/dist/3-unit/ds-table.js +5 -6
  72. package/dist/4-page/ds-grid.d.ts +0 -7
  73. package/dist/4-page/ds-grid.d.ts.map +1 -1
  74. package/dist/4-page/ds-grid.js +9 -54
  75. package/dist/4-page/ds-layout.d.ts +1 -1
  76. package/dist/4-page/ds-layout.d.ts.map +1 -1
  77. package/dist/4-page/ds-layout.js +126 -17
  78. package/dist/ds-one.bundle.js +2515 -4132
  79. package/dist/ds-one.bundle.js.map +4 -4
  80. package/dist/ds-one.bundle.min.js +245 -787
  81. package/dist/ds-one.bundle.min.js.map +4 -4
  82. package/dist/index.d.ts +16 -26
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +32 -34
  85. package/package.json +8 -9
  86. package/DS1/0-face/2025-04-23-language.ts +0 -4
  87. package/DS1/2-core/ds-article.ts +0 -454
  88. package/DS1/2-core/ds-attributes.ts +0 -155
  89. package/DS1/2-core/ds-downloadcv.ts +0 -146
  90. package/DS1/2-core/ds-header.ts +0 -82
  91. package/DS1/2-core/ds-home.ts +0 -168
  92. package/DS1/2-core/ds-link.ts +0 -121
  93. package/DS1/2-core/ds-markdown.ts +0 -252
  94. package/DS1/2-core/ds-price.ts +0 -108
  95. package/DS1/2-core/ds-squarecircle.ts +0 -155
  96. package/DS1/2-core/ds-title.ts +0 -139
  97. package/DS1/2-core/ds-viewtoggle.ts +0 -83
  98. package/DS1/3-unit/ds-doublenav.ts +0 -106
  99. package/DS1/3-unit/ds-panel.ts +0 -27
  100. package/DS1/3-unit/ds-singlenav.ts +0 -79
  101. package/DS1/utils/cdn-loader.ts +0 -208
  102. package/DS1/utils/keys.json +0 -41
  103. package/DS1/utils/pricing.ts +0 -24
  104. package/DS1/utils/scroll.ts +0 -184
  105. package/DS1/utils/settings.ts +0 -23
  106. package/DS1/utils/viewMode.ts +0 -55
  107. package/dist/0-face/2025-04-23-device.d.ts.map +0 -1
  108. package/dist/0-face/2025-04-23-language.d.ts +0 -1
  109. package/dist/0-face/2025-04-23-language.d.ts.map +0 -1
  110. package/dist/0-face/2025-04-23-language.js +0 -3
  111. package/dist/2-core/article-v1.d.ts +0 -129
  112. package/dist/2-core/article-v1.d.ts.map +0 -1
  113. package/dist/2-core/article-v1.js +0 -361
  114. package/dist/2-core/attributes-v1.d.ts +0 -47
  115. package/dist/2-core/attributes-v1.d.ts.map +0 -1
  116. package/dist/2-core/attributes-v1.js +0 -128
  117. package/dist/2-core/cycle-v1.d.ts +0 -66
  118. package/dist/2-core/cycle-v1.d.ts.map +0 -1
  119. package/dist/2-core/cycle-v1.js +0 -586
  120. package/dist/2-core/downloadcv-v1.d.ts +0 -58
  121. package/dist/2-core/downloadcv-v1.d.ts.map +0 -1
  122. package/dist/2-core/downloadcv-v1.js +0 -119
  123. package/dist/2-core/ds-article.d.ts +0 -129
  124. package/dist/2-core/ds-article.d.ts.map +0 -1
  125. package/dist/2-core/ds-article.js +0 -361
  126. package/dist/2-core/ds-attributes.d.ts +0 -47
  127. package/dist/2-core/ds-attributes.d.ts.map +0 -1
  128. package/dist/2-core/ds-attributes.js +0 -128
  129. package/dist/2-core/ds-button.figma.d.ts +0 -2
  130. package/dist/2-core/ds-button.figma.d.ts.map +0 -1
  131. package/dist/2-core/ds-button.figma.js +0 -6
  132. package/dist/2-core/ds-downloadcv.d.ts +0 -58
  133. package/dist/2-core/ds-downloadcv.d.ts.map +0 -1
  134. package/dist/2-core/ds-downloadcv.js +0 -119
  135. package/dist/2-core/ds-header.d.ts +0 -28
  136. package/dist/2-core/ds-header.d.ts.map +0 -1
  137. package/dist/2-core/ds-header.js +0 -66
  138. package/dist/2-core/ds-home.d.ts +0 -26
  139. package/dist/2-core/ds-home.d.ts.map +0 -1
  140. package/dist/2-core/ds-home.js +0 -148
  141. package/dist/2-core/ds-link.d.ts +0 -35
  142. package/dist/2-core/ds-link.d.ts.map +0 -1
  143. package/dist/2-core/ds-link.js +0 -85
  144. package/dist/2-core/ds-markdown.d.ts +0 -7
  145. package/dist/2-core/ds-markdown.d.ts.map +0 -1
  146. package/dist/2-core/ds-markdown.js +0 -240
  147. package/dist/2-core/ds-price.d.ts +0 -46
  148. package/dist/2-core/ds-price.d.ts.map +0 -1
  149. package/dist/2-core/ds-price.js +0 -72
  150. package/dist/2-core/ds-squarecircle.d.ts +0 -50
  151. package/dist/2-core/ds-squarecircle.d.ts.map +0 -1
  152. package/dist/2-core/ds-squarecircle.js +0 -133
  153. package/dist/2-core/ds-title.d.ts +0 -50
  154. package/dist/2-core/ds-title.d.ts.map +0 -1
  155. package/dist/2-core/ds-title.js +0 -103
  156. package/dist/2-core/ds-viewtoggle.d.ts +0 -27
  157. package/dist/2-core/ds-viewtoggle.d.ts.map +0 -1
  158. package/dist/2-core/ds-viewtoggle.js +0 -49
  159. package/dist/2-core/ds-year.d.ts.map +0 -1
  160. package/dist/2-core/header-v1.d.ts +0 -28
  161. package/dist/2-core/header-v1.d.ts.map +0 -1
  162. package/dist/2-core/header-v1.js +0 -66
  163. package/dist/2-core/home-v1.d.ts +0 -26
  164. package/dist/2-core/home-v1.d.ts.map +0 -1
  165. package/dist/2-core/home-v1.js +0 -148
  166. package/dist/2-core/icon-v1.d.ts +0 -28
  167. package/dist/2-core/icon-v1.d.ts.map +0 -1
  168. package/dist/2-core/icon-v1.js +0 -297
  169. package/dist/2-core/link-v1.d.ts +0 -35
  170. package/dist/2-core/link-v1.d.ts.map +0 -1
  171. package/dist/2-core/link-v1.js +0 -85
  172. package/dist/2-core/markdown-v1.d.ts +0 -7
  173. package/dist/2-core/markdown-v1.d.ts.map +0 -1
  174. package/dist/2-core/markdown-v1.js +0 -240
  175. package/dist/2-core/price-v1.d.ts +0 -46
  176. package/dist/2-core/price-v1.d.ts.map +0 -1
  177. package/dist/2-core/price-v1.js +0 -72
  178. package/dist/2-core/squarecircle-v1.d.ts +0 -50
  179. package/dist/2-core/squarecircle-v1.d.ts.map +0 -1
  180. package/dist/2-core/squarecircle-v1.js +0 -133
  181. package/dist/2-core/text-v1.d.ts +0 -48
  182. package/dist/2-core/text-v1.d.ts.map +0 -1
  183. package/dist/2-core/text-v1.js +0 -83
  184. package/dist/2-core/title-v1.d.ts +0 -50
  185. package/dist/2-core/title-v1.d.ts.map +0 -1
  186. package/dist/2-core/title-v1.js +0 -103
  187. package/dist/2-core/tooltip-v1.d.ts +0 -39
  188. package/dist/2-core/tooltip-v1.d.ts.map +0 -1
  189. package/dist/2-core/tooltip-v1.js +0 -145
  190. package/dist/2-core/viewtoggle-v1.d.ts +0 -27
  191. package/dist/2-core/viewtoggle-v1.d.ts.map +0 -1
  192. package/dist/2-core/viewtoggle-v1.js +0 -49
  193. package/dist/2-core/year-v1.d.ts +0 -16
  194. package/dist/2-core/year-v1.d.ts.map +0 -1
  195. package/dist/2-core/year-v1.js +0 -21
  196. package/dist/3-unit/ds-doublenav.d.ts.map +0 -1
  197. package/dist/3-unit/ds-panel.d.ts.map +0 -1
  198. package/dist/3-unit/ds-singlenav.d.ts.map +0 -1
  199. package/dist/utils/cdn-loader.d.ts +0 -19
  200. package/dist/utils/cdn-loader.d.ts.map +0 -1
  201. package/dist/utils/cdn-loader.js +0 -142
  202. package/dist/utils/keys.json +0 -41
  203. package/dist/utils/language.d.ts.map +0 -1
  204. package/dist/utils/pricing.d.ts +0 -8
  205. package/dist/utils/pricing.d.ts.map +0 -1
  206. package/dist/utils/pricing.js +0 -14
  207. package/dist/utils/scroll.d.ts +0 -34
  208. package/dist/utils/scroll.d.ts.map +0 -1
  209. package/dist/utils/scroll.js +0 -140
  210. package/dist/utils/settings.d.ts +0 -9
  211. package/dist/utils/settings.d.ts.map +0 -1
  212. package/dist/utils/settings.js +0 -14
  213. package/dist/utils/theme.d.ts.map +0 -1
  214. package/dist/utils/viewMode.d.ts +0 -14
  215. package/dist/utils/viewMode.d.ts.map +0 -1
  216. package/dist/utils/viewMode.js +0 -46
  217. /package/DS1/{utils → 0-face}/theme.ts +0 -0
  218. /package/DS1/{x Icon → x-icon}/1x.svg +0 -0
  219. /package/DS1/{x Icon → x-icon}/1xdots.svg +0 -0
  220. /package/DS1/{x Icon → x-icon}/1xgrid.svg +0 -0
  221. /package/DS1/{x Icon → x-icon}/1xlines.svg +0 -0
  222. /package/DS1/{x Icon → x-icon}/2x.svg +0 -0
  223. /package/DS1/{x Icon → x-icon}/2xdots.svg +0 -0
  224. /package/DS1/{x Icon → x-icon}/2xgrid.svg +0 -0
  225. /package/DS1/{x Icon → x-icon}/2xlines.svg +0 -0
  226. /package/DS1/{x Icon → x-icon}/big.svg +0 -0
  227. /package/DS1/{x Icon → x-icon}/blank.svg +0 -0
  228. /package/DS1/{x Icon → x-icon}/check.svg +0 -0
  229. /package/DS1/{x Icon → x-icon}/close.svg +0 -0
  230. /package/DS1/{x Icon → x-icon}/collapse.svg +0 -0
  231. /package/DS1/{x Icon → x-icon}/color.svg +0 -0
  232. /package/DS1/{x Icon → x-icon}/column.svg +0 -0
  233. /package/DS1/{x Icon → x-icon}/default.svg +0 -0
  234. /package/DS1/{x Icon → x-icon}/delete.svg +0 -0
  235. /package/DS1/{x Icon → x-icon}/do.svg +0 -0
  236. /package/DS1/{x Icon → x-icon}/down.svg +0 -0
  237. /package/DS1/{x Icon → x-icon}/duplicate.svg +0 -0
  238. /package/DS1/{x Icon → x-icon}/email.svg +0 -0
  239. /package/DS1/{x Icon → x-icon}/expand.svg +0 -0
  240. /package/DS1/{x Icon → x-icon}/gallery.svg +0 -0
  241. /package/DS1/{x Icon → x-icon}/group.svg +0 -0
  242. /package/DS1/{x Icon → x-icon}/head.svg +0 -0
  243. /package/DS1/{x Icon → x-icon}/icon.svg +0 -0
  244. /package/DS1/{x Icon → x-icon}/left.svg +0 -0
  245. /package/DS1/{x Icon → x-icon}/lock.svg +0 -0
  246. /package/DS1/{x Icon → x-icon}/mic.svg +0 -0
  247. /package/DS1/{x Icon → x-icon}/minimize.svg +0 -0
  248. /package/DS1/{x Icon → x-icon}/more.svg +0 -0
  249. /package/DS1/{x Icon → x-icon}/note.svg +0 -0
  250. /package/DS1/{x Icon → x-icon}/open.svg +0 -0
  251. /package/DS1/{x Icon → x-icon}/page.svg +0 -0
  252. /package/DS1/{x Icon → x-icon}/plus.svg +0 -0
  253. /package/DS1/{x Icon → x-icon}/rewind.svg +0 -0
  254. /package/DS1/{x Icon → x-icon}/right.svg +0 -0
  255. /package/DS1/{x Icon/row..svg → x-icon/row.svg} +0 -0
  256. /package/DS1/{x Icon → x-icon}/search.svg +0 -0
  257. /package/DS1/{x Icon → x-icon}/see.svg +0 -0
  258. /package/DS1/{x Icon → x-icon}/star.svg +0 -0
  259. /package/DS1/{x Icon → x-icon}/title.svg +0 -0
  260. /package/DS1/{x Icon → x-icon}/undo.svg +0 -0
  261. /package/DS1/{x Icon → x-icon}/ungroup.svg +0 -0
  262. /package/DS1/{x Icon → x-icon}/unhead.svg +0 -0
  263. /package/DS1/{x Icon → x-icon}/unicon.svg +0 -0
  264. /package/DS1/{x Icon → x-icon}/unlock.svg +0 -0
  265. /package/DS1/{x Icon → x-icon}/unmic.svg +0 -0
  266. /package/DS1/{x Icon → x-icon}/unsee.svg +0 -0
  267. /package/DS1/{x Icon → x-icon}/unstar.svg +0 -0
  268. /package/DS1/{x Icon → x-icon}/untitle.svg +0 -0
  269. /package/DS1/{x Icon → x-icon}/up.svg +0 -0
  270. /package/dist/{utils → 0-face}/theme.d.ts +0 -0
  271. /package/dist/{utils → 0-face}/theme.js +0 -0
@@ -93,9 +93,14 @@ export function initDeviceDetection(): DeviceInfo {
93
93
  const actualWidth = deviceInfo.screenWidth;
94
94
  const scalingFactor = actualWidth / designWidth;
95
95
 
96
- // Set CSS custom property for scaling
96
+ // Set CSS custom property for scaling on html element
97
97
  document.documentElement.style.setProperty(
98
- "--scaling-factor-mobile",
98
+ "--sf",
99
+ scalingFactor.toFixed(3)
100
+ );
101
+ // Also set --sf for backwards compatibility
102
+ document.documentElement.style.setProperty(
103
+ "--sf",
99
104
  scalingFactor.toFixed(3)
100
105
  );
101
106
 
@@ -105,10 +110,9 @@ export function initDeviceDetection(): DeviceInfo {
105
110
  } else {
106
111
  // Desktop - no scaling
107
112
  if (typeof document !== "undefined") {
108
- document.documentElement.style.setProperty(
109
- "--scaling-factor-mobile",
110
- "1"
111
- );
113
+ document.documentElement.style.setProperty("--sf", "1");
114
+ // Also set --sf for backwards compatibility
115
+ document.documentElement.style.setProperty("--sf", "1");
112
116
  }
113
117
  console.log(
114
118
  `[DS one] Desktop device detected (${deviceInfo.screenWidth}x${deviceInfo.screenHeight})`
@@ -8,21 +8,22 @@ type TranslationData = {
8
8
 
9
9
  type TranslationMap = Record<string, TranslationData>;
10
10
 
11
- // Import the JSON directly to ensure it's bundled
12
- import translationKeys from "./keys.json";
11
+ // Bundled translations (keys.json may not exist - will use external translations if not available)
12
+ // This is a fallback for when external translations aren't loaded
13
+ let translationKeys: TranslationMap = {};
13
14
 
14
15
  // Primary language list – prioritise the 10 requested languages when cycling
15
16
  const LANGUAGE_PRIORITY_ORDER = [
16
17
  "da",
17
- "nb",
18
- "sv",
19
- "pt",
18
+ "de",
19
+ "en",
20
20
  "es",
21
- "zh",
22
- "ko",
21
+ "fr",
22
+ "it",
23
23
  "ja",
24
- "en",
25
- "de",
24
+ "pt",
25
+ "sv",
26
+ "zh",
26
27
  ] as const;
27
28
 
28
29
  const LANGUAGE_PRIORITY_LOOKUP = new Map<string, number>(
@@ -33,27 +34,27 @@ const LANGUAGE_PRIORITY_LOOKUP = new Map<string, number>(
33
34
  const FALLBACK_LANGUAGE_NAMES: Record<string, string> = {
34
35
  da: "Danish",
35
36
  "da-dk": "Danish",
36
- nb: "Norwegian",
37
- "nb-no": "Norwegian",
38
- sv: "Swedish",
39
- "sv-se": "Swedish",
40
37
  de: "German",
41
38
  "de-de": "German",
42
39
  en: "English",
43
40
  "en-us": "English",
44
- pt: "Portuguese",
45
- "pt-pt": "Portuguese",
46
- "pt-br": "Portuguese (Brazil)",
47
41
  es: "Spanish",
48
42
  "es-es": "Spanish",
49
- "es-mx": "Spanish (Mexico)",
50
- zh: "Chinese",
51
- "zh-hans": "Chinese (Simplified)",
52
- "zh-hant": "Chinese (Traditional)",
43
+ fr: "French",
44
+ "fr-fr": "French",
45
+ it: "Italian",
46
+ "it-it": "Italian",
53
47
  ja: "Japanese",
54
48
  "ja-jp": "Japanese",
55
- ko: "Korean",
56
- "ko-kr": "Korean",
49
+ pt: "Portuguese",
50
+ "pt-pt": "Portuguese",
51
+ sv: "Swedish",
52
+ "sv-se": "Swedish",
53
+ zh: "Chinese",
54
+ "zh-cn": "Chinese",
55
+ "zh-tw": "Chinese",
56
+ "zh-hans": "Chinese",
57
+ "zh-hant": "Chinese",
57
58
  };
58
59
 
59
60
  const DISPLAY_NAME_CACHE = new Map<string, Intl.DisplayNames>();
@@ -67,6 +68,187 @@ declare global {
67
68
  }
68
69
  }
69
70
 
71
+ // CDN Loader: Automatically detects and loads translation JSON files
72
+ // for CDN users who want to use external translations
73
+
74
+ const DEFAULT_TRANSLATION_FILE = "./translations.json";
75
+ let loadAttempted = false;
76
+
77
+ function normalizeCandidate(path: string): string | null {
78
+ if (!path) {
79
+ return null;
80
+ }
81
+ const trimmed = path.trim();
82
+ if (!trimmed) {
83
+ return null;
84
+ }
85
+
86
+ if (
87
+ trimmed.startsWith("./") ||
88
+ trimmed.startsWith("../") ||
89
+ trimmed.startsWith("/") ||
90
+ /^https?:\/\//i.test(trimmed)
91
+ ) {
92
+ return trimmed;
93
+ }
94
+
95
+ return `./${trimmed}`;
96
+ }
97
+
98
+ function findAttributeCandidate(): string | null {
99
+ if (typeof document === "undefined") {
100
+ return null;
101
+ }
102
+
103
+ const scriptWithAttribute = document.querySelector(
104
+ "script[data-ds-one-translations]"
105
+ );
106
+ const scriptCandidate = scriptWithAttribute?.getAttribute(
107
+ "data-ds-one-translations"
108
+ );
109
+ if (scriptCandidate) {
110
+ return scriptCandidate;
111
+ }
112
+
113
+ const metaCandidate = document
114
+ .querySelector('meta[name="ds-one:translations"]')
115
+ ?.getAttribute("content");
116
+ if (metaCandidate) {
117
+ return metaCandidate;
118
+ }
119
+
120
+ const linkCandidate = document
121
+ .querySelector('link[rel="ds-one-translations"]')
122
+ ?.getAttribute("href");
123
+ if (linkCandidate) {
124
+ return linkCandidate;
125
+ }
126
+
127
+ return null;
128
+ }
129
+
130
+ function resolveTranslationSources(): string[] {
131
+ const candidates: string[] = [];
132
+
133
+ const windowCandidate =
134
+ typeof window !== "undefined" ? window.DS_ONE_TRANSLATIONS_FILE : null;
135
+ const attributeCandidate = findAttributeCandidate();
136
+
137
+ // Only use explicitly configured paths, or the single default
138
+ const windowNormalized = normalizeCandidate(windowCandidate ?? "");
139
+ if (windowNormalized) {
140
+ candidates.push(windowNormalized);
141
+ }
142
+
143
+ const attrNormalized = normalizeCandidate(attributeCandidate ?? "");
144
+ if (attrNormalized && !candidates.includes(attrNormalized)) {
145
+ candidates.push(attrNormalized);
146
+ }
147
+
148
+ // Only try default if no explicit path was configured
149
+ if (candidates.length === 0) {
150
+ candidates.push(DEFAULT_TRANSLATION_FILE);
151
+ }
152
+
153
+ return candidates;
154
+ }
155
+
156
+ function validateTranslationMap(
157
+ candidate: unknown
158
+ ): candidate is TranslationMap {
159
+ if (!candidate || typeof candidate !== "object") {
160
+ return false;
161
+ }
162
+
163
+ return Object.values(candidate).every(
164
+ (entry) => entry && typeof entry === "object"
165
+ );
166
+ }
167
+
168
+ async function fetchTranslationFile(
169
+ source: string
170
+ ): Promise<TranslationMap | null> {
171
+ try {
172
+ const response = await fetch(source);
173
+
174
+ if (!response.ok) {
175
+ // 404 is expected if no translations file exists - don't log as error
176
+ return null;
177
+ }
178
+
179
+ const translations = await response.json();
180
+
181
+ if (!validateTranslationMap(translations)) {
182
+ console.warn(
183
+ `[DS one] Invalid translation format in ${source}. Expected object with language codes as keys.`
184
+ );
185
+ return null;
186
+ }
187
+
188
+ const languages = Object.keys(translations);
189
+ if (languages.length === 0) {
190
+ console.warn(`[DS one] No languages found in ${source}`);
191
+ return null;
192
+ }
193
+
194
+ return translations;
195
+ } catch {
196
+ // Silently fail - file likely doesn't exist or isn't valid JSON
197
+ return null;
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Attempts to load translations from a JSON file in the same directory
203
+ */
204
+ async function loadExternalTranslations(): Promise<boolean> {
205
+ // Only attempt once
206
+ if (loadAttempted) {
207
+ return false;
208
+ }
209
+ loadAttempted = true;
210
+
211
+ if (typeof window === "undefined") {
212
+ return false;
213
+ }
214
+
215
+ // Check if translations are already loaded (e.g., by the application)
216
+ if (
217
+ window.DS_ONE_TRANSLATIONS &&
218
+ Object.keys(window.DS_ONE_TRANSLATIONS).length > 0
219
+ ) {
220
+ console.log(
221
+ `[DS one] Translations already loaded (${Object.keys(window.DS_ONE_TRANSLATIONS).length} languages), skipping auto-load`
222
+ );
223
+ return true;
224
+ }
225
+
226
+ const sources = resolveTranslationSources();
227
+
228
+ for (const source of sources) {
229
+ const translations = await fetchTranslationFile(source);
230
+ if (!translations) {
231
+ continue;
232
+ }
233
+
234
+ window.DS_ONE_TRANSLATIONS = translations;
235
+
236
+ const languages = Object.keys(translations);
237
+ console.log(
238
+ `[DS one] External translations loaded from ${source}: ${languages.length} language(s) – ${languages.join(", ")}`
239
+ );
240
+
241
+ window.dispatchEvent(new CustomEvent("translations-ready"));
242
+ return true;
243
+ }
244
+
245
+ console.info(
246
+ `[DS one] No external translations found at ${sources[0] ?? DEFAULT_TRANSLATION_FILE}. Using bundled translations.`
247
+ );
248
+
249
+ return false;
250
+ }
251
+
70
252
  // Get translation data - prioritize external, fall back to bundled
71
253
  function getTranslationData(): TranslationMap {
72
254
  // Check for externally loaded translations first (CDN usage)
@@ -81,9 +263,6 @@ function getTranslationData(): TranslationMap {
81
263
  // Cached translation data - use getter to always get fresh data
82
264
  let translationData = getTranslationData();
83
265
 
84
- type NotionCache = Map<string, string>;
85
-
86
- const notionStore = new Map<LanguageCode, NotionCache>();
87
266
  const defaultLanguage: LanguageCode = "en";
88
267
 
89
268
  function extractPrimarySubtag(code: LanguageCode): string {
@@ -227,33 +406,30 @@ export function getLanguageDisplayName(
227
406
  const BROWSER_LANGUAGE_PREFERENCES: Record<string, LanguageCode> = {
228
407
  da: "da",
229
408
  "da-dk": "da",
230
- no: "nb",
231
- nb: "nb",
232
- "nb-no": "nb",
233
- nn: "nn",
234
- "nn-no": "nn",
235
- sv: "sv",
236
- "sv-se": "sv",
237
- pt: "pt",
238
- "pt-pt": "pt",
239
- "pt-br": "pt",
409
+ de: "de",
410
+ "de-de": "de",
411
+ en: "en",
412
+ "en-us": "en",
413
+ "en-gb": "en",
240
414
  es: "es",
241
415
  "es-es": "es",
242
416
  "es-mx": "es",
417
+ fr: "fr",
418
+ "fr-fr": "fr",
419
+ it: "it",
420
+ "it-it": "it",
421
+ ja: "ja",
422
+ "ja-jp": "ja",
423
+ pt: "pt",
424
+ "pt-pt": "pt",
425
+ "pt-br": "pt",
426
+ sv: "sv",
427
+ "sv-se": "sv",
243
428
  zh: "zh",
244
429
  "zh-cn": "zh",
245
430
  "zh-hans": "zh",
246
431
  "zh-tw": "zh",
247
432
  "zh-hant": "zh",
248
- ko: "ko",
249
- "ko-kr": "ko",
250
- ja: "ja",
251
- "ja-jp": "ja",
252
- en: "en",
253
- "en-us": "en",
254
- "en-gb": "en",
255
- de: "de",
256
- "de-de": "de",
257
433
  };
258
434
 
259
435
  function resolvePreferredLanguage(languageTag: string): LanguageCode | null {
@@ -329,6 +505,19 @@ export const currentLanguage = {
329
505
  },
330
506
  };
331
507
 
508
+ // Auto-load translations when this module is imported (for CDN bundle)
509
+ if (typeof window !== "undefined") {
510
+ // Wait a bit to ensure the DOM is ready
511
+ if (document.readyState === "loading") {
512
+ document.addEventListener("DOMContentLoaded", () => {
513
+ loadExternalTranslations();
514
+ });
515
+ } else {
516
+ // DOM is already ready
517
+ loadExternalTranslations();
518
+ }
519
+ }
520
+
332
521
  // Listen for external translations being loaded
333
522
  if (typeof window !== "undefined") {
334
523
  window.addEventListener("translations-ready", () => {
@@ -337,7 +526,6 @@ if (typeof window !== "undefined") {
337
526
 
338
527
  // Dispatch that translations are loaded
339
528
  window.dispatchEvent(new CustomEvent("translations-loaded"));
340
- (window as any).notionDataLoaded = true;
341
529
 
342
530
  // Dispatch language-changed to update all components
343
531
  const currentLang = currentLanguage.value;
@@ -356,7 +544,6 @@ if (typeof window !== "undefined") {
356
544
  setTimeout(() => {
357
545
  // Since we directly imported the data, just dispatch the events
358
546
  window.dispatchEvent(new CustomEvent("translations-loaded"));
359
- (window as any).notionDataLoaded = true;
360
547
 
361
548
  // Also dispatch language-changed with the current language
362
549
  const currentLang = currentLanguage.value;
@@ -384,7 +571,9 @@ export function translate(key: string): string {
384
571
  return translationData[defaultLanguage][key];
385
572
  }
386
573
 
387
- console.warn(`[translate] No translation found for key "${key}"`);
574
+ console.warn(
575
+ `[DS one (Internationalization)] No translation found for key "${key}"`
576
+ );
388
577
  return key;
389
578
  }
390
579
 
@@ -417,51 +606,6 @@ export function getText(key: string): string {
417
606
  return translate(key);
418
607
  }
419
608
 
420
- // Get text from translation data (async for compatibility)
421
- export async function getNotionText(
422
- key: string,
423
- language: LanguageCode = currentLanguage.value
424
- ): Promise<string | null> {
425
- if (!key) {
426
- return null;
427
- }
428
-
429
- if (!translationData || !translationData[language]) {
430
- return null;
431
- }
432
-
433
- const text = translationData[language][key];
434
- if (text) {
435
- return text;
436
- }
437
-
438
- // Fallback to English
439
- if (language !== defaultLanguage && translationData[defaultLanguage]?.[key]) {
440
- return translationData[defaultLanguage][key];
441
- }
442
-
443
- return null;
444
- }
445
-
446
- // Store Notion text (for dynamic updates)
447
- export function setNotionText(
448
- key: string,
449
- value: string,
450
- language: LanguageCode = currentLanguage.value
451
- ): void {
452
- if (!key) return;
453
-
454
- const bucket = getLanguageBucket(language);
455
- bucket.set(key, value);
456
- }
457
-
458
- function getLanguageBucket(language: LanguageCode): NotionCache {
459
- if (!notionStore.has(language)) {
460
- notionStore.set(language, new Map());
461
- }
462
- return notionStore.get(language)!;
463
- }
464
-
465
609
  // Get available languages - dynamically detect from loaded data
466
610
  export function getAvailableLanguages(): Promise<LanguageCode[]> {
467
611
  // Always get fresh translation data
@@ -0,0 +1,23 @@
1
+ import type { LanguageCode } from "./i18n";
2
+ import type { ThemeType } from "./theme";
3
+
4
+ export type Preferences = {
5
+ language?: LanguageCode;
6
+ theme?: ThemeType;
7
+ [key: string]: unknown;
8
+ };
9
+
10
+ export function savePreferences(preferences: Preferences): void {
11
+ if (typeof window === "undefined") {
12
+ return;
13
+ }
14
+
15
+ try {
16
+ const raw = window.localStorage?.getItem("ds-one:preferences");
17
+ const existing = raw ? (JSON.parse(raw) as Record<string, unknown>) : {};
18
+ const next = { ...existing, ...preferences };
19
+ window.localStorage?.setItem("ds-one:preferences", JSON.stringify(next));
20
+ } catch (error) {
21
+ console.warn("ds-one: unable to persist preferences", error);
22
+ }
23
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Currency label utilities for regional price display
3
+ *
4
+ * Note: This module provides currency symbols/labels based on language and region.
5
+ * Consider moving this functionality into i18n.ts as it's region/locale-related.
6
+ * Actual price values will be stored in a database or managed via Stripe.
7
+ */
8
+
9
+ import type { LanguageCode } from "./i18n";
10
+
11
+ type PriceLabelOptions = {
12
+ language: LanguageCode;
13
+ country?: string;
14
+ };
15
+
16
+ // Simple price label mapping based on language/country
17
+ const PRICE_LABELS: Record<string, string> = {
18
+ da: "kr.",
19
+ nb: "kr.",
20
+ sv: "kr.",
21
+ de: "€",
22
+ en: "$",
23
+ pt: "€",
24
+ es: "€",
25
+ zh: "¥",
26
+ ja: "¥",
27
+ ko: "₩",
28
+ };
29
+
30
+ export function getPriceLabel(options: PriceLabelOptions): string {
31
+ const { language, country } = options;
32
+
33
+ // If country is provided, try to map it to a currency
34
+ if (country) {
35
+ const countryUpper = country.toUpperCase();
36
+ // Add country-specific mappings if needed
37
+ if (countryUpper === "US" || countryUpper === "USA") {
38
+ return "$";
39
+ }
40
+ if (countryUpper === "GB" || countryUpper === "UK") {
41
+ return "£";
42
+ }
43
+ if (countryUpper === "JP" || countryUpper === "JPN") {
44
+ return "¥";
45
+ }
46
+ if (countryUpper === "CN" || countryUpper === "CHN") {
47
+ return "¥";
48
+ }
49
+ if (countryUpper === "KR" || countryUpper === "KOR") {
50
+ return "₩";
51
+ }
52
+ }
53
+
54
+ // Fall back to language-based mapping
55
+ const primaryLang = language.toLowerCase().split(/[-_]/)[0];
56
+ return PRICE_LABELS[primaryLang] || "$";
57
+ }