intor 2.3.35 → 2.4.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 (200) hide show
  1. package/README.md +7 -3
  2. package/dist/core/export/edge/index.js +6 -0
  3. package/dist/core/src/{server → core}/translator/create-translator.js +1 -1
  4. package/dist/core/src/core/utils/normalizers/normalize-locale.js +59 -0
  5. package/dist/core/src/core/utils/normalizers/normalize-query.js +25 -0
  6. package/dist/core/src/edge/helpers/get-translator.js +29 -0
  7. package/dist/core/src/edge/translator/init-translator.js +35 -0
  8. package/dist/core/src/routing/inbound/helpers/resolve-inbound-from-request.js +29 -0
  9. package/dist/core/src/routing/inbound/resolve-inbound.js +45 -0
  10. package/dist/core/src/routing/inbound/resolve-locale/resolve-locale.js +35 -0
  11. package/dist/{next/src/routing/inbound/resolve-pathname/resolve-pathname.js → core/src/routing/inbound/resolve-path/resolve-path.js} +2 -2
  12. package/dist/core/src/routing/locale/get-locale-from-accept-language.js +38 -0
  13. package/dist/core/src/routing/locale/get-locale-from-host.js +32 -0
  14. package/dist/core/src/routing/locale/get-locale-from-pathname.js +47 -0
  15. package/dist/core/src/routing/locale/get-locale-from-query.js +21 -0
  16. package/dist/core/src/server/helpers/get-translator.js +4 -5
  17. package/dist/core/src/server/translator/init-translator.js +1 -1
  18. package/dist/express/src/adapters/express/create-intor-handler.js +2 -4
  19. package/dist/express/src/adapters/express/get-translator.js +7 -1
  20. package/dist/express/src/{server → core}/translator/create-translator.js +1 -1
  21. package/dist/express/src/core/utils/parse-cookie-header.js +22 -0
  22. package/dist/express/src/routing/inbound/resolve-inbound.js +2 -2
  23. package/dist/express/src/routing/inbound/{resolve-pathname/resolve-pathname.js → resolve-path/resolve-path.js} +2 -2
  24. package/dist/express/src/server/helpers/get-translator.js +4 -5
  25. package/dist/express/src/server/translator/init-translator.js +1 -1
  26. package/dist/fastify/export/fastify/index.js +3 -0
  27. package/dist/fastify/src/adapters/fastify/create-intor-handler.js +54 -0
  28. package/dist/fastify/src/adapters/fastify/get-translator.js +28 -0
  29. package/dist/fastify/src/adapters/fastify/intor-fastify-plugin.js +34 -0
  30. package/dist/fastify/src/core/constants/prefix-placeholder.js +4 -0
  31. package/dist/fastify/src/core/error/intor-error.js +9 -0
  32. package/dist/fastify/src/core/logger/get-logger.js +39 -0
  33. package/dist/fastify/src/core/logger/global-logger-pool.js +8 -0
  34. package/dist/fastify/src/core/messages/load-remote-messages/collect-remote-resources.js +25 -0
  35. package/dist/fastify/src/core/messages/load-remote-messages/fetch-remote-resource.js +47 -0
  36. package/dist/fastify/src/core/messages/load-remote-messages/load-remote-messages.js +93 -0
  37. package/dist/fastify/src/core/messages/load-remote-messages/resolve-remote-resources.js +24 -0
  38. package/dist/fastify/src/core/messages/merge-messages.js +33 -0
  39. package/dist/fastify/src/core/messages/utils/is-valid-messages.js +44 -0
  40. package/dist/fastify/src/core/messages/utils/nest-object-from-path.js +21 -0
  41. package/dist/fastify/src/core/render/create-html-renderer.js +44 -0
  42. package/dist/fastify/src/core/render/utils/escape-html.js +10 -0
  43. package/dist/fastify/src/core/render/utils/render-attributes.js +17 -0
  44. package/dist/fastify/src/core/translator/create-t-rich.js +22 -0
  45. package/dist/{svelte-kit/src/server → fastify/src/core}/translator/create-translator.js +1 -1
  46. package/dist/fastify/src/core/utils/deep-merge.js +47 -0
  47. package/dist/fastify/src/core/utils/normalizers/normalize-cache-key.js +45 -0
  48. package/dist/fastify/src/core/utils/normalizers/normalize-locale.js +59 -0
  49. package/dist/fastify/src/core/utils/normalizers/normalize-pathname.js +43 -0
  50. package/dist/fastify/src/core/utils/normalizers/normalize-query.js +25 -0
  51. package/dist/fastify/src/core/utils/parse-cookie-header.js +22 -0
  52. package/dist/fastify/src/core/utils/resolve-loader-options.js +27 -0
  53. package/dist/fastify/src/routing/inbound/resolve-inbound.js +45 -0
  54. package/dist/fastify/src/routing/inbound/resolve-locale/resolve-locale.js +35 -0
  55. package/dist/{svelte-kit/src/routing/inbound/resolve-pathname/resolve-pathname.js → fastify/src/routing/inbound/resolve-path/resolve-path.js} +2 -2
  56. package/dist/fastify/src/routing/inbound/resolve-path/strategies/all.js +28 -0
  57. package/dist/fastify/src/routing/inbound/resolve-path/strategies/except-default.js +29 -0
  58. package/dist/fastify/src/routing/locale/get-locale-from-accept-language.js +38 -0
  59. package/dist/fastify/src/routing/locale/get-locale-from-host.js +32 -0
  60. package/dist/fastify/src/routing/locale/get-locale-from-pathname.js +47 -0
  61. package/dist/fastify/src/routing/locale/get-locale-from-query.js +21 -0
  62. package/dist/fastify/src/routing/pathname/canonicalize-pathname.js +47 -0
  63. package/dist/fastify/src/routing/pathname/localize-pathname.js +36 -0
  64. package/dist/fastify/src/routing/pathname/materialize-pathname.js +42 -0
  65. package/dist/fastify/src/routing/pathname/standardize-pathname.js +34 -0
  66. package/dist/fastify/src/server/helpers/get-translator.js +31 -0
  67. package/dist/fastify/src/server/messages/load-local-messages/cache/messages-pool.js +11 -0
  68. package/dist/fastify/src/server/messages/load-local-messages/load-local-messages.js +108 -0
  69. package/dist/fastify/src/server/messages/load-local-messages/read-locale-messages/collect-file-entries/collect-file-entries.js +91 -0
  70. package/dist/fastify/src/server/messages/load-local-messages/read-locale-messages/parse-file-entries/parse-file-entries.js +103 -0
  71. package/dist/fastify/src/server/messages/load-local-messages/read-locale-messages/parse-file-entries/utils/json-reader.js +12 -0
  72. package/dist/fastify/src/server/messages/load-local-messages/read-locale-messages/read-locale-messages.js +42 -0
  73. package/dist/fastify/src/server/messages/load-messages.js +78 -0
  74. package/dist/fastify/src/server/translator/init-translator.js +36 -0
  75. package/dist/hono/export/hono/index.js +3 -0
  76. package/dist/hono/src/adapters/hono/create-intor-handler.js +40 -0
  77. package/dist/hono/src/adapters/hono/get-translator.js +20 -0
  78. package/dist/hono/src/core/constants/prefix-placeholder.js +4 -0
  79. package/dist/hono/src/core/error/intor-error.js +9 -0
  80. package/dist/hono/src/core/logger/get-logger.js +39 -0
  81. package/dist/hono/src/core/logger/global-logger-pool.js +8 -0
  82. package/dist/hono/src/core/messages/load-remote-messages/collect-remote-resources.js +25 -0
  83. package/dist/hono/src/core/messages/load-remote-messages/fetch-remote-resource.js +47 -0
  84. package/dist/hono/src/core/messages/load-remote-messages/load-remote-messages.js +93 -0
  85. package/dist/hono/src/core/messages/load-remote-messages/resolve-remote-resources.js +24 -0
  86. package/dist/hono/src/core/messages/merge-messages.js +33 -0
  87. package/dist/hono/src/core/messages/utils/is-valid-messages.js +44 -0
  88. package/dist/hono/src/core/messages/utils/nest-object-from-path.js +21 -0
  89. package/dist/hono/src/core/render/create-html-renderer.js +44 -0
  90. package/dist/hono/src/core/render/utils/escape-html.js +10 -0
  91. package/dist/hono/src/core/render/utils/render-attributes.js +17 -0
  92. package/dist/hono/src/core/translator/create-t-rich.js +22 -0
  93. package/dist/{next/src/server → hono/src/core}/translator/create-translator.js +1 -1
  94. package/dist/hono/src/core/utils/deep-merge.js +47 -0
  95. package/dist/hono/src/core/utils/normalizers/normalize-locale.js +59 -0
  96. package/dist/hono/src/core/utils/normalizers/normalize-pathname.js +43 -0
  97. package/dist/hono/src/core/utils/normalizers/normalize-query.js +25 -0
  98. package/dist/hono/src/core/utils/parse-cookie-header.js +22 -0
  99. package/dist/hono/src/edge/helpers/get-translator.js +29 -0
  100. package/dist/hono/src/edge/translator/init-translator.js +35 -0
  101. package/dist/hono/src/routing/inbound/resolve-inbound.js +45 -0
  102. package/dist/hono/src/routing/inbound/resolve-locale/resolve-locale.js +35 -0
  103. package/dist/hono/src/routing/inbound/resolve-path/resolve-path.js +42 -0
  104. package/dist/hono/src/routing/inbound/resolve-path/strategies/all.js +28 -0
  105. package/dist/hono/src/routing/inbound/resolve-path/strategies/except-default.js +29 -0
  106. package/dist/hono/src/routing/inbound/resolve-path/strategies/none.js +8 -0
  107. package/dist/hono/src/routing/locale/get-locale-from-accept-language.js +38 -0
  108. package/dist/hono/src/routing/locale/get-locale-from-host.js +32 -0
  109. package/dist/hono/src/routing/locale/get-locale-from-pathname.js +47 -0
  110. package/dist/hono/src/routing/locale/get-locale-from-query.js +21 -0
  111. package/dist/hono/src/routing/pathname/canonicalize-pathname.js +47 -0
  112. package/dist/hono/src/routing/pathname/localize-pathname.js +36 -0
  113. package/dist/hono/src/routing/pathname/materialize-pathname.js +42 -0
  114. package/dist/hono/src/routing/pathname/standardize-pathname.js +34 -0
  115. package/dist/next/src/adapters/next/server/get-translator.js +7 -1
  116. package/dist/next/src/core/translator/create-translator.js +30 -0
  117. package/dist/next/src/routing/inbound/resolve-inbound.js +2 -2
  118. package/dist/next/src/routing/inbound/resolve-path/resolve-path.js +42 -0
  119. package/dist/next/src/routing/inbound/resolve-path/strategies/none.js +8 -0
  120. package/dist/next/src/server/helpers/get-translator.js +4 -5
  121. package/dist/next/src/server/translator/init-translator.js +1 -1
  122. package/dist/react/src/client/react/translator/use-translator.js +5 -5
  123. package/dist/react/src/client/shared/helpers/get-client-locale.js +1 -7
  124. package/dist/svelte/src/client/shared/helpers/get-client-locale.js +1 -7
  125. package/dist/svelte/src/client/svelte/translator/use-translator.js +6 -6
  126. package/dist/svelte-kit/src/adapters/svelte-kit/server/create-intor-handler.js +1 -3
  127. package/dist/svelte-kit/src/core/translator/create-translator.js +30 -0
  128. package/dist/svelte-kit/src/routing/inbound/resolve-inbound.js +2 -2
  129. package/dist/svelte-kit/src/routing/inbound/resolve-path/resolve-path.js +42 -0
  130. package/dist/svelte-kit/src/routing/inbound/resolve-path/strategies/all.js +28 -0
  131. package/dist/svelte-kit/src/routing/inbound/resolve-path/strategies/except-default.js +29 -0
  132. package/dist/svelte-kit/src/routing/inbound/resolve-path/strategies/none.js +8 -0
  133. package/dist/svelte-kit/src/server/translator/init-translator.js +1 -1
  134. package/dist/types/export/edge/index.d.ts +2 -0
  135. package/dist/types/export/fastify/index.d.ts +1 -0
  136. package/dist/types/export/hono/index.d.ts +1 -0
  137. package/dist/types/src/adapters/express/create-intor-handler.d.ts +2 -4
  138. package/dist/types/src/adapters/express/get-translator.d.ts +5 -6
  139. package/dist/types/src/adapters/express/global.d.ts +4 -4
  140. package/dist/types/src/adapters/fastify/create-intor-handler.d.ts +12 -0
  141. package/dist/types/src/adapters/fastify/get-translator.d.ts +17 -0
  142. package/dist/types/src/adapters/fastify/global.d.ts +17 -0
  143. package/dist/types/src/adapters/fastify/index.d.ts +3 -0
  144. package/dist/types/src/adapters/fastify/intor-fastify-plugin.d.ts +21 -0
  145. package/dist/types/src/adapters/hono/create-intor-handler.d.ts +10 -0
  146. package/dist/types/src/adapters/hono/get-translator.d.ts +17 -0
  147. package/dist/types/src/adapters/hono/global.d.ts +17 -0
  148. package/dist/types/src/adapters/hono/index.d.ts +3 -0
  149. package/dist/types/src/adapters/next/server/get-translator.d.ts +5 -6
  150. package/dist/types/src/adapters/svelte-kit/server/create-intor-handler.d.ts +0 -2
  151. package/dist/types/src/client/react/translator/translator-instance.d.ts +1 -5
  152. package/dist/types/src/client/react/translator/use-translator.d.ts +4 -3
  153. package/dist/types/src/client/shared/helpers/get-client-locale.d.ts +3 -8
  154. package/dist/types/src/client/svelte/translator/translator-instance.d.ts +7 -7
  155. package/dist/types/src/client/svelte/translator/use-translator.d.ts +5 -4
  156. package/dist/types/src/client/vue/translator/translator-instance.d.ts +2 -2
  157. package/dist/types/src/client/vue/translator/use-translator.d.ts +5 -4
  158. package/dist/types/src/core/index.d.ts +2 -2
  159. package/dist/types/src/core/translator/index.d.ts +1 -0
  160. package/dist/types/src/core/types/translator-instance.d.ts +9 -2
  161. package/dist/types/src/core/utils/index.d.ts +1 -0
  162. package/dist/types/src/edge/helpers/get-translator.d.ts +15 -0
  163. package/dist/types/src/edge/helpers/index.d.ts +1 -0
  164. package/dist/types/src/edge/index.d.ts +1 -0
  165. package/dist/types/src/edge/translator/index.d.ts +1 -0
  166. package/dist/types/src/edge/translator/init-translator.d.ts +14 -0
  167. package/dist/types/src/routing/inbound/helpers/index.d.ts +1 -0
  168. package/dist/types/src/routing/inbound/helpers/resolve-inbound-from-request.d.ts +3 -0
  169. package/dist/types/src/routing/inbound/index.d.ts +1 -0
  170. package/dist/types/src/routing/inbound/resolve-path/index.d.ts +1 -0
  171. package/dist/types/src/routing/inbound/{resolve-pathname/resolve-pathname.d.ts → resolve-path/resolve-path.d.ts} +2 -2
  172. package/dist/types/src/routing/inbound/{resolve-pathname → resolve-path}/strategies/all.d.ts +2 -2
  173. package/dist/types/src/routing/inbound/{resolve-pathname → resolve-path}/strategies/except-default.d.ts +2 -2
  174. package/dist/types/src/routing/inbound/resolve-path/strategies/none.d.ts +5 -0
  175. package/dist/types/src/routing/inbound/{resolve-pathname → resolve-path}/types.d.ts +5 -5
  176. package/dist/types/src/routing/index.d.ts +1 -1
  177. package/dist/types/src/server/helpers/get-translator.d.ts +6 -8
  178. package/dist/types/src/server/index.d.ts +0 -2
  179. package/dist/types/src/server/translator/index.d.ts +1 -2
  180. package/dist/types/src/server/translator/init-translator.d.ts +3 -2
  181. package/dist/vue/src/client/shared/helpers/get-client-locale.js +1 -7
  182. package/dist/vue/src/client/vue/translator/use-translator.js +5 -5
  183. package/package.json +18 -1
  184. package/dist/types/src/routing/inbound/resolve-pathname/index.d.ts +0 -1
  185. package/dist/types/src/routing/inbound/resolve-pathname/strategies/none.d.ts +0 -5
  186. package/dist/types/src/server/shared/utils/index.d.ts +0 -1
  187. package/dist/types/src/server/translator/translator-instance.d.ts +0 -10
  188. /package/dist/{express/src/server/shared → core/src/core}/utils/parse-cookie-header.js +0 -0
  189. /package/dist/{express/src/routing/inbound/resolve-pathname → core/src/routing/inbound/resolve-path}/strategies/all.js +0 -0
  190. /package/dist/{express/src/routing/inbound/resolve-pathname → core/src/routing/inbound/resolve-path}/strategies/except-default.js +0 -0
  191. /package/dist/{express/src/routing/inbound/resolve-pathname → core/src/routing/inbound/resolve-path}/strategies/none.js +0 -0
  192. /package/dist/{svelte-kit/src/routing/inbound/resolve-pathname → express/src/routing/inbound/resolve-path}/strategies/all.js +0 -0
  193. /package/dist/{svelte-kit/src/routing/inbound/resolve-pathname → express/src/routing/inbound/resolve-path}/strategies/except-default.js +0 -0
  194. /package/dist/{next/src/routing/inbound/resolve-pathname → express/src/routing/inbound/resolve-path}/strategies/none.js +0 -0
  195. /package/dist/{svelte-kit/src/routing/inbound/resolve-pathname → fastify/src/routing/inbound/resolve-path}/strategies/none.js +0 -0
  196. /package/dist/next/src/routing/inbound/{resolve-pathname → resolve-path}/strategies/all.js +0 -0
  197. /package/dist/next/src/routing/inbound/{resolve-pathname → resolve-path}/strategies/except-default.js +0 -0
  198. /package/dist/types/src/{server → core}/translator/create-translator.d.ts +0 -0
  199. /package/dist/types/src/{server/shared → core}/utils/parse-cookie-header.d.ts +0 -0
  200. /package/dist/types/src/routing/inbound/{resolve-pathname → resolve-path}/strategies/index.d.ts +0 -0
