@inlang/paraglide-js 2.26.0 → 2.28.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 (225) hide show
  1. package/README.md +11 -1
  2. package/dist/bundler-plugins/per-locale-build/index.js +14 -7
  3. package/dist/bundler-plugins/per-locale-build/index.js.map +1 -1
  4. package/dist/bundler-plugins/per-locale-build/vite-environments.test.js +237 -4
  5. package/dist/bundler-plugins/per-locale-build/vite-environments.test.js.map +1 -1
  6. package/dist/bundler-plugins/unplugin.d.ts +1 -1
  7. package/dist/bundler-plugins/unplugin.js +79 -10
  8. package/dist/bundler-plugins/unplugin.js.map +1 -1
  9. package/dist/bundler-plugins/unplugin.test.js +334 -1
  10. package/dist/bundler-plugins/unplugin.test.js.map +1 -1
  11. package/dist/bundler-plugins/vite.js +2 -1
  12. package/dist/bundler-plugins/vite.js.map +1 -1
  13. package/dist/bundler-plugins/vite.test.js +1 -1
  14. package/dist/bundler-plugins/vite.test.js.map +1 -1
  15. package/dist/cli/commands/compile/command.js +13 -0
  16. package/dist/cli/commands/compile/command.js.map +1 -1
  17. package/dist/cli/commands/compile/command.test.js +87 -3
  18. package/dist/cli/commands/compile/command.test.js.map +1 -1
  19. package/dist/cli/commands/init/command.d.ts +5 -0
  20. package/dist/cli/commands/init/command.js +216 -44
  21. package/dist/cli/commands/init/command.js.map +1 -1
  22. package/dist/cli/commands/init/command.test.d.ts +1 -0
  23. package/dist/cli/commands/init/command.test.js +123 -0
  24. package/dist/cli/commands/init/command.test.js.map +1 -0
  25. package/dist/cli/defaults.js +2 -2
  26. package/dist/cli/defaults.js.map +1 -1
  27. package/dist/cli/init-options.d.ts +11 -0
  28. package/dist/cli/init-options.js +2 -0
  29. package/dist/cli/init-options.js.map +1 -0
  30. package/dist/cli/steps/add-inlang-check.d.ts +19 -0
  31. package/dist/cli/steps/add-inlang-check.js +60 -0
  32. package/dist/cli/steps/add-inlang-check.js.map +1 -0
  33. package/dist/cli/steps/add-inlang-check.test.d.ts +1 -0
  34. package/dist/cli/steps/add-inlang-check.test.js +88 -0
  35. package/dist/cli/steps/add-inlang-check.test.js.map +1 -0
  36. package/dist/cli/steps/add-message-lint.d.ts +34 -0
  37. package/dist/cli/steps/add-message-lint.js +105 -0
  38. package/dist/cli/steps/add-message-lint.js.map +1 -0
  39. package/dist/cli/steps/add-message-lint.test.d.ts +1 -0
  40. package/dist/cli/steps/add-message-lint.test.js +74 -0
  41. package/dist/cli/steps/add-message-lint.test.js.map +1 -0
  42. package/dist/cli/steps/add-vite-plugin.d.ts +18 -0
  43. package/dist/cli/steps/add-vite-plugin.js +34 -6
  44. package/dist/cli/steps/add-vite-plugin.js.map +1 -1
  45. package/dist/cli/steps/add-vite-plugin.test.js +25 -9
  46. package/dist/cli/steps/add-vite-plugin.test.js.map +1 -1
  47. package/dist/cli/steps/agents-md-suggestion.d.ts +11 -0
  48. package/dist/cli/steps/agents-md-suggestion.js +19 -0
  49. package/dist/cli/steps/agents-md-suggestion.js.map +1 -0
  50. package/dist/cli/steps/agents-md-suggestion.test.d.ts +1 -0
  51. package/dist/cli/steps/agents-md-suggestion.test.js +36 -0
  52. package/dist/cli/steps/agents-md-suggestion.test.js.map +1 -0
  53. package/dist/cli/steps/check-for-uncomitted-changes.d.ts +3 -0
  54. package/dist/cli/steps/check-for-uncomitted-changes.js +15 -3
  55. package/dist/cli/steps/check-for-uncomitted-changes.js.map +1 -1
  56. package/dist/cli/steps/detect-bundler.d.ts +1 -0
  57. package/dist/cli/steps/detect-bundler.js +7 -1
  58. package/dist/cli/steps/detect-bundler.js.map +1 -1
  59. package/dist/cli/steps/initialize-inlang-project.d.ts +4 -3
  60. package/dist/cli/steps/initialize-inlang-project.js +60 -157
  61. package/dist/cli/steps/initialize-inlang-project.js.map +1 -1
  62. package/dist/cli/steps/maybe-add-machine-translation.d.ts +4 -0
  63. package/dist/cli/steps/maybe-add-machine-translation.js +14 -8
  64. package/dist/cli/steps/maybe-add-machine-translation.js.map +1 -1
  65. package/dist/cli/steps/maybe-add-sherlock.d.ts +2 -0
  66. package/dist/cli/steps/maybe-add-sherlock.js +13 -7
  67. package/dist/cli/steps/maybe-add-sherlock.js.map +1 -1
  68. package/dist/cli/steps/maybe-add-sherlock.test.d.ts +1 -0
  69. package/dist/cli/steps/maybe-add-sherlock.test.js +44 -0
  70. package/dist/cli/steps/maybe-add-sherlock.test.js.map +1 -0
  71. package/dist/cli/steps/prompt-for-outdir.d.ts +2 -0
  72. package/dist/cli/steps/prompt-for-outdir.js +4 -0
  73. package/dist/cli/steps/prompt-for-outdir.js.map +1 -1
  74. package/dist/cli/steps/update-package-json.js +8 -13
  75. package/dist/cli/steps/update-package-json.js.map +1 -1
  76. package/dist/cli/steps/update-ts-config.d.ts +6 -0
  77. package/dist/cli/steps/update-ts-config.js +27 -4
  78. package/dist/cli/steps/update-ts-config.js.map +1 -1
  79. package/dist/cli/utils.d.ts +11 -0
  80. package/dist/cli/utils.js +21 -0
  81. package/dist/cli/utils.js.map +1 -1
  82. package/dist/cli/utils.test.d.ts +1 -0
  83. package/dist/cli/utils.test.js +16 -0
  84. package/dist/cli/utils.test.js.map +1 -0
  85. package/dist/compiler/compile-annotation.d.ts +48 -1
  86. package/dist/compiler/compile-annotation.js +243 -10
  87. package/dist/compiler/compile-annotation.js.map +1 -1
  88. package/dist/compiler/compile-bundle.d.ts +7 -2
  89. package/dist/compiler/compile-bundle.js +97 -3
  90. package/dist/compiler/compile-bundle.js.map +1 -1
  91. package/dist/compiler/compile-bundle.test.js +232 -19
  92. package/dist/compiler/compile-bundle.test.js.map +1 -1
  93. package/dist/compiler/compile-lifecycle.test.d.ts +1 -0
  94. package/dist/compiler/compile-lifecycle.test.js +69 -0
  95. package/dist/compiler/compile-lifecycle.test.js.map +1 -0
  96. package/dist/compiler/compile-local-variable.d.ts +3 -0
  97. package/dist/compiler/compile-local-variable.js +5 -2
  98. package/dist/compiler/compile-local-variable.js.map +1 -1
  99. package/dist/compiler/compile-local-variable.test.js +55 -15
  100. package/dist/compiler/compile-local-variable.test.js.map +1 -1
  101. package/dist/compiler/compile-message.d.ts +29 -1
  102. package/dist/compiler/compile-message.exact-number.test.d.ts +1 -0
  103. package/dist/compiler/compile-message.exact-number.test.js +786 -0
  104. package/dist/compiler/compile-message.exact-number.test.js.map +1 -0
  105. package/dist/compiler/compile-message.js +162 -66
  106. package/dist/compiler/compile-message.js.map +1 -1
  107. package/dist/compiler/compile-message.locals.test.d.ts +1 -0
  108. package/dist/compiler/compile-message.locals.test.js +498 -0
  109. package/dist/compiler/compile-message.locals.test.js.map +1 -0
  110. package/dist/compiler/compile-message.test.js +113 -22
  111. package/dist/compiler/compile-message.test.js.map +1 -1
  112. package/dist/compiler/compile-pattern.d.ts +5 -0
  113. package/dist/compiler/compile-pattern.js +6 -18
  114. package/dist/compiler/compile-pattern.js.map +1 -1
  115. package/dist/compiler/compile-pattern.test.js +169 -5
  116. package/dist/compiler/compile-pattern.test.js.map +1 -1
  117. package/dist/compiler/compile-project.js +5 -1
  118. package/dist/compiler/compile-project.js.map +1 -1
  119. package/dist/compiler/compile-project.test.js +1260 -185
  120. package/dist/compiler/compile-project.test.js.map +1 -1
  121. package/dist/compiler/compile.js +36 -17
  122. package/dist/compiler/compile.js.map +1 -1
  123. package/dist/compiler/compile.test.js +71 -1
  124. package/dist/compiler/compile.test.js.map +1 -1
  125. package/dist/compiler/compiler-options.d.ts +41 -0
  126. package/dist/compiler/compiler-options.js.map +1 -1
  127. package/dist/compiler/create-readme.d.ts +4 -1
  128. package/dist/compiler/create-readme.js +33 -125
  129. package/dist/compiler/create-readme.js.map +1 -1
  130. package/dist/compiler/emit-ts-declarations.interop.test.js +2 -2
  131. package/dist/compiler/emit-ts-declarations.interop.test.js.map +1 -1
  132. package/dist/compiler/emit-ts-declarations.test.js +4 -4
  133. package/dist/compiler/emit-ts-declarations.test.js.map +1 -1
  134. package/dist/compiler/index.d.ts +1 -1
  135. package/dist/compiler/index.js +1 -1
  136. package/dist/compiler/index.js.map +1 -1
  137. package/dist/compiler/input-alias.d.ts +21 -0
  138. package/dist/compiler/input-alias.js +42 -0
  139. package/dist/compiler/input-alias.js.map +1 -0
  140. package/dist/compiler/input-alias.test.d.ts +1 -0
  141. package/dist/compiler/input-alias.test.js +58 -0
  142. package/dist/compiler/input-alias.test.js.map +1 -0
  143. package/dist/compiler/message-locals.d.ts +46 -0
  144. package/dist/compiler/message-locals.js +129 -0
  145. package/dist/compiler/message-locals.js.map +1 -0
  146. package/dist/compiler/output-structure/locale-modules.js +7 -1
  147. package/dist/compiler/output-structure/locale-modules.js.map +1 -1
  148. package/dist/compiler/output-structure/locale-modules.test.js +35 -0
  149. package/dist/compiler/output-structure/locale-modules.test.js.map +1 -1
  150. package/dist/compiler/output-structure/message-modules.js +6 -4
  151. package/dist/compiler/output-structure/message-modules.js.map +1 -1
  152. package/dist/compiler/output-structure/message-modules.test.js +35 -0
  153. package/dist/compiler/output-structure/message-modules.test.js.map +1 -1
  154. package/dist/compiler/registry.d.ts +12 -0
  155. package/dist/compiler/registry.js +158 -31
  156. package/dist/compiler/registry.js.map +1 -1
  157. package/dist/compiler/registry.test.d.ts +1 -0
  158. package/dist/compiler/registry.test.js +291 -0
  159. package/dist/compiler/registry.test.js.map +1 -0
  160. package/dist/compiler/runtime/create-runtime.d.ts +2 -0
  161. package/dist/compiler/runtime/create-runtime.js +9 -0
  162. package/dist/compiler/runtime/create-runtime.js.map +1 -1
  163. package/dist/compiler/runtime/extract-locale-from-cookie.js +8 -1
  164. package/dist/compiler/runtime/extract-locale-from-cookie.js.map +1 -1
  165. package/dist/compiler/runtime/extract-locale-from-cookie.test.js +26 -0
  166. package/dist/compiler/runtime/extract-locale-from-cookie.test.js.map +1 -1
  167. package/dist/compiler/runtime/extract-locale-from-header.js +47 -5
  168. package/dist/compiler/runtime/extract-locale-from-header.js.map +1 -1
  169. package/dist/compiler/runtime/extract-locale-from-header.test.js +58 -0
  170. package/dist/compiler/runtime/extract-locale-from-header.test.js.map +1 -1
  171. package/dist/compiler/runtime/get-locale.js +7 -1
  172. package/dist/compiler/runtime/get-locale.js.map +1 -1
  173. package/dist/compiler/runtime/get-locale.test.js +95 -0
  174. package/dist/compiler/runtime/get-locale.test.js.map +1 -1
  175. package/dist/compiler/runtime/localize-href.d.ts +8 -0
  176. package/dist/compiler/runtime/localize-href.js +60 -0
  177. package/dist/compiler/runtime/localize-href.js.map +1 -1
  178. package/dist/compiler/runtime/localize-href.test.js +227 -1
  179. package/dist/compiler/runtime/localize-href.test.js.map +1 -1
  180. package/dist/compiler/runtime/set-locale.js +24 -6
  181. package/dist/compiler/runtime/set-locale.js.map +1 -1
  182. package/dist/compiler/runtime/set-locale.test.js +182 -0
  183. package/dist/compiler/runtime/set-locale.test.js.map +1 -1
  184. package/dist/compiler/runtime/type.d.ts +2 -0
  185. package/dist/compiler/runtime/type.js.map +1 -1
  186. package/dist/compiler/runtime/type.test.js +34 -0
  187. package/dist/compiler/runtime/type.test.js.map +1 -1
  188. package/dist/compiler/runtime/variables.d.ts +2 -0
  189. package/dist/compiler/runtime/variables.js +7 -0
  190. package/dist/compiler/runtime/variables.js.map +1 -1
  191. package/dist/compiler/server/create-server-file.js +24 -0
  192. package/dist/compiler/server/create-server-file.js.map +1 -1
  193. package/dist/compiler/server/create-server-file.test.js +8 -2
  194. package/dist/compiler/server/create-server-file.test.js.map +1 -1
  195. package/dist/compiler/server/middleware.d.ts +8 -2
  196. package/dist/compiler/server/middleware.js +204 -11
  197. package/dist/compiler/server/middleware.js.map +1 -1
  198. package/dist/compiler/server/middleware.test.js +750 -3
  199. package/dist/compiler/server/middleware.test.js.map +1 -1
  200. package/dist/eslint/index.d.ts +4 -0
  201. package/dist/eslint/index.js +8 -0
  202. package/dist/eslint/index.js.map +1 -0
  203. package/dist/eslint/no-dynamic-message-access.d.ts +3 -0
  204. package/dist/eslint/no-dynamic-message-access.js +150 -0
  205. package/dist/eslint/no-dynamic-message-access.js.map +1 -0
  206. package/dist/eslint/no-dynamic-message-access.test.d.ts +1 -0
  207. package/dist/eslint/no-dynamic-message-access.test.js +126 -0
  208. package/dist/eslint/no-dynamic-message-access.test.js.map +1 -0
  209. package/dist/eslint/oxlint.test.d.ts +1 -0
  210. package/dist/eslint/oxlint.test.js +64 -0
  211. package/dist/eslint/oxlint.test.js.map +1 -0
  212. package/dist/services/config/config-schema.d.ts +2 -0
  213. package/dist/services/config/config-schema.js +2 -0
  214. package/dist/services/config/config-schema.js.map +1 -1
  215. package/dist/services/config/load-paraglide-config.test.js +12 -0
  216. package/dist/services/config/load-paraglide-config.test.js.map +1 -1
  217. package/dist/services/env-variables/index.js +1 -1
  218. package/dist/services/env-variables/index.js.map +1 -1
  219. package/dist/services/file-handling/write-output.bench.js +51 -11
  220. package/dist/services/file-handling/write-output.bench.js.map +1 -1
  221. package/dist/services/file-handling/write-output.js +60 -20
  222. package/dist/services/file-handling/write-output.js.map +1 -1
  223. package/dist/services/file-handling/write-output.test.js +175 -0
  224. package/dist/services/file-handling/write-output.test.js.map +1 -1
  225. package/package.json +15 -5
@@ -8,53 +8,180 @@
8
8
  * For now, Paraglide ships a custom solution with INTL functions.
9
9
  */
10
10
  /**
11
- * Creates the Registry file
12
- */
13
- export function createRegistry() {
14
- return `
15
- /**
16
- * @typedef {"year" | "years" | "quarter" | "quarters" | "month" | "months" | "week" | "weeks" | "day" | "days" | "hour" | "hours" | "minute" | "minutes" | "second" | "seconds"} RelativeTimeFormatUnit
11
+ * The functions of the generated registry.js file, by name.
17
12
  */
18
-
19
- /**
13
+ const registryFunctions = {
14
+ getIntlFormatter: {
15
+ jsdoc: `/**
16
+ * @template T
17
+ * @param {new (locale: string, options?: any) => T} Constructor
18
+ * @param {string} locale
19
+ * @param {any} options
20
+ * @param {boolean} cacheable Compiler-created plain options only.
21
+ * @returns {T}
22
+ */`,
23
+ code: `function getIntlFormatter(Constructor, locale, options, cacheable) {
24
+ // Arbitrary user options may have getters or Proxy traps. Only inspect
25
+ // the plain data objects the compiler creates, or absent options.
26
+ if (typeof locale !== "string" || (options !== undefined && !cacheable)) {
27
+ return new Constructor(locale, options)
28
+ }
29
+ const state = /** @type {{ caches?: WeakMap<object, Map<string, any>>, prototypeKeys?: Set<string> }} */ (/** @type {unknown} */ (getIntlFormatter));
30
+ // Compiler literals inherit Object.prototype. Extensions to it may supply
31
+ // Intl options, so preserve native reads instead of caching in that case.
32
+ if (options !== undefined) {
33
+ const standardKeys = state.prototypeKeys ??= new Set(["constructor", "__defineGetter__", "__defineSetter__", "hasOwnProperty", "__lookupGetter__", "__lookupSetter__", "isPrototypeOf", "propertyIsEnumerable", "toString", "valueOf", "__proto__", "toLocaleString"]);
34
+ if (Object.getOwnPropertyNames(Object.prototype).some((name) => !standardKeys.has(name))) return new Constructor(locale, options)
35
+ }
36
+ const entries = [];
37
+ for (const name of Object.keys(options ?? {}).sort()) {
38
+ const value = options[name];
39
+ if (value === undefined) continue
40
+ if ((value !== null && !["string", "number", "boolean"].includes(typeof value)) || (typeof value === "number" && !Number.isFinite(value))) {
41
+ return new Constructor(locale, options)
42
+ }
43
+ entries.push([name, value]);
44
+ }
45
+ const key = JSON.stringify([locale, entries]);
46
+ const caches = state.caches ??= new WeakMap();
47
+ let cache = caches.get(Constructor);
48
+ if (!cache) {
49
+ cache = new Map();
50
+ caches.set(Constructor, cache);
51
+ }
52
+ if (cache.has(key)) return cache.get(key)
53
+ const formatter = new Constructor(locale, options);
54
+ // Bound retention when applications use dynamic formatter options.
55
+ if (cache.size >= 256) {
56
+ const oldest = cache.keys().next().value;
57
+ if (oldest !== undefined) cache.delete(oldest);
58
+ }
59
+ cache.set(key, formatter);
60
+ return formatter
61
+ };`,
62
+ dependencies: [],
63
+ },
64
+ plural: {
65
+ jsdoc: `/**
66
+ * Selects the plural category of "input - options.offset". The offset
67
+ * comes from ICU MessageFormat 1 "{count, plural, offset:1 ...}" and does not
68
+ * change exact matches like "=0", which compare the input itself.
69
+ *
20
70
  * @param {import("./runtime.js").Locale} locale
21
71
  * @param {unknown} input
22
- * @param {Intl.PluralRulesOptions} [options]
72
+ * @param {Intl.PluralRulesOptions & { offset?: unknown }} [options]
73
+ * @param {boolean} [cacheable=false] Internal compiler flag for plain data options.
23
74
  * @returns {string}
24
- */
25
- export function plural(locale, input, options) {
26
- return new Intl.PluralRules(locale, options).select(Number(input))
27
- };
28
-
29
- /**
75
+ */`,
76
+ code: `function plural(locale, input, options, cacheable = false) {
77
+ return getIntlFormatter(Intl.PluralRules, locale, options, cacheable).select(Number(input) - Number(options?.offset ?? 0))
78
+ };`,
79
+ dependencies: ["getIntlFormatter"],
80
+ },
81
+ number: {
82
+ jsdoc: `/**
30
83
  * @param {import("./runtime.js").Locale} locale
31
84
  * @param {unknown} input
32
85
  * @param {Intl.NumberFormatOptions} [options]
86
+ * @param {boolean} [cacheable=false] Internal compiler flag for plain data options.
33
87
  * @returns {string}
34
- */
35
- export function number(locale, input, options) {
36
- return new Intl.NumberFormat(locale, options).format(Number(input))
37
- };
38
-
39
- /**
88
+ */`,
89
+ code: `function number(locale, input, options, cacheable = false) {
90
+ return getIntlFormatter(Intl.NumberFormat, locale, options, cacheable).format(Number(input))
91
+ };`,
92
+ dependencies: ["getIntlFormatter"],
93
+ },
94
+ icuPound: {
95
+ jsdoc: `/**
96
+ * Formats "#" in ICU MessageFormat 1 plurals: the number "input - options.offset",
97
+ * formatted like number(). The offset comes from "{count, plural, offset:1 ...}".
98
+ * An input that is not a finite number (or a numeric string) is displayed as is,
99
+ * without subtracting the offset.
100
+ *
101
+ * @param {import("./runtime.js").Locale} locale
102
+ * @param {unknown} input
103
+ * @param {{ offset?: unknown }} [options]
104
+ * @returns {string}
105
+ */`,
106
+ code: `function icuPound(locale, input, options) {
107
+ const value = typeof input === "number" ? input : typeof input === "string" && input.trim() !== "" ? Number(input) : NaN
108
+ if (!Number.isFinite(value)) return String(input)
109
+ return number(locale, value - Number(options?.offset ?? 0))
110
+ };`,
111
+ dependencies: ["number"],
112
+ },
113
+ datetime: {
114
+ jsdoc: `/**
40
115
  * @param {import("./runtime.js").Locale} locale
41
116
  * @param {unknown} input
42
117
  * @param {Intl.DateTimeFormatOptions} [options]
118
+ * @param {boolean} [cacheable=false] Internal compiler flag for plain data options.
43
119
  * @returns {string}
44
- */
45
- export function datetime(locale, input, options) {
46
- return new Intl.DateTimeFormat(locale, options).format(new Date(/** @type {string} */ (input)))
47
- };
48
-
49
- /**
120
+ */`,
121
+ code: `function datetime(locale, input, options, cacheable = false) {
122
+ // The host default zone can change while the app is running.
123
+ const formatter = cacheable && options?.timeZone !== undefined
124
+ ? getIntlFormatter(Intl.DateTimeFormat, locale, options, cacheable)
125
+ : new Intl.DateTimeFormat(locale, options);
126
+ return formatter.format(new Date(/** @type {string} */ (input)))
127
+ };`,
128
+ dependencies: ["getIntlFormatter"],
129
+ },
130
+ relativetime: {
131
+ jsdoc: `/**
50
132
  * @param {import("./runtime.js").Locale} locale
51
133
  * @param {unknown} input
52
134
  * @param {Intl.RelativeTimeFormatOptions & { unit: RelativeTimeFormatUnit }} options
135
+ * @param {boolean} [cacheable=false] Internal compiler flag for plain data options.
53
136
  * @returns {string}
54
- */
55
- export function relativetime(locale, input, options) {
137
+ */`,
138
+ code: `function relativetime(locale, input, options, cacheable = false) {
56
139
  const { unit, ...intlOptions } = options;
57
- return new Intl.RelativeTimeFormat(locale, intlOptions).format(Number(input), unit);
58
- };`;
140
+ return getIntlFormatter(Intl.RelativeTimeFormat, locale, intlOptions, cacheable).format(Number(input), unit);
141
+ };`,
142
+ dependencies: ["getIntlFormatter"],
143
+ },
144
+ };
145
+ /**
146
+ * Creates the Registry file
147
+ */
148
+ export function createRegistry() {
149
+ return `
150
+ /**
151
+ * @typedef {"year" | "years" | "quarter" | "quarters" | "month" | "months" | "week" | "weeks" | "day" | "days" | "hour" | "hours" | "minute" | "minutes" | "second" | "seconds"} RelativeTimeFormatUnit
152
+ */
153
+
154
+ ${Object.values(registryFunctions)
155
+ .map((fn) => `${fn.jsdoc}\n${fn === registryFunctions.getIntlFormatter ? "" : "export "}${fn.code}`)
156
+ .join("\n\n")}`;
157
+ }
158
+ /**
159
+ * Returns the given registry functions and the registry functions they call,
160
+ * in registry order.
161
+ *
162
+ * @example
163
+ * withRegistryDependencies(["icuPound"]) // ["number", "icuPound"]
164
+ */
165
+ export function withRegistryDependencies(names) {
166
+ const needed = new Set();
167
+ const add = (name) => {
168
+ const fn = registryFunctions[name];
169
+ if (!fn || needed.has(name))
170
+ return;
171
+ needed.add(name);
172
+ fn.dependencies.forEach(add);
173
+ };
174
+ for (const name of names)
175
+ add(name);
176
+ return Object.keys(registryFunctions).filter((name) => needed.has(name));
177
+ }
178
+ /**
179
+ * Returns the code of the given registry functions without `export`, by name.
180
+ */
181
+ export function registryFunctionCode(names) {
182
+ return Object.fromEntries(Array.from(names).flatMap((name) => {
183
+ const fn = registryFunctions[name];
184
+ return fn ? [[name, fn.code]] : [];
185
+ }));
59
186
  }