package/README.md CHANGED
@@ -2,16 +2,20 @@
2
2
 
3
3
  <div align="center">
4
4
 
5
- A lightweight, framework-agnostic i18n engine that works instantly with a clean, type-safe API.
6
- Fast to start, easy to extend, and free from the usual i18n heaviness.
5
+ The i18n library for modern JavaScript
7
6
 
8
7
  </div>
9
8
 
10
9
  <div align="center">
11
10
 
12
11
  [![NPM version](https://img.shields.io/npm/v/intor?style=flat&colorA=000000&colorB=000000)](https://www.npmjs.com/package/intor)
13
- [![Bundle size](https://img.shields.io/bundlephobia/minzip/intor?style=flat&colorA=000000&colorB=000000)](https://bundlephobia.com/package/intor)
14
12
  [![TypeScript](https://img.shields.io/badge/TypeScript-%E2%9C%94-blue?style=flat&colorA=000000&colorB=000000)](https://www.typescriptlang.org/)
15
13
  [![License](https://img.shields.io/npm/l/intor?style=flat&colorA=000000&colorB=000000)](LICENSE)
16
14
 
17
15
  </div>
16
+
17
+ <div align="center">
18
+
19
+ #### [![Documentation ↗](https://img.shields.io/badge/%20Documentation%20%F0%9F%93%9A%20-3e668c?style=for-the-badge)](https://intor-doc.pages.dev/en-US/frameworks)
20
+
21
+ </div>
@@ -0,0 +1,6 @@
1
+ export { getTranslator } from '../../src/edge/helpers/get-translator.js';
2
+ import '../../src/core/error/intor-error.js';
3
+ import 'logry';
4
+ import 'p-limit';
5
+ import 'intor-translator';
6
+ export { resolveInboundFromRequest } from '../../src/routing/inbound/helpers/resolve-inbound-from-request.js';
@@ -1,5 +1,5 @@
1
1
  import { Translator } from 'intor-translator';
2
- import { mergeMessages } from '../../core/messages/merge-messages.js';
2
+ import { mergeMessages } from '../messages/merge-messages.js';
3
3
 
4
4
  /**
5
5
  * Create a server-side Translator instance for a fixed locale.
@@ -0,0 +1,59 @@
1
+ const toCanonical = (input) => {
2
+ try {
3
+ return Intl.getCanonicalLocales(input)[0];
4
+ }
5
+ catch {
6
+ return;
7
+ }
8
+ };
9
+ /**
10
+ * Normalizes a locale string and resolves the best match
11
+ * from a list of supported locales.
12
+ *
13
+ * Resolution strategy:
14
+ *
15
+ * 1. Exact canonical match (BCP 47)
16
+ * 2. Base language fallback
17
+ * - Falls back by base language when no exact match is found
18
+ * (e.g. `"en"` → `"en-US"`).
19
+ * - Script and region subtags are ignored during this step
20
+ * (e.g. `"zh-Hans"` → `"zh-Hant-TW"`)
21
+ * - Preference is determined by the order of `supportedLocales`.
22
+ *
23
+ * Returns `undefined` if no suitable match is found.
24
+ *
25
+ * Notes:
26
+ * - Invalid locale inputs are ignored gracefully.
27
+ * - Always returns an original entry from `supportedLocales`.
28
+ * - Requires `Intl` locale support in the runtime.
29
+ */
30
+ const normalizeLocale = (locale, supportedLocales = []) => {
31
+ if (!locale || supportedLocales.length === 0)
32
+ return;
33
+ const canonicalLocale = toCanonical(locale);
34
+ if (!canonicalLocale)
35
+ return;
36
+ const supportedCanonicalMap = new Map();
37
+ for (const l of supportedLocales) {
38
+ const normalized = toCanonical(l);
39
+ if (normalized) {
40
+ supportedCanonicalMap.set(normalized, l);
41
+ }
42
+ }
43
+ // 1. Exact match
44
+ if (supportedCanonicalMap.has(canonicalLocale)) {
45
+ return supportedCanonicalMap.get(canonicalLocale);
46
+ }
47
+ const baseLang = canonicalLocale.split("-")[0];
48
+ // 2. Match by same base language (e.g., "en" matches "en-US" or "en-GB")
49
+ for (const [key, original] of supportedCanonicalMap) {
50
+ const supportedBase = key.split("-")[0];
51
+ if (supportedBase === baseLang) {
52
+ return original;
53
+ }
54
+ }
55
+ // 3. No match
56
+ return;
57
+ };
58
+
59
+ export { normalizeLocale };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Normalize a raw query object into a string-only map.
3
+ *
4
+ * This utility is used to sanitize framework-specific query inputs
5
+ * into a stable shape that the routing core
6
+ * can safely consume.
7
+ *
8
+ * Behavior:
9
+ * - Keeps only entries whose values are strings
10
+ * - Ignores arrays, objects, and other non-string values
11
+ * - Does not throw or attempt to coerce values
12
+ *
13
+ * This function is intentionally conservative by design.
14
+ */
15
+ function normalizeQuery(query) {
16
+ const normalized = {};
17
+ for (const [key, value] of Object.entries(query)) {
18
+ if (typeof value === "string") {
19
+ normalized[key] = value;
20
+ }
21
+ }
22
+ return normalized;
23
+ }
24
+
25
+ export { normalizeQuery };
@@ -0,0 +1,29 @@
1
+ import '../../core/error/intor-error.js';
2
+ import 'logry';
3
+ import 'p-limit';
4
+ import 'intor-translator';
5
+ import { createTRich } from '../../core/translator/create-t-rich.js';
6
+ import { initTranslator } from '../translator/init-translator.js';
7
+
8
+ /**
9
+ * Get a edge-runtime translator for the current execution context.
10
+ */
11
+ async function getTranslator(config, params) {
12
+ const { locale, fetch, preKey, handlers, plugins } = params;
13
+ // Initialize a locale-bound translator snapshot with messages loaded
14
+ const translator = await initTranslator(config, locale, {
15
+ fetch: fetch || globalThis.fetch,
16
+ plugins,
17
+ handlers,
18
+ });
19
+ const scoped = translator.scoped(preKey);
20
+ return {
21
+ messages: translator.messages,
22
+ locale: translator.locale,
23
+ hasKey: scoped.hasKey,
24
+ t: scoped.t,
25
+ tRich: createTRich(scoped.t),
26
+ };
27
+ }
28
+
29
+ export { getTranslator };
@@ -0,0 +1,35 @@
1
+ import '../../core/error/intor-error.js';
2
+ import 'logry';
3
+ import { loadRemoteMessages } from '../../core/messages/load-remote-messages/load-remote-messages.js';
4
+ import { createTranslator } from '../../core/translator/create-translator.js';
5
+ import 'intor-translator';
6
+
7
+ /**
8
+ * Initialize a locale-bound Translator snapshot.
9
+ *
10
+ * - Loads translation messages using the configured remote loader, if present.
11
+ * - Creates an immutable Translator instance for edge runtimes
12
+ */
13
+ async function initTranslator(config, locale, options) {
14
+ const { loader } = config;
15
+ const { fetch, handlers, plugins } = options;
16
+ // Load messages
17
+ let messages = {};
18
+ if (loader && loader.mode === "remote") {
19
+ const loaded = await loadRemoteMessages({
20
+ locale,
21
+ fallbackLocales: config.fallbackLocales[locale] || [],
22
+ namespaces: loader.namespaces,
23
+ concurrency: loader.concurrency,
24
+ fetch,
25
+ url: loader.url,
26
+ headers: loader.headers,
27
+ loggerOptions: config.logger,
28
+ });
29
+ messages = loaded || {};
30
+ }
31
+ // Create immutable translator snapshot
32
+ return createTranslator({ config, locale, messages, handlers, plugins });
33
+ }
34
+
35
+ export { initTranslator };
@@ -0,0 +1,29 @@
1
+ import '../../../core/error/intor-error.js';
2
+ import { parseCookieHeader } from '../../../core/utils/parse-cookie-header.js';
3
+ import { normalizeQuery } from '../../../core/utils/normalizers/normalize-query.js';
4
+ import 'logry';
5
+ import 'p-limit';
6
+ import 'intor-translator';
7
+ import { getLocaleFromAcceptLanguage } from '../../locale/get-locale-from-accept-language.js';
8
+ import { resolveInbound } from '../resolve-inbound.js';
9
+
10
+ async function resolveInboundFromRequest(config, request) {
11
+ const url = new URL(request.url);
12
+ const headers = request.headers;
13
+ // Cookie
14
+ const cookies = parseCookieHeader(headers.get("cookie") ?? undefined);
15
+ const cookieLocale = cookies[config.cookie.name];
16
+ // Query
17
+ const normalizedQuery = normalizeQuery(Object.fromEntries(url.searchParams));
18
+ // Accept-Language
19
+ const acceptLanguage = headers.get("accept-language") ?? undefined;
20
+ const localeFromAcceptLanguage = getLocaleFromAcceptLanguage(acceptLanguage, config.supportedLocales);
21
+ return resolveInbound(config, url.pathname, {
22
+ host: url.hostname,
23
+ query: normalizedQuery,
24
+ cookie: cookieLocale,
25
+ detected: localeFromAcceptLanguage || config.defaultLocale,
26
+ });
27
+ }
28
+
29
+ export { resolveInboundFromRequest };
@@ -0,0 +1,45 @@
1
+ import { getLocaleFromPathname } from '../locale/get-locale-from-pathname.js';
2
+ import { getLocaleFromHost } from '../locale/get-locale-from-host.js';
3
+ import { getLocaleFromQuery } from '../locale/get-locale-from-query.js';
4
+ import { resolveLocale } from './resolve-locale/resolve-locale.js';
5
+ import { resolvePath } from './resolve-path/resolve-path.js';
6
+
7
+ /**
8
+ * Resolves inbound routing state.
9
+ *
10
+ * - Resolves the effective locale from inbound inputs
11
+ * - Localizes the pathname based on the resolved locale
12
+ * - Indicates whether a redirect is required
13
+ *
14
+ * No side effects. No navigation.
15
+ */
16
+ async function resolveInbound(config, rawPathname, localeInputs, options) {
17
+ const { queryKey } = config.routing.inbound;
18
+ const { host, query, cookie, detected } = localeInputs;
19
+ // ------------------------------------------------------
20
+ // Resolve locale from inbound inputs
21
+ // ------------------------------------------------------
22
+ const pathLocale = getLocaleFromPathname(rawPathname, config);
23
+ const { locale, localeSource } = resolveLocale(config, {
24
+ path: { locale: pathLocale },
25
+ host: { locale: getLocaleFromHost(host) },
26
+ query: { locale: getLocaleFromQuery(query, queryKey) },
27
+ cookie: { locale: cookie },
28
+ detected: { locale: detected },
29
+ });
30
+ // ------------------------------------------------------
31
+ // Resolve localized pathname and redirect requirement
32
+ // ------------------------------------------------------
33
+ const { pathname, shouldRedirect } = resolvePath(config, rawPathname, {
34
+ locale,
35
+ hasPathLocale: !!pathLocale,
36
+ hasPersisted: !!cookie});
37
+ return {
38
+ locale,
39
+ localeSource,
40
+ pathname,
41
+ shouldRedirect,
42
+ };
43
+ }
44
+
45
+ export { resolveInbound };
@@ -0,0 +1,35 @@
1
+ import '../../../core/error/intor-error.js';
2
+ import { normalizeLocale } from '../../../core/utils/normalizers/normalize-locale.js';
3
+ import 'logry';
4
+ import 'p-limit';
5
+ import 'intor-translator';
6
+
7
+ /**
8
+ * Resolve the active locale from inbound routing configuration.
9
+ *
10
+ * Iterates through configured locale sources and returns the first normalized, supported locale.
11
+ *
12
+ * Falls back to the detected locale or the default locale if none match.
13
+ */
14
+ function resolveLocale(config, context) {
15
+ const { localeSources } = config.routing.inbound;
16
+ for (const source of localeSources) {
17
+ const locale = context[source]?.locale;
18
+ const normalized = normalizeLocale(locale, config.supportedLocales);
19
+ if (!normalized)
20
+ continue;
21
+ return {
22
+ locale: normalized,
23
+ localeSource: source,
24
+ };
25
+ }
26
+ // Fallback: detected is always available
27
+ const fallback = normalizeLocale(context.detected.locale, config.supportedLocales) ||
28
+ config.defaultLocale;
29
+ return {
30
+ locale: fallback,
31
+ localeSource: "detected",
32
+ };
33
+ }
34
+
35
+ export { resolveLocale };
@@ -12,7 +12,7 @@ const assertNever = (x) => {
12
12
  * The resolved pathname represents the final, normalized form
13
13
  * used for routing and navigation.
14
14
  */
15
- const resolvePathname = (config, rawPathname, context) => {
15
+ const resolvePath = (config, rawPathname, context) => {
16
16
  const { localePrefix } = config.routing;
17
17
  let directive;
18
18
  switch (localePrefix) {
@@ -39,4 +39,4 @@ const resolvePathname = (config, rawPathname, context) => {
39
39
  };
40
40
  };
41
41
 
42
- export { resolvePathname };
42
+ export { resolvePath };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Get locale candidate from the `Accept-Language` header.
3
+ *
4
+ * Parses language priorities and returns the highest-priority
5
+ * language present in `supportedLocales`, without normalization.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * getLocaleFromAcceptLanguage("en-US,en;q=0.8,zh-TW;q=0.9", ["en-US", "zh-TW"])
10
+ * // => "en-US"
11
+ *
12
+ * getLocaleFromAcceptLanguage("fr,ja;q=0.9", ["en", "zh-TW"])
13
+ * // => undefined
14
+ * ```
15
+ */
16
+ const getLocaleFromAcceptLanguage = (acceptLanguageHeader, supportedLocales) => {
17
+ if (!acceptLanguageHeader || supportedLocales.length === 0) {
18
+ return;
19
+ }
20
+ const supportedLocalesSet = new Set(supportedLocales);
21
+ // 1. Parse Accept-Language header into language + priority pairs
22
+ const parsedLanguages = acceptLanguageHeader.split(",").map((part) => {
23
+ const [rawLang, rawQ] = part.split(";");
24
+ const lang = rawLang.trim();
25
+ const q = rawQ ? Number.parseFloat(rawQ.split("=")[1]) : 1;
26
+ return {
27
+ lang,
28
+ q: Number.isNaN(q) ? 0 : q, // Invalid q values have lowest priority
29
+ };
30
+ });
31
+ // 2. Sort by priority (highest first)
32
+ const sortedByPriority = parsedLanguages.toSorted((a, b) => b.q - a.q);
33
+ // 3. Pick the first language explicitly supported
34
+ const preferred = sortedByPriority.find(({ lang }) => supportedLocalesSet.has(lang))?.lang;
35
+ return preferred;
36
+ };
37
+
38
+ export { getLocaleFromAcceptLanguage };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Get locale candidate from hostname.
3
+ *
4
+ * Returns the left-most hostname label, without validation or normalization.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * getLocaleFromHost("en.example.com")
9
+ * // => "en"
10
+ *
11
+ * getLocaleFromHost("example.com")
12
+ * // => "example"
13
+ *
14
+ * getLocaleFromHost("api.jp.example.com")
15
+ * // => "api"
16
+ *
17
+ * getLocaleFromHost("localhost")
18
+ * // => undefined
19
+ * ```
20
+ */
21
+ function getLocaleFromHost(host) {
22
+ if (!host)
23
+ return;
24
+ // Remove port (e.g. localhost:3000)
25
+ const hostname = host.split(":")[0];
26
+ const parts = hostname.split(".");
27
+ if (parts.length < 2)
28
+ return;
29
+ return parts[0];
30
+ }
31
+
32
+ export { getLocaleFromHost };
@@ -0,0 +1,47 @@
1
+ import '../../core/error/intor-error.js';
2
+ import { normalizePathname } from '../../core/utils/normalizers/normalize-pathname.js';
3
+ import 'logry';
4
+ import 'p-limit';
5
+ import 'intor-translator';
6
+
7
+ /**
8
+ * Get locale from pathname.
9
+ *
10
+ * Extracts the first pathname segment (after basePath) as a locale
11
+ * if it exactly matches one of the supported locales.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * getLocaleFromPathname(config, "/en/about")
16
+ * // => "en"
17
+ * getLocaleFromPathname(config, "/zh-TW")
18
+ * // => "zh-TW"
19
+ * getLocaleFromPathname(config, "/about")
20
+ * // => undefined
21
+ *
22
+ * // config.routing.basePath: "/app"
23
+ * getLocaleFromPathname(config, "/app/en/dashboard")
24
+ * // => "en"
25
+ * ```
26
+ */
27
+ function getLocaleFromPathname(pathname, config) {
28
+ const { routing, supportedLocales } = config;
29
+ const { basePath } = routing;
30
+ // 1. Normalize pathname
31
+ const normalizedPathname = normalizePathname(pathname);
32
+ // 2. Strip basePath
33
+ let prefixedPathname = normalizedPathname;
34
+ if (basePath && normalizedPathname === basePath) {
35
+ prefixedPathname = "/";
36
+ }
37
+ else if (basePath && normalizedPathname.startsWith(basePath + "/")) {
38
+ prefixedPathname = normalizedPathname.slice(basePath.length);
39
+ }
40
+ // 3. Detect locale segment
41
+ const firstSegment = prefixedPathname.split("/").find(Boolean);
42
+ return firstSegment && supportedLocales.includes(firstSegment)
43
+ ? firstSegment
44
+ : undefined;
45
+ }
46
+
47
+ export { getLocaleFromPathname };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Get locale candidate from URL query parameters.
3
+ *
4
+ * Extracts the value of the configured query key, without
5
+ * validation or normalization.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * getLocaleFromQuery({ locale: "en" }, "locale")
10
+ * // => "en"
11
+ *
12
+ * getLocaleFromQuery({}, "locale")
13
+ * // => undefined
14
+ */
15
+ function getLocaleFromQuery(query, queryKey) {
16
+ if (!query)
17
+ return;
18
+ return query[queryKey];
19
+ }
20
+
21
+ export { getLocaleFromQuery };
@@ -1,10 +1,13 @@
1
1
  import '../../core/error/intor-error.js';
2
2
  import 'logry';
3
3
  import 'p-limit';
4
+ import 'intor-translator';
4
5
  import { createTRich } from '../../core/translator/create-t-rich.js';
5
6
  import { initTranslator } from '../translator/init-translator.js';
6
7
 
7
- // Implementation
8
+ /**
9
+ * Get a server-side translator for the current execution context.
10
+ */
8
11
  async function getTranslator(config, params) {
9
12
  const { locale, readers, allowCacheWrite, fetch, preKey, handlers, plugins } = params;
10
13
  // Initialize a locale-bound translator snapshot with messages loaded
@@ -22,10 +25,6 @@ async function getTranslator(config, params) {
22
25
  hasKey: scoped.hasKey,
23
26
  t: scoped.t,
24
27
  tRich: createTRich(scoped.t),
25
- // NOTE:
26
- // The runtime implementation is intentionally erased.
27
- // Type safety is guaranteed by public type contracts.
28
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
28
  };
30
29
  }
31
30
 
@@ -2,11 +2,11 @@ import '../../core/error/intor-error.js';
2
2
  import { resolveLoaderOptions } from '../../core/utils/resolve-loader-options.js';
3
3
  import 'logry';
4
4
  import 'p-limit';
5
+ import { createTranslator } from '../../core/translator/create-translator.js';
5
6
  import 'intor-translator';
6
7
  import { loadMessages } from '../messages/load-messages.js';
7
8
  import 'node:path';
8
9
  import 'node:fs/promises';
9
- import { createTranslator } from './create-translator.js';
10
10
 
11
11
  /**
12
12
  * Initialize a locale-bound Translator snapshot.
@@ -1,4 +1,5 @@
1
1
  import '../../core/error/intor-error.js';
2
+ import { parseCookieHeader } from '../../core/utils/parse-cookie-header.js';
2
3
  import { normalizeQuery } from '../../core/utils/normalizers/normalize-query.js';
3
4
  import 'logry';
4
5
  import 'p-limit';
@@ -8,14 +9,11 @@ import { getLocaleFromAcceptLanguage } from '../../routing/locale/get-locale-fro
8
9
  import 'node:path';
9
10
  import 'node:fs/promises';
10
11
  import { getTranslator } from '../../server/helpers/get-translator.js';
11
- import { parseCookieHeader } from '../../server/shared/utils/parse-cookie-header.js';
12
12
 
13
13
  /**
14
14
  * Resolves locale-aware routing for the current execution context.
15
15
  *
16
- * The resolved routing state is exposed via response headers.
17
- *
18
- * - Permits cache writes during server execution.
16
+ * - Binds resolved routing state to the request.
19
17
  * - Convenience routing shortcuts are also bound to the request for downstream consumption.
20
18
  *
21
19
  * @platform Express
@@ -6,7 +6,13 @@ import 'node:path';
6
6
  import 'node:fs/promises';
7
7
  import { getTranslator as getTranslator$1 } from '../../server/helpers/get-translator.js';
8
8
 
9
- // Implementation
9
+ /**
10
+ * Get a server-side translator for the current execution context.
11
+ *
12
+ * - Automatically resolves the locale from the framework context.
13
+ *
14
+ * @platform Express
15
+ */
10
16
  async function getTranslator(config, req, params) {
11
17
  const { preKey, handlers, plugins, readers, allowCacheWrite } = params || {};
12
18
  return getTranslator$1(config, {
@@ -1,5 +1,5 @@
1
1
  import { Translator } from 'intor-translator';
2
- import { mergeMessages } from '../../core/messages/merge-messages.js';
2
+ import { mergeMessages } from '../messages/merge-messages.js';
3
3
 
4
4
  /**
5
5
  * Create a server-side Translator instance for a fixed locale.
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Parse a raw HTTP Cookie header into a key-value record.
3
+ */
4
+ function parseCookieHeader(cookieHeader) {
5
+ if (!cookieHeader)
6
+ return {};
7
+ const record = {};
8
+ // Split the Cookie header into individual key-value pairs
9
+ const pairs = cookieHeader.split(";");
10
+ for (const pair of pairs) {
11
+ // Locate the first "=" to separate key and value
12
+ const index = pair.indexOf("=");
13
+ if (index === -1)
14
+ continue;
15
+ const key = pair.slice(0, index).trim();
16
+ const value = pair.slice(index + 1).trim();
17
+ record[key] = decodeURIComponent(value);
18
+ }
19
+ return record;
20
+ }
21
+
22
+ export { parseCookieHeader };
@@ -2,7 +2,7 @@ import { getLocaleFromPathname } from '../locale/get-locale-from-pathname.js';
2
2
  import { getLocaleFromHost } from '../locale/get-locale-from-host.js';
3
3
  import { getLocaleFromQuery } from '../locale/get-locale-from-query.js';
4
4
  import { resolveLocale } from './resolve-locale/resolve-locale.js';
5
- import { resolvePathname } from './resolve-pathname/resolve-pathname.js';
5
+ import { resolvePath } from './resolve-path/resolve-path.js';
6
6
 
7
7
  /**
8
8
  * Resolves inbound routing state.
@@ -30,7 +30,7 @@ async function resolveInbound(config, rawPathname, localeInputs, options) {
30
30
  // ------------------------------------------------------
31
31
  // Resolve localized pathname and redirect requirement
32
32
  // ------------------------------------------------------
33
- const { pathname, shouldRedirect } = resolvePathname(config, rawPathname, {
33
+ const { pathname, shouldRedirect } = resolvePath(config, rawPathname, {
34
34
  locale,
35
35
  hasPathLocale: !!pathLocale,
36
36
  hasPersisted: !!cookie});
@@ -12,7 +12,7 @@ const assertNever = (x) => {
12
12
  * The resolved pathname represents the final, normalized form
13
13
  * used for routing and navigation.
14
14
  */
15
- const resolvePathname = (config, rawPathname, context) => {
15
+ const resolvePath = (config, rawPathname, context) => {
16
16
  const { localePrefix } = config.routing;
17
17
  let directive;
18
18
  switch (localePrefix) {
@@ -39,4 +39,4 @@ const resolvePathname = (config, rawPathname, context) => {
39
39
  };
40
40
  };
41
41
 
42
- export { resolvePathname };
42
+ export { resolvePath };
@@ -1,10 +1,13 @@
1
1
  import '../../core/error/intor-error.js';
2
2
  import 'logry';
3
3
  import 'p-limit';
4
+ import 'intor-translator';
4
5
  import { createTRich } from '../../core/translator/create-t-rich.js';
5
6
  import { initTranslator } from '../translator/init-translator.js';
6
7
 
7
- // Implementation
8
+ /**
9
+ * Get a server-side translator for the current execution context.
10
+ */
8
11
  async function getTranslator(config, params) {
9
12
  const { locale, readers, allowCacheWrite, fetch, preKey, handlers, plugins } = params;
10
13
  // Initialize a locale-bound translator snapshot with messages loaded
@@ -22,10 +25,6 @@ async function getTranslator(config, params) {
22
25
  hasKey: scoped.hasKey,
23
26
  t: scoped.t,
24
27
  tRich: createTRich(scoped.t),
25
- // NOTE:
26
- // The runtime implementation is intentionally erased.
27
- // Type safety is guaranteed by public type contracts.
28
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
28
  };
30
29
  }
31
30
 
@@ -2,11 +2,11 @@ import '../../core/error/intor-error.js';
2
2
  import { resolveLoaderOptions } from '../../core/utils/resolve-loader-options.js';
3
3
  import 'logry';
4
4
  import 'p-limit';
5
+ import { createTranslator } from '../../core/translator/create-translator.js';
5
6
  import 'intor-translator';
6
7
  import { loadMessages } from '../messages/load-messages.js';
7
8
  import 'node:path';
8
9
  import 'node:fs/promises';
9
- import { createTranslator } from './create-translator.js';
10
10
 
11
11
  /**
12
12
  * Initialize a locale-bound Translator snapshot.
@@ -0,0 +1,3 @@
1
+ export { intorFastifyPlugin } from '../../src/adapters/fastify/intor-fastify-plugin.js';
2
+ export { getTranslator } from '../../src/adapters/fastify/get-translator.js';
3
+ import 'fastify';