60
187
  //# sourceMappingURL=registry.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/compiler/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;GAEG;AACH,MAAM,UAAU,cAAc;IAC7B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CL,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Re: CUSTOMIZING THE REGISTRY\n *\n * We want to enable anyone (designer, developer, translator) to\n * specify options for functions. That requires design work in\n * the inlang SDK.\n *\n * For now, Paraglide ships a custom solution with INTL functions.\n */\n\n/**\n * Creates the Registry file\n */\nexport function createRegistry(): string {\n\treturn `\n/**\n * @typedef {\"year\" | \"years\" | \"quarter\" | \"quarters\" | \"month\" | \"months\" | \"week\" | \"weeks\" | \"day\" | \"days\" | \"hour\" | \"hours\" | \"minute\" | \"minutes\" | \"second\" | \"seconds\"} RelativeTimeFormatUnit\n */\n\n/**\n * @param {import(\"./runtime.js\").Locale} locale\n * @param {unknown} input\n * @param {Intl.PluralRulesOptions} [options]\n * @returns {string}\n */\nexport function plural(locale, input, options) { \n\treturn new Intl.PluralRules(locale, options).select(Number(input))\n};\n\n/**\n * @param {import(\"./runtime.js\").Locale} locale\n * @param {unknown} input\n * @param {Intl.NumberFormatOptions} [options]\n * @returns {string}\n */\nexport function number(locale, input, options) {\n\treturn new Intl.NumberFormat(locale, options).format(Number(input))\n};\n\n/**\n * @param {import(\"./runtime.js\").Locale} locale\n * @param {unknown} input\n * @param {Intl.DateTimeFormatOptions} [options]\n * @returns {string}\n */\nexport function datetime(locale, input, options) {\n\treturn new Intl.DateTimeFormat(locale, options).format(new Date(/** @type {string} */ (input)))\n};\n\n/**\n * @param {import(\"./runtime.js\").Locale} locale\n * @param {unknown} input\n * @param {Intl.RelativeTimeFormatOptions & { unit: RelativeTimeFormatUnit }} options\n * @returns {string}\n */\nexport function relativetime(locale, input, options) {\n\tconst { unit, ...intlOptions } = options;\n\treturn new Intl.RelativeTimeFormat(locale, intlOptions).format(Number(input), unit);\n};`;\n}\n"]}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/compiler/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAWH;;GAEG;AACH,MAAM,iBAAiB,GAAqC;IAC3D,gBAAgB,EAAE;QACjB,KAAK,EAAE;;;;;;;IAOL;QACF,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCL;QACD,YAAY,EAAE,EAAE;KAChB;IACD,MAAM,EAAE;QACP,KAAK,EAAE;;;;;;;;;;IAUL;QACF,IAAI,EAAE;;GAEL;QACD,YAAY,EAAE,CAAC,kBAAkB,CAAC;KAClC;IACD,MAAM,EAAE;QACP,KAAK,EAAE;;;;;;IAML;QACF,IAAI,EAAE;;GAEL;QACD,YAAY,EAAE,CAAC,kBAAkB,CAAC;KAClC;IACD,QAAQ,EAAE;QACT,KAAK,EAAE;;;;;;;;;;IAUL;QACF,IAAI,EAAE;;;;GAIL;QACD,YAAY,EAAE,CAAC,QAAQ,CAAC;KACxB;IACD,QAAQ,EAAE;QACT,KAAK,EAAE;;;;;;IAML;QACF,IAAI,EAAE;;;;;;GAML;QACD,YAAY,EAAE,CAAC,kBAAkB,CAAC;KAClC;IACD,YAAY,EAAE;QACb,KAAK,EAAE;;;;;;IAML;QACF,IAAI,EAAE;;;GAGL;QACD,YAAY,EAAE,CAAC,kBAAkB,CAAC;KAClC;CACD,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,cAAc;IAC7B,OAAO;;;;;EAKN,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC;SAChC,GAAG,CACH,CAAC,EAAE,EAAE,EAAE,CACN,GAAG,EAAE,CAAC,KAAK,KAAK,EAAE,KAAK,iBAAiB,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,EAAE,CAAC,IAAI,EAAE,CACvF;SACA,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;AACjB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CAAC,KAAuB;IAC/D,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IACjC,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,EAAE;QAC5B,MAAM,EAAE,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,EAAE,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO;QACpC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACjB,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC9B,CAAC,CAAC;IACF,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,GAAG,CAAC,IAAI,CAAC,CAAC;IACpC,OAAO,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;AAC1E,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CACnC,KAAuB;IAEvB,OAAO,MAAM,CAAC,WAAW,CACxB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QAClC,MAAM,EAAE,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;QACnC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACpC,CAAC,CAAC,CACF,CAAC;AACH,CAAC","sourcesContent":["/**\n * Re: CUSTOMIZING THE REGISTRY\n *\n * We want to enable anyone (designer, developer, translator) to\n * specify options for functions. That requires design work in\n * the inlang SDK.\n *\n * For now, Paraglide ships a custom solution with INTL functions.\n */\n\ntype RegistryFunction = {\n\t/** JSDoc of the function in registry.js */\n\tjsdoc: string;\n\t/** The function declaration, without `export` */\n\tcode: string;\n\t/** Registry functions the code calls */\n\tdependencies: string[];\n};\n\n/**\n * The functions of the generated registry.js file, by name.\n */\nconst registryFunctions: Record<string, RegistryFunction> = {\n\tgetIntlFormatter: {\n\t\tjsdoc: `/**\n * @template T\n * @param {new (locale: string, options?: any) => T} Constructor\n * @param {string} locale\n * @param {any} options\n * @param {boolean} cacheable Compiler-created plain options only.\n * @returns {T}\n */`,\n\t\tcode: `function getIntlFormatter(Constructor, locale, options, cacheable) {\n\t// Arbitrary user options may have getters or Proxy traps. Only inspect\n\t// the plain data objects the compiler creates, or absent options.\n\tif (typeof locale !== \"string\" || (options !== undefined && !cacheable)) {\n\t\treturn new Constructor(locale, options)\n\t}\n\tconst state = /** @type {{ caches?: WeakMap<object, Map<string, any>>, prototypeKeys?: Set<string> }} */ (/** @type {unknown} */ (getIntlFormatter));\n\t// Compiler literals inherit Object.prototype. Extensions to it may supply\n\t// Intl options, so preserve native reads instead of caching in that case.\n\tif (options !== undefined) {\n\t\tconst standardKeys = state.prototypeKeys ??= new Set([\"constructor\", \"__defineGetter__\", \"__defineSetter__\", \"hasOwnProperty\", \"__lookupGetter__\", \"__lookupSetter__\", \"isPrototypeOf\", \"propertyIsEnumerable\", \"toString\", \"valueOf\", \"__proto__\", \"toLocaleString\"]);\n\t\tif (Object.getOwnPropertyNames(Object.prototype).some((name) => !standardKeys.has(name))) return new Constructor(locale, options)\n\t}\n\tconst entries = [];\n\tfor (const name of Object.keys(options ?? {}).sort()) {\n\t\tconst value = options[name];\n\t\tif (value === undefined) continue\n\t\tif ((value !== null && ![\"string\", \"number\", \"boolean\"].includes(typeof value)) || (typeof value === \"number\" && !Number.isFinite(value))) {\n\t\t\treturn new Constructor(locale, options)\n\t\t}\n\t\tentries.push([name, value]);\n\t}\n\tconst key = JSON.stringify([locale, entries]);\n\tconst caches = state.caches ??= new WeakMap();\n\tlet cache = caches.get(Constructor);\n\tif (!cache) {\n\t\tcache = new Map();\n\t\tcaches.set(Constructor, cache);\n\t}\n\tif (cache.has(key)) return cache.get(key)\n\tconst formatter = new Constructor(locale, options);\n\t// Bound retention when applications use dynamic formatter options.\n\tif (cache.size >= 256) {\n\t\tconst oldest = cache.keys().next().value;\n\t\tif (oldest !== undefined) cache.delete(oldest);\n\t}\n\tcache.set(key, formatter);\n\treturn formatter\n};`,\n\t\tdependencies: [],\n\t},\n\tplural: {\n\t\tjsdoc: `/**\n * Selects the plural category of \"input - options.offset\". The offset\n * comes from ICU MessageFormat 1 \"{count, plural, offset:1 ...}\" and does not\n * change exact matches like \"=0\", which compare the input itself.\n *\n * @param {import(\"./runtime.js\").Locale} locale\n * @param {unknown} input\n * @param {Intl.PluralRulesOptions & { offset?: unknown }} [options]\n * @param {boolean} [cacheable=false] Internal compiler flag for plain data options.\n * @returns {string}\n */`,\n\t\tcode: `function plural(locale, input, options, cacheable = false) {\n\treturn getIntlFormatter(Intl.PluralRules, locale, options, cacheable).select(Number(input) - Number(options?.offset ?? 0))\n};`,\n\t\tdependencies: [\"getIntlFormatter\"],\n\t},\n\tnumber: {\n\t\tjsdoc: `/**\n * @param {import(\"./runtime.js\").Locale} locale\n * @param {unknown} input\n * @param {Intl.NumberFormatOptions} [options]\n * @param {boolean} [cacheable=false] Internal compiler flag for plain data options.\n * @returns {string}\n */`,\n\t\tcode: `function number(locale, input, options, cacheable = false) {\n\treturn getIntlFormatter(Intl.NumberFormat, locale, options, cacheable).format(Number(input))\n};`,\n\t\tdependencies: [\"getIntlFormatter\"],\n\t},\n\ticuPound: {\n\t\tjsdoc: `/**\n * Formats \"#\" in ICU MessageFormat 1 plurals: the number \"input - options.offset\",\n * formatted like number(). The offset comes from \"{count, plural, offset:1 ...}\".\n * An input that is not a finite number (or a numeric string) is displayed as is,\n * without subtracting the offset.\n *\n * @param {import(\"./runtime.js\").Locale} locale\n * @param {unknown} input\n * @param {{ offset?: unknown }} [options]\n * @returns {string}\n */`,\n\t\tcode: `function icuPound(locale, input, options) {\n\tconst value = typeof input === \"number\" ? input : typeof input === \"string\" && input.trim() !== \"\" ? Number(input) : NaN\n\tif (!Number.isFinite(value)) return String(input)\n\treturn number(locale, value - Number(options?.offset ?? 0))\n};`,\n\t\tdependencies: [\"number\"],\n\t},\n\tdatetime: {\n\t\tjsdoc: `/**\n * @param {import(\"./runtime.js\").Locale} locale\n * @param {unknown} input\n * @param {Intl.DateTimeFormatOptions} [options]\n * @param {boolean} [cacheable=false] Internal compiler flag for plain data options.\n * @returns {string}\n */`,\n\t\tcode: `function datetime(locale, input, options, cacheable = false) {\n\t// The host default zone can change while the app is running.\n\tconst formatter = cacheable && options?.timeZone !== undefined\n\t\t? getIntlFormatter(Intl.DateTimeFormat, locale, options, cacheable)\n\t\t: new Intl.DateTimeFormat(locale, options);\n\treturn formatter.format(new Date(/** @type {string} */ (input)))\n};`,\n\t\tdependencies: [\"getIntlFormatter\"],\n\t},\n\trelativetime: {\n\t\tjsdoc: `/**\n * @param {import(\"./runtime.js\").Locale} locale\n * @param {unknown} input\n * @param {Intl.RelativeTimeFormatOptions & { unit: RelativeTimeFormatUnit }} options\n * @param {boolean} [cacheable=false] Internal compiler flag for plain data options.\n * @returns {string}\n */`,\n\t\tcode: `function relativetime(locale, input, options, cacheable = false) {\n\tconst { unit, ...intlOptions } = options;\n\treturn getIntlFormatter(Intl.RelativeTimeFormat, locale, intlOptions, cacheable).format(Number(input), unit);\n};`,\n\t\tdependencies: [\"getIntlFormatter\"],\n\t},\n};\n\n/**\n * Creates the Registry file\n */\nexport function createRegistry(): string {\n\treturn `\n/**\n * @typedef {\"year\" | \"years\" | \"quarter\" | \"quarters\" | \"month\" | \"months\" | \"week\" | \"weeks\" | \"day\" | \"days\" | \"hour\" | \"hours\" | \"minute\" | \"minutes\" | \"second\" | \"seconds\"} RelativeTimeFormatUnit\n */\n\n${Object.values(registryFunctions)\n\t.map(\n\t\t(fn) =>\n\t\t\t`${fn.jsdoc}\\n${fn === registryFunctions.getIntlFormatter ? \"\" : \"export \"}${fn.code}`\n\t)\n\t.join(\"\\n\\n\")}`;\n}\n\n/**\n * Returns the given registry functions and the registry functions they call,\n * in registry order.\n *\n * @example\n * withRegistryDependencies([\"icuPound\"]) // [\"number\", \"icuPound\"]\n */\nexport function withRegistryDependencies(names: Iterable<string>): string[] {\n\tconst needed = new Set<string>();\n\tconst add = (name: string) => {\n\t\tconst fn = registryFunctions[name];\n\t\tif (!fn || needed.has(name)) return;\n\t\tneeded.add(name);\n\t\tfn.dependencies.forEach(add);\n\t};\n\tfor (const name of names) add(name);\n\treturn Object.keys(registryFunctions).filter((name) => needed.has(name));\n}\n\n/**\n * Returns the code of the given registry functions without `export`, by name.\n */\nexport function registryFunctionCode(\n\tnames: Iterable<string>\n): Record<string, string> {\n\treturn Object.fromEntries(\n\t\tArray.from(names).flatMap((name) => {\n\t\t\tconst fn = registryFunctions[name];\n\t\t\treturn fn ? [[name, fn.code]] : [];\n\t\t})\n\t);\n}\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,291 @@
1
+ import { afterEach, expect, test, vi } from "vitest";
2
+ import { execFileSync } from "node:child_process";
3
+ import { compileMessage } from "./compile-message.js";
4
+ import { createRegistry, registryFunctionCode, withRegistryDependencies, } from "./registry.js";
5
+ afterEach(() => vi.restoreAllMocks());
6
+ function spyConstructor(name) {
7
+ const Original = Intl[name];
8
+ return vi
9
+ .spyOn(Intl, name)
10
+ .mockImplementation((...args) => Reflect.construct(Original, args));
11
+ }
12
+ async function loadRegistry(inline = false) {
13
+ const source = registrySource(inline);
14
+ const module = await import(`data:text/javascript;base64,${Buffer.from(source).toString("base64")}`);
15
+ return inline ? module.registry : module;
16
+ }
17
+ function registrySource(inline) {
18
+ return inline
19
+ ? `export const registry = (() => { ${Object.values(registryFunctionCode(withRegistryDependencies(["plural", "icuPound", "datetime", "relativetime"]))).join("\n")}\nreturn { plural, icuPound, number, datetime, relativetime }; })();`
20
+ : createRegistry();
21
+ }
22
+ for (const inline of [false, true]) {
23
+ test(`reuses each formatter with fresh equivalent options (inline=${inline})`, async () => {
24
+ const registry = await loadRegistry(inline);
25
+ const cases = [
26
+ ["plural", "PluralRules", 2, { type: "cardinal" }, "other"],
27
+ ["number", "NumberFormat", 12, { minimumFractionDigits: 2 }, "12.00"],
28
+ [
29
+ "datetime",
30
+ "DateTimeFormat",
31
+ "2026-10-11",
32
+ { timeZone: "UTC", year: "numeric" },
33
+ "2026",
34
+ ],
35
+ [
36
+ "relativetime",
37
+ "RelativeTimeFormat",
38
+ -1,
39
+ { unit: "day", numeric: "auto" },
40
+ "yesterday",
41
+ ],
42
+ ];
43
+ for (const [fn, name, input, options, expected] of cases) {
44
+ const spy = spyConstructor(name);
45
+ for (let i = 0; i < 100; i++)
46
+ expect(registry[fn]("en", input, { ...options }, true)).toBe(expected);
47
+ expect(spy).toHaveBeenCalledTimes(1);
48
+ spy.mockRestore();
49
+ }
50
+ expect(registry.icuPound("en", 3, { offset: 1 })).toBe("2");
51
+ });
52
+ test(`default-zone datetime follows host TZ changes; explicit zones stay cached (inline=${inline})`, () => {
53
+ // TZ changes affect Intl in Node's main thread, not Vitest worker threads.
54
+ const url = `data:text/javascript;base64,${Buffer.from(registrySource(inline)).toString("base64")}`;
55
+ execFileSync(process.execPath, [
56
+ "--input-type=module",
57
+ "-e",
58
+ `
59
+ import assert from "node:assert/strict";
60
+ const module = await import(${JSON.stringify(url)});
61
+ const registry = ${inline ? "module.registry" : "module"};
62
+ const Native = Intl.DateTimeFormat;
63
+ let constructions = 0;
64
+ Intl.DateTimeFormat = new Proxy(Native, {
65
+ construct(target, args, newTarget) {
66
+ constructions++;
67
+ return Reflect.construct(target, args, newTarget);
68
+ }
69
+ });
70
+ const input = "2026-01-01T02:00:00Z";
71
+ for (const options of [undefined, {}, { timeZone: undefined }, { hour: "numeric" }]) {
72
+ const start = constructions;
73
+ process.env.TZ = "UTC";
74
+ const utc = registry.datetime("en", input, options, true);
75
+ assert.equal(utc, new Native("en", options).format(new Date(input)));
76
+ process.env.TZ = "America/New_York";
77
+ const newYork = registry.datetime("en", input, options, true);
78
+ assert.equal(newYork, new Native("en", options).format(new Date(input)));
79
+ assert.notEqual(utc, newYork);
80
+ assert.equal(constructions - start, 2);
81
+ }
82
+ const start = constructions;
83
+ process.env.TZ = "UTC";
84
+ const utc = registry.datetime("en", input, { timeZone: "UTC" }, true);
85
+ process.env.TZ = "America/New_York";
86
+ assert.equal(registry.datetime("en", input, { timeZone: "UTC" }, true), utc);
87
+ assert.equal(constructions - start, 1);
88
+ `,
89
+ ], { env: { ...process.env, TZ: "UTC" }, timeout: 10000 });
90
+ });
91
+ }
92
+ test("keys distinguish locales and options, independent of property order", async () => {
93
+ const registry = await loadRegistry();
94
+ const expectedGerman = new Intl.NumberFormat("de", {
95
+ style: "currency",
96
+ currency: "EUR",
97
+ }).format(2);
98
+ const spy = spyConstructor("NumberFormat");
99
+ const options = { style: "currency", currency: "USD" };
100
+ expect(registry.number("en", 2, options, true)).toBe("$2.00");
101
+ expect(registry.number("en", 2, { currency: "USD", style: "currency" }, true)).toBe("$2.00");
102
+ expect(spy).toHaveBeenCalledTimes(1);
103
+ options.currency = "EUR";
104
+ expect(registry.number("en", 2, options, true)).toBe("€2.00");
105
+ expect(registry.number("de", 2, options, true)).toBe(expectedGerman);
106
+ expect(spy.mock.calls.filter(([locale]) => locale === "en")).toHaveLength(2);
107
+ });
108
+ test("preserves cardinal, ordinal, offsets, relative units and defaults", async () => {
109
+ const registry = await loadRegistry();
110
+ expect(registry.plural("en", 2, { type: "cardinal" })).toBe("other");
111
+ expect(registry.plural("en", 2, { type: "ordinal" })).toBe("two");
112
+ expect(registry.plural("en", 2, { offset: 1 })).toBe("one");
113
+ expect(registry.relativetime("en", -1, { unit: "day", numeric: "auto" })).toBe("yesterday");
114
+ expect(registry.relativetime("en", -1, { unit: "year", numeric: "auto" })).toBe("last year");
115
+ const spy = spyConstructor("PluralRules");
116
+ expect(registry.plural("en", 1)).toBe("one");
117
+ expect(registry.plural("en", 1, {}, true)).toBe("one");
118
+ expect(registry.plural("en", 1, { type: undefined }, true)).toBe("one");
119
+ expect(spy).toHaveBeenCalledTimes(1);
120
+ });
121
+ test("preserves changing accessors, inherited options and object-valued options", async () => {
122
+ const registry = await loadRegistry();
123
+ let digits = 0;
124
+ const accessor = {
125
+ get minimumFractionDigits() {
126
+ return digits++;
127
+ },
128
+ };
129
+ expect(registry.number("en", 2, accessor)).toBe("2");
130
+ expect(registry.number("en", 2, accessor)).toBe("2.0");
131
+ const inherited = Object.create({ minimumFractionDigits: 1 });
132
+ expect(registry.number("en", 2, inherited)).toBe("2.0");
133
+ Object.getPrototypeOf(inherited).minimumFractionDigits = 2;
134
+ expect(registry.number("en", 2, inherited)).toBe("2.00");
135
+ let currency = "USD";
136
+ const options = { style: "currency", currency: { toString: () => currency } };
137
+ expect(registry.number("en", 2, options)).toBe("$2.00");
138
+ currency = "EUR";
139
+ expect(registry.number("en", 2, options)).toBe("€2.00");
140
+ });
141
+ test("preserves invalid option errors, without caching failures", async () => {
142
+ const registry = await loadRegistry();
143
+ const spy = spyConstructor("NumberFormat");
144
+ for (let i = 0; i < 2; i++) {
145
+ expect(() => registry.number("en", 2, { minimumFractionDigits: NaN })).toThrow(RangeError);
146
+ expect(() => registry.number("en", 2, null)).toThrow(TypeError);
147
+ }
148
+ expect(spy).toHaveBeenCalledTimes(4);
149
+ });
150
+ test("bounds each constructor cache and evicts oldest entries", async () => {
151
+ const registry = await loadRegistry();
152
+ const spy = spyConstructor("NumberFormat");
153
+ for (let i = 0; i < 257; i++)
154
+ registry.number("en", i, {
155
+ minimumIntegerDigits: (i % 21) + 1,
156
+ minimumFractionDigits: Math.floor(i / 21),
157
+ }, true);
158
+ registry.number("en", 2, {
159
+ minimumIntegerDigits: 1,
160
+ minimumFractionDigits: 0,
161
+ }, true);
162
+ expect(spy).toHaveBeenCalledTimes(258);
163
+ });
164
+ test("uses a replacement constructor after a polyfill is installed", async () => {
165
+ const registry = await loadRegistry();
166
+ expect(registry.plural("en", 2)).toBe("other");
167
+ const replacement = spyConstructor("PluralRules");
168
+ expect(registry.plural("en", 2)).toBe("other");
169
+ expect(registry.plural("en", 2)).toBe("other");
170
+ expect(replacement).toHaveBeenCalledTimes(1);
171
+ });
172
+ test("direct calls preserve effective proxy options", async () => {
173
+ const registry = await loadRegistry();
174
+ let style = "short";
175
+ const options = new Proxy({}, {
176
+ get: (_, key) => key === "dateStyle" ? style : key === "timeZone" ? "UTC" : undefined,
177
+ });
178
+ const input = "2026-10-11";
179
+ expect(registry.datetime("en", input, options)).toBe(new Intl.DateTimeFormat("en", {
180
+ dateStyle: "short",
181
+ timeZone: "UTC",
182
+ }).format(new Date(input)));
183
+ style = "full";
184
+ expect(registry.datetime("en", input, options)).toBe(new Intl.DateTimeFormat("en", {
185
+ dateStyle: "full",
186
+ timeZone: "UTC",
187
+ }).format(new Date(input)));
188
+ });
189
+ test("direct calls never invoke prototype or descriptor traps", async () => {
190
+ const registry = await loadRegistry();
191
+ let probes = 0;
192
+ const options = new Proxy({}, {
193
+ getPrototypeOf() {
194
+ probes++;
195
+ return Object.prototype;
196
+ },
197
+ getOwnPropertyDescriptor() {
198
+ probes++;
199
+ return undefined;
200
+ },
201
+ get(_, key) {
202
+ return key === "minimumFractionDigits" ? probes : undefined;
203
+ },
204
+ });
205
+ expect(registry.number("en", 2, options)).toBe("2");
206
+ expect(probes).toBe(0);
207
+ });
208
+ test("keeps the cache helper private in the generated registry", async () => {
209
+ expect(await loadRegistry()).not.toHaveProperty("getIntlFormatter");
210
+ expect(await loadRegistry(true)).not.toHaveProperty("getIntlFormatter");
211
+ });
212
+ test("does not enumerate proxy option keys or inspect descriptors", async () => {
213
+ const registry = await loadRegistry();
214
+ const options = new Proxy({}, {
215
+ ownKeys() {
216
+ throw new Error("must not enumerate");
217
+ },
218
+ getOwnPropertyDescriptor() {
219
+ throw new Error("must not inspect");
220
+ },
221
+ getPrototypeOf() {
222
+ throw new Error("must not inspect prototype");
223
+ },
224
+ });
225
+ expect(registry.number("en", 2, options)).toBe("2");
226
+ });
227
+ test("does not touch later getters after Intl rejects an earlier option", async () => {
228
+ const registry = await loadRegistry();
229
+ let touched = false;
230
+ const options = {
231
+ localeMatcher: "invalid",
232
+ get currency() {
233
+ touched = true;
234
+ return "USD";
235
+ },
236
+ };
237
+ expect(() => registry.number("en", 1, options)).toThrow(RangeError);
238
+ expect(touched).toBe(false);
239
+ });
240
+ test("compiled messages reuse formatters for their plain option literals", async () => {
241
+ const compiled = compileMessage([{ type: "input-variable", name: "amount" }], { id: "price", bundleId: "price", locale: "en", selectors: [] }, [
242
+ {
243
+ id: "price",
244
+ matches: [],
245
+ pattern: [
246
+ {
247
+ type: "expression",
248
+ arg: { type: "variable-reference", name: "amount" },
249
+ annotation: {
250
+ type: "function-reference",
251
+ name: "number",
252
+ options: [],
253
+ },
254
+ },
255
+ ],
256
+ },
257
+ ]);
258
+ const source = `${createRegistry()}\nconst registry = { number };\nexport const price = ${compiled.code}`;
259
+ const { price } = await import(`data:text/javascript;base64,${Buffer.from(source).toString("base64")}`);
260
+ const spy = spyConstructor("NumberFormat");
261
+ for (let i = 0; i < 100; i++)
262
+ expect(price({ amount: 12 })).toBe("12");
263
+ expect(spy).toHaveBeenCalledTimes(1);
264
+ });
265
+ test("compiler-created object-valued options retain dynamic coercion", async () => {
266
+ const registry = await loadRegistry();
267
+ let currency = "USD";
268
+ const options = { style: "currency", currency: { toString: () => currency } };
269
+ expect(registry.number("en", 2, options, true)).toBe("$2.00");
270
+ currency = "EUR";
271
+ expect(registry.number("en", 2, options, true)).toBe("€2.00");
272
+ });
273
+ test("preserves options inherited from an extended Object.prototype", async () => {
274
+ const registry = await loadRegistry();
275
+ expect(registry.number("en", 2, {}, true)).toBe("2");
276
+ const previous = Object.getOwnPropertyDescriptor(Object.prototype, "minimumFractionDigits");
277
+ try {
278
+ Object.defineProperty(Object.prototype, "minimumFractionDigits", {
279
+ value: 2,
280
+ configurable: true,
281
+ });
282
+ expect(registry.number("en", 2, {}, true)).toBe("2.00");
283
+ }
284
+ finally {
285
+ if (previous)
286
+ Object.defineProperty(Object.prototype, "minimumFractionDigits", previous);
287
+ else
288
+ Reflect.deleteProperty(Object.prototype, "minimumFractionDigits");
289
+ }
290
+ });
291
+ //# sourceMappingURL=registry.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.test.js","sourceRoot":"","sources":["../../src/compiler/registry.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { afterEach, expect, test, vi } from \"vitest\";\nimport { execFileSync } from \"node:child_process\";\nimport { compileMessage } from \"./compile-message.js\";\nimport {\n\tcreateRegistry,\n\tregistryFunctionCode,\n\twithRegistryDependencies,\n} from \"./registry.js\";\n\nafterEach(() => vi.restoreAllMocks());\n\nfunction spyConstructor(\n\tname: \"PluralRules\" | \"NumberFormat\" | \"DateTimeFormat\" | \"RelativeTimeFormat\"\n) {\n\tconst Original = Intl[name];\n\treturn vi\n\t\t.spyOn(Intl, name)\n\t\t.mockImplementation((...args: any[]) => Reflect.construct(Original, args));\n}\n\nasync function loadRegistry(inline = false) {\n\tconst source = registrySource(inline);\n\tconst module = await import(\n\t\t`data:text/javascript;base64,${Buffer.from(source).toString(\"base64\")}`\n\t);\n\treturn inline ? module.registry : module;\n}\n\nfunction registrySource(inline: boolean) {\n\treturn inline\n\t\t? `export const registry = (() => { ${Object.values(registryFunctionCode(withRegistryDependencies([\"plural\", \"icuPound\", \"datetime\", \"relativetime\"]))).join(\"\\n\")}\\nreturn { plural, icuPound, number, datetime, relativetime }; })();`\n\t\t: createRegistry();\n}\n\nfor (const inline of [false, true]) {\n\ttest(`reuses each formatter with fresh equivalent options (inline=${inline})`, async () => {\n\t\tconst registry = await loadRegistry(inline);\n\t\tconst cases = [\n\t\t\t[\"plural\", \"PluralRules\", 2, { type: \"cardinal\" }, \"other\"],\n\t\t\t[\"number\", \"NumberFormat\", 12, { minimumFractionDigits: 2 }, \"12.00\"],\n\t\t\t[\n\t\t\t\t\"datetime\",\n\t\t\t\t\"DateTimeFormat\",\n\t\t\t\t\"2026-10-11\",\n\t\t\t\t{ timeZone: \"UTC\", year: \"numeric\" },\n\t\t\t\t\"2026\",\n\t\t\t],\n\t\t\t[\n\t\t\t\t\"relativetime\",\n\t\t\t\t\"RelativeTimeFormat\",\n\t\t\t\t-1,\n\t\t\t\t{ unit: \"day\", numeric: \"auto\" },\n\t\t\t\t\"yesterday\",\n\t\t\t],\n\t\t] as const;\n\t\tfor (const [fn, name, input, options, expected] of cases) {\n\t\t\tconst spy = spyConstructor(name);\n\t\t\tfor (let i = 0; i < 100; i++)\n\t\t\t\texpect(registry[fn](\"en\", input, { ...options }, true)).toBe(expected);\n\t\t\texpect(spy).toHaveBeenCalledTimes(1);\n\t\t\tspy.mockRestore();\n\t\t}\n\t\texpect(registry.icuPound(\"en\", 3, { offset: 1 })).toBe(\"2\");\n\t});\n\n\ttest(`default-zone datetime follows host TZ changes; explicit zones stay cached (inline=${inline})`, () => {\n\t\t// TZ changes affect Intl in Node's main thread, not Vitest worker threads.\n\t\tconst url = `data:text/javascript;base64,${Buffer.from(registrySource(inline)).toString(\"base64\")}`;\n\t\texecFileSync(\n\t\t\tprocess.execPath,\n\t\t\t[\n\t\t\t\t\"--input-type=module\",\n\t\t\t\t\"-e\",\n\t\t\t\t`\n\t\t\timport assert from \"node:assert/strict\";\n\t\t\tconst module = await import(${JSON.stringify(url)});\n\t\t\tconst registry = ${inline ? \"module.registry\" : \"module\"};\n\t\t\tconst Native = Intl.DateTimeFormat;\n\t\t\tlet constructions = 0;\n\t\t\tIntl.DateTimeFormat = new Proxy(Native, {\n\t\t\t\tconstruct(target, args, newTarget) {\n\t\t\t\t\tconstructions++;\n\t\t\t\t\treturn Reflect.construct(target, args, newTarget);\n\t\t\t\t}\n\t\t\t});\n\t\t\tconst input = \"2026-01-01T02:00:00Z\";\n\t\t\tfor (const options of [undefined, {}, { timeZone: undefined }, { hour: \"numeric\" }]) {\n\t\t\t\tconst start = constructions;\n\t\t\t\tprocess.env.TZ = \"UTC\";\n\t\t\t\tconst utc = registry.datetime(\"en\", input, options, true);\n\t\t\t\tassert.equal(utc, new Native(\"en\", options).format(new Date(input)));\n\t\t\t\tprocess.env.TZ = \"America/New_York\";\n\t\t\t\tconst newYork = registry.datetime(\"en\", input, options, true);\n\t\t\t\tassert.equal(newYork, new Native(\"en\", options).format(new Date(input)));\n\t\t\t\tassert.notEqual(utc, newYork);\n\t\t\t\tassert.equal(constructions - start, 2);\n\t\t\t}\n\t\t\tconst start = constructions;\n\t\t\tprocess.env.TZ = \"UTC\";\n\t\t\tconst utc = registry.datetime(\"en\", input, { timeZone: \"UTC\" }, true);\n\t\t\tprocess.env.TZ = \"America/New_York\";\n\t\t\tassert.equal(registry.datetime(\"en\", input, { timeZone: \"UTC\" }, true), utc);\n\t\t\tassert.equal(constructions - start, 1);\n\t\t`,\n\t\t\t],\n\t\t\t{ env: { ...process.env, TZ: \"UTC\" }, timeout: 10000 }\n\t\t);\n\t});\n}\n\ntest(\"keys distinguish locales and options, independent of property order\", async () => {\n\tconst registry = await loadRegistry();\n\tconst expectedGerman = new Intl.NumberFormat(\"de\", {\n\t\tstyle: \"currency\",\n\t\tcurrency: \"EUR\",\n\t}).format(2);\n\tconst spy = spyConstructor(\"NumberFormat\");\n\tconst options = { style: \"currency\", currency: \"USD\" };\n\texpect(registry.number(\"en\", 2, options, true)).toBe(\"$2.00\");\n\texpect(\n\t\tregistry.number(\"en\", 2, { currency: \"USD\", style: \"currency\" }, true)\n\t).toBe(\"$2.00\");\n\texpect(spy).toHaveBeenCalledTimes(1);\n\toptions.currency = \"EUR\";\n\texpect(registry.number(\"en\", 2, options, true)).toBe(\"€2.00\");\n\texpect(registry.number(\"de\", 2, options, true)).toBe(expectedGerman);\n\texpect(spy.mock.calls.filter(([locale]) => locale === \"en\")).toHaveLength(2);\n});\n\ntest(\"preserves cardinal, ordinal, offsets, relative units and defaults\", async () => {\n\tconst registry = await loadRegistry();\n\texpect(registry.plural(\"en\", 2, { type: \"cardinal\" })).toBe(\"other\");\n\texpect(registry.plural(\"en\", 2, { type: \"ordinal\" })).toBe(\"two\");\n\texpect(registry.plural(\"en\", 2, { offset: 1 })).toBe(\"one\");\n\texpect(\n\t\tregistry.relativetime(\"en\", -1, { unit: \"day\", numeric: \"auto\" })\n\t).toBe(\"yesterday\");\n\texpect(\n\t\tregistry.relativetime(\"en\", -1, { unit: \"year\", numeric: \"auto\" })\n\t).toBe(\"last year\");\n\tconst spy = spyConstructor(\"PluralRules\");\n\texpect(registry.plural(\"en\", 1)).toBe(\"one\");\n\texpect(registry.plural(\"en\", 1, {}, true)).toBe(\"one\");\n\texpect(registry.plural(\"en\", 1, { type: undefined }, true)).toBe(\"one\");\n\texpect(spy).toHaveBeenCalledTimes(1);\n});\n\ntest(\"preserves changing accessors, inherited options and object-valued options\", async () => {\n\tconst registry = await loadRegistry();\n\tlet digits = 0;\n\tconst accessor = {\n\t\tget minimumFractionDigits() {\n\t\t\treturn digits++;\n\t\t},\n\t};\n\texpect(registry.number(\"en\", 2, accessor)).toBe(\"2\");\n\texpect(registry.number(\"en\", 2, accessor)).toBe(\"2.0\");\n\tconst inherited = Object.create({ minimumFractionDigits: 1 });\n\texpect(registry.number(\"en\", 2, inherited)).toBe(\"2.0\");\n\tObject.getPrototypeOf(inherited).minimumFractionDigits = 2;\n\texpect(registry.number(\"en\", 2, inherited)).toBe(\"2.00\");\n\tlet currency = \"USD\";\n\tconst options = { style: \"currency\", currency: { toString: () => currency } };\n\texpect(registry.number(\"en\", 2, options)).toBe(\"$2.00\");\n\tcurrency = \"EUR\";\n\texpect(registry.number(\"en\", 2, options)).toBe(\"€2.00\");\n});\n\ntest(\"preserves invalid option errors, without caching failures\", async () => {\n\tconst registry = await loadRegistry();\n\tconst spy = spyConstructor(\"NumberFormat\");\n\tfor (let i = 0; i < 2; i++) {\n\t\texpect(() =>\n\t\t\tregistry.number(\"en\", 2, { minimumFractionDigits: NaN })\n\t\t).toThrow(RangeError);\n\t\texpect(() => registry.number(\"en\", 2, null)).toThrow(TypeError);\n\t}\n\texpect(spy).toHaveBeenCalledTimes(4);\n});\n\ntest(\"bounds each constructor cache and evicts oldest entries\", async () => {\n\tconst registry = await loadRegistry();\n\tconst spy = spyConstructor(\"NumberFormat\");\n\tfor (let i = 0; i < 257; i++)\n\t\tregistry.number(\n\t\t\t\"en\",\n\t\t\ti,\n\t\t\t{\n\t\t\t\tminimumIntegerDigits: (i % 21) + 1,\n\t\t\t\tminimumFractionDigits: Math.floor(i / 21),\n\t\t\t},\n\t\t\ttrue\n\t\t);\n\tregistry.number(\n\t\t\"en\",\n\t\t2,\n\t\t{\n\t\t\tminimumIntegerDigits: 1,\n\t\t\tminimumFractionDigits: 0,\n\t\t},\n\t\ttrue\n\t);\n\texpect(spy).toHaveBeenCalledTimes(258);\n});\n\ntest(\"uses a replacement constructor after a polyfill is installed\", async () => {\n\tconst registry = await loadRegistry();\n\texpect(registry.plural(\"en\", 2)).toBe(\"other\");\n\tconst replacement = spyConstructor(\"PluralRules\");\n\texpect(registry.plural(\"en\", 2)).toBe(\"other\");\n\texpect(registry.plural(\"en\", 2)).toBe(\"other\");\n\texpect(replacement).toHaveBeenCalledTimes(1);\n});\n\ntest(\"direct calls preserve effective proxy options\", async () => {\n\tconst registry = await loadRegistry();\n\tlet style = \"short\";\n\tconst options = new Proxy(\n\t\t{},\n\t\t{\n\t\t\tget: (_, key) =>\n\t\t\t\tkey === \"dateStyle\" ? style : key === \"timeZone\" ? \"UTC\" : undefined,\n\t\t}\n\t);\n\tconst input = \"2026-10-11\";\n\texpect(registry.datetime(\"en\", input, options)).toBe(\n\t\tnew Intl.DateTimeFormat(\"en\", {\n\t\t\tdateStyle: \"short\",\n\t\t\ttimeZone: \"UTC\",\n\t\t}).format(new Date(input))\n\t);\n\tstyle = \"full\";\n\texpect(registry.datetime(\"en\", input, options)).toBe(\n\t\tnew Intl.DateTimeFormat(\"en\", {\n\t\t\tdateStyle: \"full\",\n\t\t\ttimeZone: \"UTC\",\n\t\t}).format(new Date(input))\n\t);\n});\n\ntest(\"direct calls never invoke prototype or descriptor traps\", async () => {\n\tconst registry = await loadRegistry();\n\tlet probes = 0;\n\tconst options = new Proxy(\n\t\t{},\n\t\t{\n\t\t\tgetPrototypeOf() {\n\t\t\t\tprobes++;\n\t\t\t\treturn Object.prototype;\n\t\t\t},\n\t\t\tgetOwnPropertyDescriptor() {\n\t\t\t\tprobes++;\n\t\t\t\treturn undefined;\n\t\t\t},\n\t\t\tget(_, key) {\n\t\t\t\treturn key === \"minimumFractionDigits\" ? probes : undefined;\n\t\t\t},\n\t\t}\n\t);\n\texpect(registry.number(\"en\", 2, options)).toBe(\"2\");\n\texpect(probes).toBe(0);\n});\n\ntest(\"keeps the cache helper private in the generated registry\", async () => {\n\texpect(await loadRegistry()).not.toHaveProperty(\"getIntlFormatter\");\n\texpect(await loadRegistry(true)).not.toHaveProperty(\"getIntlFormatter\");\n});\n\ntest(\"does not enumerate proxy option keys or inspect descriptors\", async () => {\n\tconst registry = await loadRegistry();\n\tconst options = new Proxy(\n\t\t{},\n\t\t{\n\t\t\townKeys() {\n\t\t\t\tthrow new Error(\"must not enumerate\");\n\t\t\t},\n\t\t\tgetOwnPropertyDescriptor() {\n\t\t\t\tthrow new Error(\"must not inspect\");\n\t\t\t},\n\t\t\tgetPrototypeOf() {\n\t\t\t\tthrow new Error(\"must not inspect prototype\");\n\t\t\t},\n\t\t}\n\t);\n\texpect(registry.number(\"en\", 2, options)).toBe(\"2\");\n});\n\ntest(\"does not touch later getters after Intl rejects an earlier option\", async () => {\n\tconst registry = await loadRegistry();\n\tlet touched = false;\n\tconst options = {\n\t\tlocaleMatcher: \"invalid\",\n\t\tget currency() {\n\t\t\ttouched = true;\n\t\t\treturn \"USD\";\n\t\t},\n\t};\n\texpect(() => registry.number(\"en\", 1, options)).toThrow(RangeError);\n\texpect(touched).toBe(false);\n});\n\ntest(\"compiled messages reuse formatters for their plain option literals\", async () => {\n\tconst compiled = compileMessage(\n\t\t[{ type: \"input-variable\", name: \"amount\" }],\n\t\t{ id: \"price\", bundleId: \"price\", locale: \"en\", selectors: [] },\n\t\t[\n\t\t\t{\n\t\t\t\tid: \"price\",\n\t\t\t\tmatches: [],\n\t\t\t\tpattern: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"expression\",\n\t\t\t\t\t\targ: { type: \"variable-reference\", name: \"amount\" },\n\t\t\t\t\t\tannotation: {\n\t\t\t\t\t\t\ttype: \"function-reference\",\n\t\t\t\t\t\t\tname: \"number\",\n\t\t\t\t\t\t\toptions: [],\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t]\n\t);\n\tconst source = `${createRegistry()}\\nconst registry = { number };\\nexport const price = ${compiled.code}`;\n\tconst { price } = await import(\n\t\t`data:text/javascript;base64,${Buffer.from(source).toString(\"base64\")}`\n\t);\n\tconst spy = spyConstructor(\"NumberFormat\");\n\tfor (let i = 0; i < 100; i++) expect(price({ amount: 12 })).toBe(\"12\");\n\texpect(spy).toHaveBeenCalledTimes(1);\n});\n\ntest(\"compiler-created object-valued options retain dynamic coercion\", async () => {\n\tconst registry = await loadRegistry();\n\tlet currency = \"USD\";\n\tconst options = { style: \"currency\", currency: { toString: () => currency } };\n\texpect(registry.number(\"en\", 2, options, true)).toBe(\"$2.00\");\n\tcurrency = \"EUR\";\n\texpect(registry.number(\"en\", 2, options, true)).toBe(\"€2.00\");\n});\n\ntest(\"preserves options inherited from an extended Object.prototype\", async () => {\n\tconst registry = await loadRegistry();\n\texpect(registry.number(\"en\", 2, {}, true)).toBe(\"2\");\n\tconst previous = Object.getOwnPropertyDescriptor(\n\t\tObject.prototype,\n\t\t\"minimumFractionDigits\"\n\t);\n\ttry {\n\t\tObject.defineProperty(Object.prototype, \"minimumFractionDigits\", {\n\t\t\tvalue: 2,\n\t\t\tconfigurable: true,\n\t\t});\n\t\texpect(registry.number(\"en\", 2, {}, true)).toBe(\"2.00\");\n\t} finally {\n\t\tif (previous)\n\t\t\tObject.defineProperty(\n\t\t\t\tObject.prototype,\n\t\t\t\t\"minimumFractionDigits\",\n\t\t\t\tprevious\n\t\t\t);\n\t\telse Reflect.deleteProperty(Object.prototype, \"minimumFractionDigits\");\n\t}\n});\n"]}
@@ -11,6 +11,8 @@ export declare function createRuntimeFile(args: {
11
11
  cookieName: NonNullable<CompilerOptions["cookieName"]>;
12
12
  cookieMaxAge: NonNullable<CompilerOptions["cookieMaxAge"]>;
13
13
  cookieDomain: CompilerOptions["cookieDomain"];
14
+ cookieSameSite?: CompilerOptions["cookieSameSite"];
15
+ cookieSecure?: CompilerOptions["cookieSecure"];
14
16
  urlPatterns?: CompilerOptions["urlPatterns"];
15
17
  trailingSlash?: CompilerOptions["trailingSlash"];
16
18
  experimentalMiddlewareLocaleSplitting: CompilerOptions["experimentalMiddlewareLocaleSplitting"];
@@ -42,6 +42,11 @@ export function createRuntimeFile(args) {
42
42
  }
43
43
  }
44
44
  }
45
+ const cookieSecure = args.compilerOptions.cookieSecure ?? false;
46
+ if (args.compilerOptions.cookieSameSite === "none" &&
47
+ cookieSecure === false) {
48
+ throw new Error(`cookieSameSite "none" requires cookieSecure to be true or "auto". Browsers reject SameSite=None cookies without the Secure attribute.`);
49
+ }
45
50
  const code = `
46
51
  ${needsUrlPatternPolyfill ? `import "@inlang/paraglide-js/urlpattern-polyfill";` : "/** @type {any} */\nconst URLPattern = {}"}
47
52
 
@@ -52,6 +57,10 @@ ${injectCode("./variables.js")
52
57
  .replace(`<cookie-name>`, `${args.compilerOptions.cookieName}`)
53
58
  .replace(`60 * 60 * 24 * 400`, `${args.compilerOptions.cookieMaxAge}`)
54
59
  .replace(`<cookie-domain>`, `${args.compilerOptions.cookieDomain}`)
60
+ .replace(`/** @type {"lax" | "strict" | "none" | undefined} */ (undefined)`, `/** @type {"lax" | "strict" | "none" | undefined} */ (${args.compilerOptions.cookieSameSite === undefined
61
+ ? "undefined"
62
+ : JSON.stringify(args.compilerOptions.cookieSameSite)})`)
63
+ .replace(`/** @type {boolean | "auto"} */ (false)`, `/** @type {boolean | "auto"} */ (${JSON.stringify(cookieSecure)})`)
55
64
  .replace(`export const routeStrategies = [];`, `export const routeStrategies = ${JSON.stringify(routeStrategies, null, 2)};`)
56
65
  .replace(`export const TREE_SHAKE_COOKIE_STRATEGY_USED = false;`, `const TREE_SHAKE_COOKIE_STRATEGY_USED = ${allUsedStrategies.has("cookie")};`)
57
66
  .replace(`export const TREE_SHAKE_URL_STRATEGY_USED = false;`, `const TREE_SHAKE_URL_STRATEGY_USED = ${allUsedStrategies.has("url")};`)