@koine/i18n 2.0.0-beta.76 → 2.0.0-beta.78

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 (260) hide show
  1. package/README.md +1 -0
  2. package/api.cjs.js +2203 -0
  3. package/api.esm.js +2179 -0
  4. package/compiler-sync.cjs.d.ts +1 -0
  5. package/compiler-sync.cjs.default.js +1 -0
  6. package/compiler-sync.cjs.js +20 -0
  7. package/compiler-sync.cjs.mjs +2 -0
  8. package/compiler-sync.esm.js +16 -0
  9. package/compiler-worker.cjs.d.ts +1 -0
  10. package/compiler-worker.cjs.default.js +1 -0
  11. package/compiler-worker.cjs.js +18 -0
  12. package/compiler-worker.cjs.mjs +2 -0
  13. package/compiler-worker.esm.js +16 -0
  14. package/compiler.cjs.d.ts +1 -0
  15. package/compiler.cjs.default.js +1 -0
  16. package/compiler.cjs.js +21 -0
  17. package/compiler.cjs.mjs +2 -0
  18. package/compiler.esm.js +13 -0
  19. package/formatRoutePathname.cjs.js +12 -0
  20. package/formatRoutePathname.esm.js +10 -0
  21. package/index.cjs.d.ts +1 -0
  22. package/index.cjs.default.js +1 -0
  23. package/index.cjs.js +47 -0
  24. package/index.cjs.mjs +2 -0
  25. package/index.esm.js +42 -0
  26. package/next.cjs.d.ts +1 -0
  27. package/next.cjs.default.js +1 -0
  28. package/next.cjs.js +377 -0
  29. package/next.cjs.mjs +2 -0
  30. package/next.esm.js +353 -0
  31. package/package.json +22 -305
  32. package/actions/helpers-git.js +0 -45
  33. package/actions/helpers-i18n.js +0 -27
  34. package/actions/i18n.js +0 -17
  35. package/adapter-js/code/config.cjs.js +0 -14
  36. package/adapter-js/code/config.js +0 -14
  37. package/adapter-js/code/defaultLocale.js +0 -7
  38. package/adapter-js/code/deriveLocalisedPathnames.js +0 -78
  39. package/adapter-js/code/index.js +0 -123
  40. package/adapter-js/code/isLocale.js +0 -9
  41. package/adapter-js/code/locales.js +0 -8
  42. package/adapter-js/code/pathnameToRouteId.js +0 -20
  43. package/adapter-js/code/routes.js +0 -11
  44. package/adapter-js/code/routesSlim.js +0 -11
  45. package/adapter-js/code/routesSpa.js +0 -11
  46. package/adapter-js/code/tFns.js +0 -34
  47. package/adapter-js/code/tInterpolateParams.js +0 -18
  48. package/adapter-js/code/tPluralise.js +0 -10
  49. package/adapter-js/code/to.js +0 -32
  50. package/adapter-js/code/toFns.js +0 -25
  51. package/adapter-js/code/toFormat.js +0 -56
  52. package/adapter-js/code/toSpa.js +0 -42
  53. package/adapter-js/code/types.js +0 -327
  54. package/adapter-next/code/index.js +0 -54
  55. package/adapter-next/code/next-redirects.js +0 -5
  56. package/adapter-next/code/next-rewrites.js +0 -5
  57. package/adapter-next/code/useCurrentLocalisedPathnames.js +0 -20
  58. package/adapter-next/code/useLocale.js +0 -8
  59. package/adapter-next/code/useRouteId.js +0 -10
  60. package/adapter-next/code/useTo.js +0 -26
  61. package/adapter-next/code/useToSpa.js +0 -34
  62. package/adapter-next/plugin-async.js +0 -11
  63. package/adapter-next/plugin-legacy.js +0 -187
  64. package/adapter-next/plugin-shared.js +0 -39
  65. package/adapter-next/plugin.js +0 -13
  66. package/adapter-next/redirects.js +0 -51
  67. package/adapter-next/rewrites.js +0 -50
  68. package/adapter-next/transformPathname.js +0 -3
  69. package/adapter-next/webpackPluginI18n.js +0 -11
  70. package/adapter-next-translate/code/DynamicNamespaces.js +0 -9
  71. package/adapter-next-translate/code/T.js +0 -31
  72. package/adapter-next-translate/code/TransText.js +0 -9
  73. package/adapter-next-translate/code/getT.js +0 -15
  74. package/adapter-next-translate/code/index.js +0 -49
  75. package/adapter-next-translate/code/nextTranslateI18n.js +0 -19
  76. package/adapter-next-translate/code/useT.js +0 -43
  77. package/cjs/adapter-js/code/config.cjs.d.ts +0 -3
  78. package/cjs/adapter-js/code/config.d.ts +0 -3
  79. package/cjs/adapter-js/code/defaultLocale.d.ts +0 -3
  80. package/cjs/adapter-js/code/deriveLocalisedPathnames.d.ts +0 -2
  81. package/cjs/adapter-js/code/index.d.ts +0 -3
  82. package/cjs/adapter-js/code/isLocale.d.ts +0 -2
  83. package/cjs/adapter-js/code/locales.d.ts +0 -3
  84. package/cjs/adapter-js/code/pathnameToRouteId.d.ts +0 -3
  85. package/cjs/adapter-js/code/routes.d.ts +0 -3
  86. package/cjs/adapter-js/code/routesSlim.d.ts +0 -3
  87. package/cjs/adapter-js/code/routesSpa.d.ts +0 -3
  88. package/cjs/adapter-js/code/tFns.d.ts +0 -3
  89. package/cjs/adapter-js/code/tInterpolateParams.d.ts +0 -3
  90. package/cjs/adapter-js/code/tPluralise.d.ts +0 -2
  91. package/cjs/adapter-js/code/to.d.ts +0 -3
  92. package/cjs/adapter-js/code/toFns.d.ts +0 -3
  93. package/cjs/adapter-js/code/toFormat.d.ts +0 -3
  94. package/cjs/adapter-js/code/toSpa.d.ts +0 -3
  95. package/cjs/adapter-js/code/types.d.ts +0 -3
  96. package/cjs/adapter-next/code/index.d.ts +0 -3
  97. package/cjs/adapter-next/code/next-redirects.d.ts +0 -3
  98. package/cjs/adapter-next/code/next-rewrites.d.ts +0 -3
  99. package/cjs/adapter-next/code/useCurrentLocalisedPathnames.d.ts +0 -2
  100. package/cjs/adapter-next/code/useLocale.d.ts +0 -3
  101. package/cjs/adapter-next/code/useRouteId.d.ts +0 -2
  102. package/cjs/adapter-next/code/useTo.d.ts +0 -2
  103. package/cjs/adapter-next/code/useToSpa.d.ts +0 -2
  104. package/cjs/adapter-next/plugin-async.d.ts +0 -9
  105. package/cjs/adapter-next/plugin-legacy.d.ts +0 -22
  106. package/cjs/adapter-next/plugin-shared.d.ts +0 -13
  107. package/cjs/adapter-next/plugin.d.ts +0 -7
  108. package/cjs/adapter-next/redirects.d.ts +0 -4
  109. package/cjs/adapter-next/rewrites.d.ts +0 -4
  110. package/cjs/adapter-next/transformPathname.d.ts +0 -1
  111. package/cjs/adapter-next/webpackPluginI18n.d.ts +0 -7
  112. package/cjs/adapter-next-translate/code/DynamicNamespaces.d.ts +0 -2
  113. package/cjs/adapter-next-translate/code/T.d.ts +0 -2
  114. package/cjs/adapter-next-translate/code/TransText.d.ts +0 -2
  115. package/cjs/adapter-next-translate/code/getT.d.ts +0 -2
  116. package/cjs/adapter-next-translate/code/index.d.ts +0 -3
  117. package/cjs/adapter-next-translate/code/nextTranslateI18n.d.ts +0 -3
  118. package/cjs/adapter-next-translate/code/useT.d.ts +0 -2
  119. package/cjs/client/formatRoutePathname.d.ts +0 -4
  120. package/cjs/client/index.d.ts +0 -3
  121. package/cjs/client/interpolateTo.d.ts +0 -4
  122. package/cjs/client/routeHasDynamicPortion.d.ts +0 -2
  123. package/cjs/compiler/api.d.ts +0 -30
  124. package/cjs/compiler/code/data-routes.d.ts +0 -19
  125. package/cjs/compiler/code/data-translations.d.ts +0 -13
  126. package/cjs/compiler/code/data.d.ts +0 -33
  127. package/cjs/compiler/code/generate.d.ts +0 -14
  128. package/cjs/compiler/code/index.d.ts +0 -3
  129. package/cjs/compiler/code/tsCompile.d.ts +0 -2
  130. package/cjs/compiler/code/write.d.ts +0 -11
  131. package/cjs/compiler/config.d.ts +0 -12
  132. package/cjs/compiler/helpers.d.ts +0 -2
  133. package/cjs/compiler/input/data-local.d.ts +0 -4
  134. package/cjs/compiler/input/data-remote.d.ts +0 -7
  135. package/cjs/compiler/input/data.d.ts +0 -4
  136. package/cjs/compiler/input/index.d.ts +0 -4
  137. package/cjs/compiler/input/types.d.ts +0 -14
  138. package/cjs/compiler/input/write.d.ts +0 -8
  139. package/cjs/compiler/pluralisation.d.ts +0 -37
  140. package/cjs/compiler/summary/data.d.ts +0 -6
  141. package/cjs/compiler/summary/generate.d.ts +0 -4
  142. package/cjs/compiler/summary/index.d.ts +0 -3
  143. package/cjs/compiler/summary/write.d.ts +0 -10
  144. package/cjs/compiler/types.d.ts +0 -82
  145. package/cjs/compiler-sync.d.ts +0 -4
  146. package/cjs/compiler-worker.d.ts +0 -1
  147. package/cjs/compiler.d.ts +0 -2
  148. package/cjs/i18n/actions/helpers-git.js +0 -45
  149. package/cjs/i18n/actions/helpers-i18n.js +0 -27
  150. package/cjs/i18n/actions/i18n.js +0 -17
  151. package/cjs/i18n/adapter-js/code/config.cjs.js +0 -14
  152. package/cjs/i18n/adapter-js/code/config.js +0 -14
  153. package/cjs/i18n/adapter-js/code/defaultLocale.js +0 -7
  154. package/cjs/i18n/adapter-js/code/deriveLocalisedPathnames.js +0 -78
  155. package/cjs/i18n/adapter-js/code/index.js +0 -123
  156. package/cjs/i18n/adapter-js/code/isLocale.js +0 -9
  157. package/cjs/i18n/adapter-js/code/locales.js +0 -8
  158. package/cjs/i18n/adapter-js/code/pathnameToRouteId.js +0 -20
  159. package/cjs/i18n/adapter-js/code/routes.js +0 -11
  160. package/cjs/i18n/adapter-js/code/routesSlim.js +0 -11
  161. package/cjs/i18n/adapter-js/code/routesSpa.js +0 -11
  162. package/cjs/i18n/adapter-js/code/tFns.js +0 -34
  163. package/cjs/i18n/adapter-js/code/tInterpolateParams.js +0 -18
  164. package/cjs/i18n/adapter-js/code/tPluralise.js +0 -10
  165. package/cjs/i18n/adapter-js/code/to.js +0 -32
  166. package/cjs/i18n/adapter-js/code/toFns.js +0 -25
  167. package/cjs/i18n/adapter-js/code/toFormat.js +0 -56
  168. package/cjs/i18n/adapter-js/code/toSpa.js +0 -42
  169. package/cjs/i18n/adapter-js/code/types.js +0 -327
  170. package/cjs/i18n/adapter-next/code/index.js +0 -54
  171. package/cjs/i18n/adapter-next/code/next-redirects.js +0 -5
  172. package/cjs/i18n/adapter-next/code/next-rewrites.js +0 -5
  173. package/cjs/i18n/adapter-next/code/useCurrentLocalisedPathnames.js +0 -20
  174. package/cjs/i18n/adapter-next/code/useLocale.js +0 -8
  175. package/cjs/i18n/adapter-next/code/useRouteId.js +0 -10
  176. package/cjs/i18n/adapter-next/code/useTo.js +0 -26
  177. package/cjs/i18n/adapter-next/code/useToSpa.js +0 -34
  178. package/cjs/i18n/adapter-next/plugin-async.js +0 -11
  179. package/cjs/i18n/adapter-next/plugin-legacy.js +0 -187
  180. package/cjs/i18n/adapter-next/plugin-shared.js +0 -39
  181. package/cjs/i18n/adapter-next/plugin.js +0 -13
  182. package/cjs/i18n/adapter-next/redirects.js +0 -51
  183. package/cjs/i18n/adapter-next/rewrites.js +0 -50
  184. package/cjs/i18n/adapter-next/transformPathname.js +0 -3
  185. package/cjs/i18n/adapter-next/webpackPluginI18n.js +0 -11
  186. package/cjs/i18n/adapter-next-translate/code/DynamicNamespaces.js +0 -9
  187. package/cjs/i18n/adapter-next-translate/code/T.js +0 -31
  188. package/cjs/i18n/adapter-next-translate/code/TransText.js +0 -9
  189. package/cjs/i18n/adapter-next-translate/code/getT.js +0 -15
  190. package/cjs/i18n/adapter-next-translate/code/index.js +0 -49
  191. package/cjs/i18n/adapter-next-translate/code/nextTranslateI18n.js +0 -19
  192. package/cjs/i18n/adapter-next-translate/code/useT.js +0 -43
  193. package/cjs/i18n/client/formatRoutePathname.js +0 -5
  194. package/cjs/i18n/client/index.js +0 -3
  195. package/cjs/i18n/client/interpolateTo.js +0 -14
  196. package/cjs/i18n/client/routeHasDynamicPortion.js +0 -2
  197. package/cjs/i18n/compiler/api.js +0 -18
  198. package/cjs/i18n/compiler/code/data-routes.js +0 -99
  199. package/cjs/i18n/compiler/code/data-translations.js +0 -86
  200. package/cjs/i18n/compiler/code/data.js +0 -20
  201. package/cjs/i18n/compiler/code/generate.js +0 -46
  202. package/cjs/i18n/compiler/code/index.js +0 -2
  203. package/cjs/i18n/compiler/code/tsCompile.js +0 -27
  204. package/cjs/i18n/compiler/code/write.js +0 -39
  205. package/cjs/i18n/compiler/config.js +0 -13
  206. package/cjs/i18n/compiler/helpers.js +0 -14
  207. package/cjs/i18n/compiler/input/data-local.js +0 -60
  208. package/cjs/i18n/compiler/input/data-remote.js +0 -41
  209. package/cjs/i18n/compiler/input/data.js +0 -11
  210. package/cjs/i18n/compiler/input/index.js +0 -3
  211. package/cjs/i18n/compiler/input/types.js +0 -1
  212. package/cjs/i18n/compiler/input/write.js +0 -15
  213. package/cjs/i18n/compiler/pluralisation.js +0 -59
  214. package/cjs/i18n/compiler/summary/data.js +0 -28
  215. package/cjs/i18n/compiler/summary/generate.js +0 -27
  216. package/cjs/i18n/compiler/summary/index.js +0 -2
  217. package/cjs/i18n/compiler/summary/write.js +0 -18
  218. package/cjs/i18n/compiler/types.js +0 -1
  219. package/cjs/i18n/compiler-sync.js +0 -2
  220. package/cjs/i18n/compiler-worker.js +0 -3
  221. package/cjs/i18n/compiler.js +0 -1
  222. package/cjs/i18n/index.js +0 -2
  223. package/cjs/i18n/next.js +0 -3
  224. package/cjs/i18n/types.js +0 -1
  225. package/cjs/index.d.ts +0 -2
  226. package/cjs/next.d.ts +0 -3
  227. package/cjs/package.json +0 -34
  228. package/cjs/types.d.ts +0 -48
  229. package/client/formatRoutePathname.js +0 -5
  230. package/client/index.js +0 -3
  231. package/client/interpolateTo.js +0 -14
  232. package/client/routeHasDynamicPortion.js +0 -2
  233. package/compiler/api.js +0 -18
  234. package/compiler/code/data-routes.js +0 -99
  235. package/compiler/code/data-translations.js +0 -86
  236. package/compiler/code/data.js +0 -20
  237. package/compiler/code/generate.js +0 -46
  238. package/compiler/code/index.js +0 -2
  239. package/compiler/code/tsCompile.js +0 -27
  240. package/compiler/code/write.js +0 -39
  241. package/compiler/config.js +0 -13
  242. package/compiler/helpers.js +0 -14
  243. package/compiler/input/data-local.js +0 -60
  244. package/compiler/input/data-remote.js +0 -41
  245. package/compiler/input/data.js +0 -11
  246. package/compiler/input/index.js +0 -3
  247. package/compiler/input/types.js +0 -1
  248. package/compiler/input/write.js +0 -15
  249. package/compiler/pluralisation.js +0 -59
  250. package/compiler/summary/data.js +0 -28
  251. package/compiler/summary/generate.js +0 -27
  252. package/compiler/summary/index.js +0 -2
  253. package/compiler/summary/write.js +0 -18
  254. package/compiler/types.js +0 -1
  255. package/compiler-sync.js +0 -2
  256. package/compiler-worker.js +0 -3
  257. package/compiler.js +0 -1
  258. package/index.js +0 -2
  259. package/next.js +0 -3
  260. package/types.js +0 -1
package/api.esm.js ADDED
@@ -0,0 +1,2179 @@
1
+ import { escapeRegExp, isPrimitive, isString, isNumber, isBoolean, isArray, areEqual, changeCaseSnake, splitReverse, isNumericLiteral, forin, split, objectPick, isObject, arrayUniqueByProperties, isPromise, objectSortByKeysMatching, objectFlat, objectSort, objectMergeWithDefaults, arraySum } from '@koine/utils';
2
+ import { rmSync } from 'node:fs';
3
+ import { join, dirname, basename, extname, sep } from 'node:path';
4
+ import { fsWrite } from '@koine/node';
5
+ import { f as formatRoutePathname } from './formatRoutePathname.esm.js';
6
+ import * as ts from 'typescript';
7
+ import { minimatch } from 'minimatch';
8
+ import { isAbsoluteUrl } from 'next/dist/shared/lib/utils';
9
+ import { readFile } from 'node:fs/promises';
10
+ import { glob } from 'glob';
11
+ import 'node:child_process';
12
+ import { request } from 'node:https';
13
+
14
+ var config = ({ config }) => `
15
+ import { locales } from "./locales";
16
+ import { defaultLocale } from "./defaultLocale";
17
+
18
+ /**
19
+ */
20
+ export const config = {
21
+ locales,
22
+ defaultLocale,
23
+ hideDefaultLocaleInUrl: ${config.hideDefaultLocaleInUrl},
24
+ }
25
+
26
+ export default config;
27
+ `;
28
+
29
+ var configCjs = ({ config }) => `
30
+ const { locales } = require("./locales");
31
+ const { defaultLocale } = require("./defaultLocale");
32
+
33
+ const config = {
34
+ locales,
35
+ defaultLocale,
36
+ hideDefaultLocaleInUrl: ${config.hideDefaultLocaleInUrl},
37
+ };
38
+
39
+ exports.config = config;
40
+
41
+ module.exports = config;
42
+ `;
43
+
44
+ var defaultLocale = ({ config }) => `
45
+ import type { I18n } from "./types";
46
+
47
+ export const defaultLocale: I18n.Locale = "${config.defaultLocale}";
48
+
49
+ export default defaultLocale;
50
+ `;
51
+
52
+ var deriveLocalisedPathnames = () => `
53
+ import { locales } from "./locales";
54
+ import { to } from "./to";
55
+ import type { I18n } from "./types";
56
+
57
+ function getPathnameDynamicPortionName(pathname: string) {
58
+ const res = pathname.match(/\\[(.+)\\]/);
59
+ return res ? res[1] : false;
60
+ }
61
+
62
+ function isStaticPathname(pathname: string) {
63
+ return !/\\[/.test(pathname);
64
+ }
65
+
66
+ // e.g. ['my', 'path', 'id', 'view'] or ['nl' 'my', 'path', 'id', 'view']
67
+ // where the first might be the locale (depending on hideDefaultLocaleInUrl)
68
+ function getPathnameParts(pathname: string) {
69
+ return pathname
70
+ .split("/")
71
+ .slice(1)
72
+ .filter((p, i) => (i === 0 ? !locales.includes(p as I18n.Locale) : true));
73
+ }
74
+
75
+ function localisePathname(
76
+ locale: I18n.Locale,
77
+ // e.g. "my.path.[id].view"
78
+ routeId: I18n.RouteIdDynamic | I18n.RouteIdStatic,
79
+ locationLike: LocationLike
80
+ ) {
81
+ const toPathname = to(routeId as I18n.RouteIdStatic, locale);
82
+
83
+ if (isStaticPathname(toPathname)) {
84
+ return toPathname;
85
+ }
86
+ // e.g. ['my', 'path', '[id]', 'view']
87
+ const toPathnameParts = getPathnameParts(toPathname);
88
+ // e.g. "my.path.[id].view"
89
+ const routeIdDynamic = routeId as I18n.RouteIdDynamic;
90
+ // e.g. /my/path/123/view
91
+ const currentPathname = locationLike.pathname;
92
+ // e.g. ['my', 'path', '123', 'view']
93
+ const currentPathnameParts = getPathnameParts(currentPathname);
94
+ // e.g. { id: "123" }
95
+ const params: Record<string, string> = {};
96
+
97
+ for (let i = 0; i < toPathnameParts.length; i++) {
98
+ // e.g. "my" or "[id]"
99
+ const part = toPathnameParts[i];
100
+ // e.g. "id"
101
+ const name = getPathnameDynamicPortionName(part);
102
+ if (name) {
103
+ // e.g. "123"
104
+ const value = currentPathnameParts[i];
105
+ if (value) {
106
+ params[name] = value;
107
+ }
108
+ }
109
+ }
110
+
111
+ return (
112
+ to(routeIdDynamic, params as never, locale) +
113
+ location?.search || "" +
114
+ location?.hash || ""
115
+ );
116
+ }
117
+
118
+ type LocationLike = { pathname: string; search?: string; hash?: string; };
119
+
120
+ export type LocalisedPathnames = Record<I18n.Locale, string>;
121
+
122
+ export const deriveLocalisedPathnames = (routeId: I18n.RouteId, locationLike: LocationLike) =>
123
+ locales.reduce((pathnames, locale) => {
124
+ pathnames[locale] = localisePathname(locale, routeId, locationLike);
125
+ return pathnames;
126
+ }, {} as LocalisedPathnames);
127
+
128
+ export default deriveLocalisedPathnames;
129
+ `;
130
+
131
+ var isLocale = () => `
132
+ import { locales } from "./locales";
133
+ import type { I18n } from "./types";
134
+
135
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
136
+ export const isLocale = (payload: any): payload is I18n.Locale => locales.includes(payload);
137
+
138
+ export default isLocale;
139
+ `;
140
+
141
+ var locales = ({ config }) => {
142
+ const value = `[${config.locales.map((l) => `"${l}"`).join(", ")}]`;
143
+ return `
144
+ export const locales = ${value} as const;
145
+
146
+ export default locales;
147
+ `;
148
+ };
149
+
150
+ var pathnameToRouteId = ({ options }) => {
151
+ const { idDelimiter, optionalCatchAll, catchAll } = options.routes.tokens;
152
+ return `
153
+ /**
154
+ * Convert a URL like pathname to a "named route"
155
+ * E.g. it transforms:
156
+ * - \`/dashboard/user/[id]\` into \`dashboard.user.[id]\`
157
+ */
158
+ export const pathnameToRouteId = (pathname: string) =>
159
+ pathname
160
+ .replace(/^\\//g, "")
161
+ .replace(/\\//g, "${idDelimiter}")
162
+ .replace(/${escapeRegExp(idDelimiter)}${escapeRegExp(optionalCatchAll.start)}.+$/, "")
163
+ .replace(/${escapeRegExp(idDelimiter)}${escapeRegExp(catchAll.start)}.+$/, "")
164
+ .replace(/\\/index$/, "");
165
+
166
+ export default pathnameToRouteId;
167
+ `;
168
+ };
169
+
170
+ var routes = ({ routes }) => {
171
+ const value = JSON.stringify(Object.fromEntries(Object.entries(routes.byId)
172
+ .map(([routeId, { pathnames }]) => [routeId, pathnames])
173
+ .sort()), null, 2);
174
+ return `
175
+ export const routes = ${value} as const;
176
+
177
+ export default routes;
178
+ `;
179
+ };
180
+
181
+ var routesSlim = ({ routes }) => {
182
+ const value = JSON.stringify(Object.fromEntries(Object.entries(routes.byId)
183
+ .map(([routeId, { pathnamesSlim, pathnames }]) => [
184
+ routeId,
185
+ pathnamesSlim || pathnames,
186
+ ])
187
+ .sort()), null, 2);
188
+ return `
189
+ export const routesSlim = ${value} as const;
190
+
191
+ export default routesSlim;
192
+ `;
193
+ };
194
+
195
+ var routesSpa = ({ routes }) => {
196
+ const value = JSON.stringify(Object.fromEntries(Object.entries(routes.byId)
197
+ .filter(([, { pathnamesSpa }]) => !!pathnamesSpa)
198
+ .map(([routeId, { pathnamesSpa }]) => [routeId, pathnamesSpa])
199
+ .sort()), null, 2);
200
+ return `
201
+ export const routesSpa = ${value} as const;
202
+
203
+ export default routesSpa;
204
+ `;
205
+ };
206
+
207
+ let dataParamsToTsInterfaceBody = (params) => Object.keys(params)
208
+ .reduce((pairs, paramName) => {
209
+ const value = params[paramName];
210
+ let type = "";
211
+ switch (value) {
212
+ case "number":
213
+ type = "number";
214
+ break;
215
+ case "string":
216
+ type = "string";
217
+ break;
218
+ default:
219
+ type = "string | number";
220
+ break;
221
+ }
222
+ pairs.push(`${paramName}: ${type};`);
223
+ return pairs;
224
+ }, [])
225
+ .join(" ");
226
+
227
+ const getTranslationValueOutput = (value) => {
228
+ if (isString(value) || isNumber(value)) {
229
+ return `"${value}"`;
230
+ }
231
+ else if (isBoolean(value)) {
232
+ return `${value}`;
233
+ }
234
+ else if (isArray(value)) {
235
+ return JSON.stringify(value);
236
+ }
237
+ return `(${JSON.stringify(value)})`;
238
+ };
239
+ const areEqualTranslationsValues = (a, b) => areEqual(a, b);
240
+ const getFunctionBodyWithLocales$1 = (config, perLocaleValues) => {
241
+ const { defaultLocale } = config;
242
+ let output = "";
243
+ for (const locale in perLocaleValues) {
244
+ const value = perLocaleValues[locale];
245
+ if (locale !== defaultLocale &&
246
+ !areEqualTranslationsValues(value, perLocaleValues[defaultLocale])) {
247
+ output += `locale === "${locale}" ? ${getTranslationValueOutput(value)} : `;
248
+ }
249
+ }
250
+ output += getTranslationValueOutput(perLocaleValues[defaultLocale]);
251
+ return output;
252
+ };
253
+ var tFns = ({ config, options, translations }) => {
254
+ let output = `
255
+ /* eslint-disable @typescript-eslint/no-unused-vars */
256
+ /* eslint-disable prefer-const */
257
+ import type { I18n } from "./types";
258
+ import { tInterpolateParams } from "./tInterpolateParams";
259
+ import { tPluralise } from "./tPluralise";
260
+
261
+ `;
262
+ for (const translationId in translations) {
263
+ let { values, params, plural } = translations[translationId];
264
+ const name = `${options.translations.fnsPrefix}${translationId}`;
265
+ if (plural) {
266
+ if (params) {
267
+ params["count"] = "number";
268
+ }
269
+ else {
270
+ params = { count: "number" };
271
+ }
272
+ }
273
+ const argParam = params
274
+ ? `params: { ${dataParamsToTsInterfaceBody(params)} }`
275
+ : "";
276
+ const argLocale = "locale?: I18n.Locale";
277
+ const args = [argParam, argLocale].filter(Boolean).join(", ");
278
+ output += `export let ${name} = (${args}) => `;
279
+ let outputFnReturn = "";
280
+ if (isPrimitive(values)) {
281
+ outputFnReturn += getTranslationValueOutput(values);
282
+ }
283
+ else {
284
+ outputFnReturn += getFunctionBodyWithLocales$1(config, values);
285
+ }
286
+ if (plural) {
287
+ outputFnReturn = `tPluralise(${outputFnReturn}, params.count)`;
288
+ }
289
+ if (params) {
290
+ outputFnReturn = `tInterpolateParams(${outputFnReturn}, params);`;
291
+ }
292
+ else {
293
+ outputFnReturn = `${outputFnReturn};`;
294
+ }
295
+ output += outputFnReturn;
296
+ output += `\n`;
297
+ }
298
+ return output;
299
+ };
300
+
301
+ const escapeEachChar = (input) => input
302
+ .split("")
303
+ .map((v) => `\\${v}`)
304
+ .join("");
305
+ var tInterpolateParams = ({ options }) => {
306
+ const { start, end } = options.translations.dynamicDelimiters;
307
+ return `
308
+ /* eslint-disable prefer-const */
309
+ export let tInterpolateParams = (
310
+ value: string,
311
+ params?: object,
312
+ ) =>
313
+ params ? value.replace(
314
+ /${escapeEachChar(start)}(.*?)${escapeEachChar(end)}/g,
315
+ (_, key) =>
316
+ params[key.trim() as keyof typeof params] + "",
317
+ ) : value;
318
+
319
+ export default tInterpolateParams;
320
+ `;
321
+ };
322
+
323
+ var tPluralise = () => {
324
+ return `
325
+ /* eslint-disable prefer-const */
326
+ let pluralRules = new Intl.PluralRules();
327
+
328
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
329
+ export let tPluralise = (values: any, count: number) =>
330
+ values[count] || values[pluralRules.select(count)] || (count === 0 ? values.zero : values["other"]);
331
+
332
+ export default tPluralise;
333
+ `;
334
+ };
335
+
336
+ var to = ({ config }) => `
337
+ import { isLocale } from "./isLocale";
338
+ import { toFormat } from "./toFormat";
339
+ import { routesSlim } from "./routesSlim";
340
+ import type { I18n } from "./types";
341
+
342
+ /**
343
+ * *To* route utility
344
+ *
345
+ * @returns A localised relative URL based on your i18nCompiler configuration
346
+ */
347
+ export function to<Id extends I18n.RouteId>(
348
+ id: Id,
349
+ ...args: Id extends I18n.RouteIdDynamic
350
+ ?
351
+ | [I18n.RouteParams[Id]]
352
+ | [I18n.RouteParams[Id], I18n.Locale]
353
+ : [] | [I18n.Locale]
354
+ ) {
355
+ const locale = (isLocale(args[0]) ? args[0] : args[1]) || "${config.defaultLocale}";
356
+
357
+ return toFormat(
358
+ locale,
359
+ (routesSlim[id] as Record<string, string>)[locale] ??
360
+ (routesSlim[id] as Record<string, string>)["${config.defaultLocale}"] ??
361
+ routesSlim[id],
362
+ isLocale(args[0]) ? undefined : args[0]
363
+ ) as I18n.RoutePathnames[Id];
364
+ }
365
+
366
+ export default to;
367
+ `;
368
+
369
+ const getFunctionBodyWithLocales = (config, perLocaleValues) => {
370
+ const { defaultLocale } = config;
371
+ let output = "";
372
+ for (const locale in perLocaleValues) {
373
+ const value = perLocaleValues[locale];
374
+ if (locale !== defaultLocale && value !== perLocaleValues[defaultLocale]) {
375
+ output += `locale === "${locale}" ? "${value}" : `;
376
+ }
377
+ }
378
+ output += '"' + perLocaleValues[defaultLocale] + '"';
379
+ return output;
380
+ };
381
+ var toFns = ({ config, routes, options }) => {
382
+ const hasOneLocale = config.locales.length === 1;
383
+ let output = `
384
+ /* eslint-disable prefer-const */
385
+ import { toFormat } from "./toFormat";
386
+ import type { I18n } from "./types";
387
+
388
+ `;
389
+ for (const routeId in routes.byId) {
390
+ const { pathnames, params } = routes.byId[routeId];
391
+ const name = `${options.routes.fnsPrefix}${changeCaseSnake(routeId)}`;
392
+ const paramsType = `I18n.RouteParams["${routeId}"]`;
393
+ const argParam = params ? `params: ${paramsType}` : "";
394
+ const argLocale = hasOneLocale ? "" : "locale?: I18n.Locale";
395
+ const args = [argParam, argLocale].filter(Boolean).join(", ");
396
+ const formatArgLocale = hasOneLocale ? `""` : "locale";
397
+ const formatArgParams = params ? ", params" : "";
398
+ output += `export let ${name} = (${args}) => `;
399
+ if (isString(pathnames)) {
400
+ output += `toFormat(${formatArgLocale}, "${pathnames}"${formatArgParams});`;
401
+ }
402
+ else {
403
+ output += `toFormat(${formatArgLocale}, ${getFunctionBodyWithLocales(config, pathnames)}${formatArgParams});`;
404
+ }
405
+ output += `\n`;
406
+ }
407
+ return output;
408
+ };
409
+
410
+ var toFormat = ({ config }) => `
411
+ export function toFormat(
412
+ locale: string | undefined,
413
+ pathname: string,
414
+ params?: object,
415
+ ) {
416
+ locale = locale || "${config.defaultLocale}";
417
+ if (process.env["NODE_ENV"] === "development") {
418
+ if (params) {
419
+ pathname.replace(/\\[(.*?)\\]/g, (_, dynamicKey) => {
420
+ const key = dynamicKey as Extract<keyof typeof params, string>;
421
+
422
+ if (!(key in params)) {
423
+ console.warn(
424
+ "[@koine/i18n]::interpolateTo, using '" +
425
+ pathname +
426
+ "' without param '" +
427
+ key +
428
+ "'",
429
+ { params }
430
+ );
431
+ }
432
+
433
+ if (!["string", "number"].includes(typeof params[key])) {
434
+ console.warn(
435
+ "[@koine/i18n]::toFormat, using '" +
436
+ pathname +
437
+ "' with unserializable param '" +
438
+ key +
439
+ "' (type '" +
440
+ Object.prototype.toString.call((params[key])).slice(8, -1) +
441
+ "')",
442
+ );
443
+ }
444
+ return "";
445
+ });
446
+ }
447
+ }
448
+
449
+ if (params) {
450
+ pathname = pathname.replace(
451
+ /\\[(.*?)\\]/g,
452
+ (_, key) =>
453
+ params[key as keyof typeof params] + "",
454
+ )
455
+ }
456
+ ${config.hideDefaultLocaleInUrl
457
+ ? `
458
+ if (locale !== "${config.defaultLocale}") {
459
+ return "/" + locale + pathname;
460
+ }
461
+ `
462
+ : ``}
463
+ return pathname;
464
+ }
465
+
466
+ export default toFormat;
467
+ `;
468
+
469
+ var toSpa = ({ config, options }) => {
470
+ const { idDelimiter } = options.routes.tokens;
471
+ return `
472
+ import { isLocale } from "./isLocale";
473
+ import { routesSpa } from "./routesSpa";
474
+ import { toFormat } from "./toFormat";
475
+ import type { I18n } from "./types";
476
+
477
+ /**
478
+ * *To spa* route utility
479
+ *
480
+ * @returns A localised relative URL based on your i18nCompiler configuration
481
+ */
482
+ export function toSpa<
483
+ Root extends keyof I18n.RouteSpa,
484
+ Path extends Extract<keyof I18n.RouteSpa[Root], string>,
485
+ >(
486
+ rootId: Root,
487
+ pathId: Path,
488
+ ...args: I18n.RouteJoinedId<Root, Path> extends I18n.RouteIdDynamic
489
+ ?
490
+ | [I18n.RouteParams[I18n.RouteJoinedId<Root, Path>]]
491
+ | [I18n.RouteParams[I18n.RouteJoinedId<Root, Path>], I18n.Locale]
492
+ : [] | [I18n.Locale]
493
+ ) {
494
+ const locale = (isLocale(args[0]) ? args[0] : args[1]) || "${config.defaultLocale}";
495
+ const fullId = \`\${rootId}${idDelimiter}\${pathId}\` as I18n.RouteJoinedId<Root, Path>;
496
+ return toFormat(
497
+ // FIXME: actually the locale will be prepended if hideDefaultLocaleInUrl will be false
498
+ "", // do not pass the locale so that won't be prepended
499
+ (routesSpa[fullId] as Record<string, string>)[locale],
500
+ args.length === 2
501
+ ? args[0]
502
+ : args[0] && !isLocale(args[0])
503
+ ? args[0]
504
+ : void 0,
505
+ ) as I18n.RouteSpa[Root][Path];
506
+ }
507
+
508
+ export default toSpa;
509
+ `;
510
+ };
511
+
512
+ const pluralSuffixes = ["zero", "one", "two", "few", "many", "other"];
513
+ const requiredPluralSuffix = "other";
514
+ let isPluralSuffix = (suffix) => pluralSuffixes.includes(suffix) ||
515
+ isNumericLiteral(suffix);
516
+ let removePluralSuffix = (key) => {
517
+ const [suffix] = splitReverse(key, "_");
518
+ return suffix ? key.replace(`_${suffix}`, "") : key;
519
+ };
520
+ let getPluralSuffix = (key) => splitReverse(key, "_")[0];
521
+ let isPluralKey = (key) => {
522
+ const [suffix] = splitReverse(key, "_");
523
+ return isPluralSuffix(suffix);
524
+ };
525
+ const groupPluralsKeysByRoot = (pluralKeys) => {
526
+ const map = {};
527
+ pluralKeys.forEach((key) => {
528
+ const [root] = split(key, "_");
529
+ map[root] = map[root] || [];
530
+ map[root].push(key);
531
+ });
532
+ return map;
533
+ };
534
+ let transformKeysForPlurals = (keys) => {
535
+ if (keys.some(hasRequiredPluralSuffix) ||
536
+ keys.includes(requiredPluralSuffix)) {
537
+ const pluralKeys = keys.filter(isPluralKey);
538
+ if (pluralKeys.length) {
539
+ let transformedKeys = [...keys];
540
+ const groupedPlurals = groupPluralsKeysByRoot(pluralKeys);
541
+ forin(groupedPlurals, (pluralRoot, pluralKeys) => {
542
+ if (!keys.includes(pluralRoot)) {
543
+ transformedKeys.push(pluralRoot);
544
+ }
545
+ pluralKeys.forEach((pluralKey) => {
546
+ if (keys.includes(pluralKey)) {
547
+ transformedKeys = transformedKeys.filter((k) => k !== pluralKey);
548
+ }
549
+ });
550
+ });
551
+ return transformedKeys;
552
+ }
553
+ }
554
+ return keys;
555
+ };
556
+ let hasRequiredPluralSuffix = (key) => isPluralKey(key) && getPluralSuffix(key) === requiredPluralSuffix;
557
+ let hasPlurals = (value) => Object.keys(value).some(hasRequiredPluralSuffix) ||
558
+ Object.keys(value).includes(requiredPluralSuffix);
559
+ let hasOnlyPluralKeys = (value) => hasPlurals(value) ? pickNonPluralKeys(value).length === 0 : false;
560
+ let pickNonPluralKeys = (value) => Object.keys(value).filter((k) => !isPluralSuffix(k));
561
+ let pickNonPluralValue = (value) => (hasPlurals(value) ? objectPick(value, pickNonPluralKeys(value)) : value);
562
+
563
+ const buildTypeForObjectValue = (key, value) => {
564
+ if (!isArray(value) && isObject(value)) {
565
+ if (hasPlurals(value)) {
566
+ return hasOnlyPluralKeys(value)
567
+ ? `"${key}": string;`
568
+ : `"${key}": ${buildTypeForValue(pickNonPluralValue(value))}`;
569
+ }
570
+ }
571
+ return `"${key}": ${buildTypeForValue(value)}`;
572
+ };
573
+ const buildTypeForValue = (value) => {
574
+ let out = "";
575
+ let primitiveType = "";
576
+ if (isBoolean(value)) {
577
+ primitiveType = "boolean";
578
+ }
579
+ else if (isString(value)) {
580
+ primitiveType = "string";
581
+ }
582
+ if (primitiveType) {
583
+ out += primitiveType + ";";
584
+ }
585
+ else if (!value) {
586
+ out += "";
587
+ }
588
+ else if (isArray(value)) {
589
+ const firstValue = value[0];
590
+ out += `${buildTypeForValue(firstValue)}[];`;
591
+ }
592
+ else if (isObject(value)) {
593
+ out += "{";
594
+ const keys = transformKeysForPlurals(Object.keys(value));
595
+ for (let i = 0; i < keys.length; i++) {
596
+ const key = keys[i];
597
+ const single = value[key] || "";
598
+ out += buildTypeForObjectValue(key, single);
599
+ }
600
+ out += "};";
601
+ }
602
+ out = out.replace(/;\[\];/g, "[];");
603
+ out = out.replace(/;+/g, ";");
604
+ return out;
605
+ };
606
+ const buildTranslationsDictionary = (config, dataInput) => {
607
+ const { translationFiles } = dataInput;
608
+ const { defaultLocale } = config;
609
+ const defaultLocaleFiles = translationFiles.filter((f) => f.locale === defaultLocale);
610
+ const out = [];
611
+ for (let i = 0; i < defaultLocaleFiles.length; i++) {
612
+ const { path, data } = defaultLocaleFiles[i];
613
+ const namespace = path.replace(".json", "");
614
+ out.push(`"${namespace}": ${buildTypeForValue(data)}`);
615
+ }
616
+ return out.sort();
617
+ };
618
+ const buildRouteParams = (routes) => {
619
+ const out = [];
620
+ forin(routes.byId, (routeId, { params }) => {
621
+ if (params) {
622
+ out.push(`"${routeId}": { ${dataParamsToTsInterfaceBody(params)} };`);
623
+ }
624
+ });
625
+ return out;
626
+ };
627
+ const buildUnionType = (values) => values
628
+ .sort()
629
+ .map((routeId) => `"${routeId}"`)
630
+ .join(" | ");
631
+ const buildRoutesUnion = (routes, filterFn) => buildUnionType(Object.keys(routes.byId).filter((routeId) => filterFn(routeId, routes.byId[routeId])));
632
+ const groupRoutesSpa = (routes) => Object.keys(routes.byId).reduce((map, routeId) => {
633
+ const route = routes.byId[routeId];
634
+ if (route.inWildcard) {
635
+ for (let I = 0; I < routes.wildcardIds.length; I++) {
636
+ const spaRootRouteId = routes.wildcardIds[I];
637
+ if (routeId.startsWith(spaRootRouteId)) {
638
+ map[spaRootRouteId] = map[spaRootRouteId] || [];
639
+ map[spaRootRouteId].push(routeId);
640
+ }
641
+ }
642
+ }
643
+ return map;
644
+ }, {});
645
+ const buildRoutesSpa = (config, routes, options) => {
646
+ const map = groupRoutesSpa(routes);
647
+ const output = [];
648
+ for (const rootRouteId in map) {
649
+ const pathRoutesIds = map[rootRouteId].map((fullRouteId) => fullRouteId.split(rootRouteId)[1].slice(1));
650
+ const rootPathname = routes.byId[rootRouteId].pathnames[config.defaultLocale];
651
+ const pathnames = [];
652
+ for (let i = 0; i < pathRoutesIds.length; i++) {
653
+ const subRouteId = pathRoutesIds[i];
654
+ const fullRouteId = `${rootRouteId}${options.routes.tokens.idDelimiter}${subRouteId}`;
655
+ const fullPathname = routes.byId[fullRouteId].pathnames[config.defaultLocale];
656
+ const pathPathname = fullPathname.split(rootPathname)[1];
657
+ pathnames.push(`"${subRouteId}": "${pathPathname}";`);
658
+ }
659
+ output.push(`"${rootRouteId}": { ${pathnames.join(" ")} }`);
660
+ }
661
+ return output;
662
+ };
663
+ const buildRoutesPathnames = (config, routes) => {
664
+ const output = [];
665
+ for (const routeId in routes.byId) {
666
+ const route = routes.byId[routeId];
667
+ output.push(`"${route.id}": "${route.pathnames[config.defaultLocale]}";`);
668
+ }
669
+ return output;
670
+ };
671
+ var types = ({ config, input, routes, options, }) => {
672
+ const routeIdStatic = buildRoutesUnion(routes, (_, { params }) => !params);
673
+ const routeIdDynamic = buildRoutesUnion(routes, (_, { params }) => !!params);
674
+ const { idDelimiter } = options.routes.tokens;
675
+ return `
676
+ /* eslint-disable @typescript-eslint/no-namespace */
677
+ /* eslint-disable @typescript-eslint/no-explicit-any */
678
+ /* eslint-disable @typescript-eslint/ban-types */
679
+ import type { Split } from "@koine/utils";
680
+ import type { I18nUtils } from "@koine/i18n";
681
+
682
+ export namespace I18n {
683
+ /**
684
+ * Any of the available locale code
685
+ */
686
+ export type Locale = ${config.locales.map((l) => `"${l}"`).join(" | ")};
687
+
688
+ /**
689
+ * Utility to map values by all available locales
690
+ *
691
+ * @usecase I need to map zendesk URLs to my project's locales
692
+ */
693
+ export type LocalesMap<T = any> = Record<Locale, T>;
694
+
695
+ /**
696
+ * Any of the available route id
697
+ */
698
+ export type RouteId = RouteIdStatic | RouteIdDynamic;
699
+
700
+ /**
701
+ * The static routes available ids
702
+ */
703
+ export type RouteIdStatic = ${routeIdStatic};
704
+
705
+ /**
706
+ * The dynamic routes available ids
707
+ */
708
+ export type RouteIdDynamic = ${routeIdDynamic};
709
+
710
+ /**
711
+ * SPA routes ids map
712
+ */
713
+ /**
714
+ * Map every SPA path divided by their roots to their actual pathname value for the default locale
715
+ */
716
+ export type RouteSpa = {
717
+ ${buildRoutesSpa(config, routes, options).join("\n ")}
718
+ }
719
+
720
+ /**
721
+ * Map every route id to its actual pathanem value for the default locale
722
+ */
723
+ export type RoutePathnames = {
724
+ ${buildRoutesPathnames(config, routes).join("\n ")}
725
+ }
726
+
727
+ /**
728
+ * Route dynamic params dictionary for each dynamic route id
729
+ */
730
+ export type RouteParams = {
731
+ ${buildRouteParams(routes).join("\n ")}
732
+ }
733
+
734
+ /**
735
+ * Utility to join two route ids
736
+ */
737
+ export type RouteJoinedId<Root extends string, Tail extends string> = \`\${Root}${idDelimiter}\${Tail}\` extends RouteId ? \`\${Root}${idDelimiter}\${Tail}\` : never;
738
+
739
+ /**
740
+ * Extract all children routes that starts with the given string
741
+ *
742
+ * This is useful to get the subroutes of an application area, e.g. all subroutes
743
+ * of a dashboard, using it with: \` type DashboardRoutes = RoutesChildrenOf<"dashboard">;\`
744
+ */
745
+ export type RoutesChildrenOf<
746
+ TStarts extends string,
747
+ T extends string = RouteId,
748
+ > = T extends \`\${TStarts}.\${infer First}\` ? \`\${TStarts}.\${First}\` : never;
749
+
750
+ /**
751
+ * The types extracted from the translations JSON files, this is a little
752
+ * more sophisticated than the type result of \`typeof "./en/messages.json"\`
753
+ */
754
+ export type TranslationsDictionary = {
755
+ ${buildTranslationsDictionary(config, input).join("\n ")}
756
+ }
757
+
758
+ /**
759
+ * Any of the available translations namespaces
760
+ */
761
+ export type TranslateNamespace = Extract<keyof TranslationsDictionary, string>;
762
+
763
+ /**
764
+ * Translation **value** found at a specific _path_ in the given _namespace_
765
+ *
766
+ * \`TPath\` can be any of all possible paths:
767
+ * - \`myKey\` sub dictionaries within a namespace
768
+ * - \`myKey.nested\` whatever nested level of nesting within a namespace
769
+ */
770
+ export type TranslationAtPathFromNamespace<
771
+ TNamespace extends TranslateNamespace,
772
+ TPath extends TranslationsPaths<TranslationsDictionary[TNamespace]>,
773
+ > = TNamespace extends TranslateNamespace
774
+ ? TPath extends string // TranslationsPaths<TranslationsDictionary[TNamespace]>
775
+ ? I18nUtils.Get<TranslationsDictionary[TNamespace], TPath>
776
+ : TranslationsDictionary[TNamespace]
777
+ : never;
778
+
779
+ /**
780
+ * The generic type passed and to use with {@link TranslationAtPath} when you
781
+ * want to build a type extending that
782
+ */
783
+ export type TranslationAtPathGeneric =
784
+ | TranslateNamespace
785
+ | TranslationsAllPaths;
786
+
787
+ /**
788
+ * Translation **value** found at a _path_
789
+ *
790
+ * \`TPath\` can be any of all possible paths begininng with a namespace:
791
+ * - \`namespace\` only a namespace
792
+ * - \`namespace:myKey\` sub dictionaries within a namespace
793
+ * - \`namespace:myKey.nested\` whatever nested level of nesting
794
+ */
795
+ export type TranslationAtPath<TPath extends TranslationAtPathGeneric> =
796
+ TPath extends TranslateNamespace
797
+ ? TranslationsDictionary[TPath]
798
+ : TPath extends \`\${infer Namespace}:\${infer Path}\`
799
+ ? Namespace extends TranslateNamespace
800
+ ? I18nUtils.Get<TranslationsDictionary[Namespace], Path>
801
+ : never
802
+ : never;
803
+
804
+ /**
805
+ * All translations paths from the given _path_
806
+ *
807
+ * \`TPath\` can be any of all possible paths begininng with a namespace:
808
+ * - \`namespace\` only a namespace
809
+ * - \`namespace:myKey\` sub dictionaries within a namespace
810
+ * - \`namespace:myKey.nested\` whatever nested level of nesting
811
+ */
812
+ export type TranslationsPathsFrom<TPath extends TranslationAtPathGeneric> =
813
+ TPath extends TranslateNamespace
814
+ ? TranslationsPaths<TranslationsDictionary[TPath]>
815
+ : TPath extends \`\${infer Namespace}:\${infer Path}\`
816
+ ? Namespace extends TranslateNamespace
817
+ ? I18nUtils.Get<TranslationsDictionary[Namespace], Path> extends object
818
+ ? TranslationsPaths<I18nUtils.Get<TranslationsDictionary[Namespace], Path>>
819
+ : TranslationsPaths<TranslationsDictionary[Namespace]>
820
+ : never
821
+ : never;
822
+
823
+ /**
824
+ * All translations _paths_ of the given one, e.g. if \`TPath\` would be
825
+ * \`"dashboard.users.[id].edit"\` the generated type would be the union
826
+ * \`"dashboard.users.[id]" | "dashboard.users" | "dashboard"\`.
827
+ */
828
+ export type TranslationsPathsAncestors<
829
+ TPath extends string,
830
+ TSeparator extends string = ".",
831
+ > = I18nUtils.BuildRecursiveJoin<Split<TPath, TSeparator>, TSeparator>;
832
+
833
+ /**
834
+ * Recursive mapped type to extract all usable string paths from a translation
835
+ * definition object (usually from a JSON file).
836
+ */
837
+ export type TranslationsPaths<T, TAsObj extends boolean = true> = I18nUtils.Paths<T, TAsObj>;
838
+
839
+ /**
840
+ * Recursive mapped type of all usable string paths from the whole translations
841
+ * dictionary.
842
+ */
843
+ export type TranslationsAllPaths = I18nUtils.AllPaths<TranslationsDictionary>;
844
+
845
+ /**
846
+ * Unlike in \`next-translate\` we allow passing some predefined arguments as
847
+ * shortcuts for common use cases:
848
+ * - \`"obj"\` as a shortcut for \`{ returnObjects: true }\`
849
+ * - \`""\` as a shortcut for \`{ fallback: "" }\`
850
+ *
851
+ */
852
+ export type TranslationShortcut = "obj" | "";
853
+
854
+ /**
855
+ * Query object to populate the returned translated string interpolating data
856
+ * or a TranslationShortcut.
857
+ *
858
+ * NOTE: when using a shortcut passing TranslationOptions to \`t()\` is not supported
859
+ * TODO: type safe this behaviour of the third argument (options).
860
+ */
861
+ export type TranslationQuery =
862
+ | undefined
863
+ | null
864
+ | TranslationShortcut
865
+ | {
866
+ [key: string]: string | number | boolean;
867
+ };
868
+
869
+ /**
870
+ * Opions of the translate function or a TranslationShortcut.
871
+ *
872
+ * NOTE: when using a shortcut passing TranslationOptions to \`t()\` is not supported
873
+ * TODO: type safe this behaviour of the third argument (options).
874
+ */
875
+ export type TranslationOptions =
876
+ | undefined
877
+ | TranslationShortcut
878
+ | {
879
+ returnObjects?: boolean;
880
+ fallback?: string | string[];
881
+ default?: string;
882
+ };
883
+
884
+ /**
885
+ * Translate function which optionally accept a namespace as first argument
886
+ */
887
+ export type Translate<
888
+ TNamespace extends TranslateNamespace | undefined = TranslateNamespace,
889
+ > = TNamespace extends TranslateNamespace
890
+ ? TranslateNamespaced<TNamespace>
891
+ : TranslateDefault;
892
+
893
+ /**
894
+ * Translate function **without** namespace, it allows to select any of the all
895
+ * available strings in _all_ namespaces.
896
+ */
897
+ export type TranslateDefault = <
898
+ TPath extends TranslationsAllPaths,
899
+ TReturn = TranslationAtPath<TPath>,
900
+ >(
901
+ s: TPath,
902
+ q?: TranslationQuery,
903
+ o?: TranslationOptions,
904
+ ) => TReturn;
905
+
906
+ /**
907
+ * Translate function **with** namespace, it allows to select all available
908
+ * strings _only_ in the given namespace.
909
+ */
910
+ export type TranslateNamespaced<TNamespace extends TranslateNamespace> = <
911
+ TPath extends TranslationsPaths<TranslationsDictionary[TNamespace]>,
912
+ TReturn = TranslationAtPathFromNamespace<TNamespace, TPath>,
913
+ >(
914
+ s: TPath,
915
+ q?: TranslationQuery,
916
+ o?: TranslationOptions,
917
+ ) => TReturn;
918
+
919
+ /**
920
+ * Translate function _loose_ type, to use only in implementations that uses
921
+ * the \`t\` function indirectly without needng knowledge of the string it needs
922
+ * to output.
923
+ */
924
+ export type TranslateLoose<TReturn = string> = (
925
+ s?: any,
926
+ q?: TranslationQuery,
927
+ o?: TranslationOptions,
928
+ ) => TReturn;
929
+
930
+ /**
931
+ * Translate function _loosest_ type it allows to return string or object or array
932
+ * or whatever basically.
933
+ */
934
+ export type TranslateLoosest<TReturn = any> = (
935
+ s?: any,
936
+ q?: TranslationQuery,
937
+ o?: TranslationOptions,
938
+ ) => TReturn;
939
+ }
940
+ `;
941
+ };
942
+
943
+ const adapter$2 = () => {
944
+ return {
945
+ files: [
946
+ { name: "config.cjs", fn: configCjs, ext: "js" },
947
+ { name: "config", fn: config, ext: "ts", index: true },
948
+ { name: "defaultLocale", fn: defaultLocale, ext: "ts", index: true },
949
+ {
950
+ name: "deriveLocalisedPathnames",
951
+ fn: deriveLocalisedPathnames,
952
+ ext: "ts",
953
+ index: true,
954
+ },
955
+ { name: "isLocale", fn: isLocale, ext: "ts", index: true },
956
+ { name: "locales", fn: locales, ext: "ts", index: true },
957
+ {
958
+ name: "pathnameToRouteId",
959
+ fn: pathnameToRouteId,
960
+ ext: "ts",
961
+ index: true,
962
+ },
963
+ { name: "routes", fn: routes, ext: "ts" },
964
+ { name: "routesSlim", fn: routesSlim, ext: "ts" },
965
+ { name: "routesSpa", fn: routesSpa, ext: "ts" },
966
+ { name: "tFns", fn: tFns, ext: "ts" },
967
+ {
968
+ name: "tInterpolateParams",
969
+ fn: tInterpolateParams,
970
+ ext: "ts",
971
+ },
972
+ { name: "to", fn: to, ext: "ts", index: true },
973
+ { name: "toFns", fn: toFns, ext: "ts", index: true },
974
+ { name: "toFormat", fn: toFormat, ext: "ts", index: true },
975
+ { name: "toSpa", fn: toSpa, ext: "ts", index: true },
976
+ { name: "tPluralise", fn: tPluralise, ext: "ts" },
977
+ { name: "types", fn: types, ext: "ts", index: true },
978
+ ],
979
+ };
980
+ };
981
+
982
+ var DynamicNamespaces = () => `
983
+ "use client";
984
+
985
+ import _DynamicNamespaces from "next-translate/DynamicNamespaces";
986
+
987
+ export const DynamicNamespaces = _DynamicNamespaces;
988
+
989
+ export default DynamicNamespaces;
990
+ `;
991
+
992
+ var T = () => `
993
+ "use client";
994
+
995
+ import type { TransProps } from "next-translate";
996
+ import Trans from "next-translate/Trans";
997
+ import type { I18n } from "./types";
998
+
999
+ export type TProps<
1000
+ TNamespace extends I18n.TranslateNamespace | undefined = undefined,
1001
+ > =
1002
+ | (Omit<TransProps, "i18nKey" | "ns"> & {
1003
+ i18nKey: I18n.TranslationsAllPaths;
1004
+ })
1005
+ | (Omit<TransProps, "i18nKey" | "ns"> & {
1006
+ ns: TNamespace;
1007
+ i18nKey: I18n.TranslationsPaths<TNamespace>;
1008
+ });
1009
+
1010
+ const TypedT = <
1011
+ TNamespace extends I18n.TranslateNamespace | undefined = undefined,
1012
+ >(
1013
+ props: TProps<TNamespace>,
1014
+ ) =>
1015
+ (<Trans {...(props as TransProps)} />) as React.ReactElement<
1016
+ TProps<TNamespace>
1017
+ >;
1018
+
1019
+ export const T = Trans as typeof TypedT;
1020
+
1021
+ export default T;
1022
+ `;
1023
+
1024
+ var TransText = () => `
1025
+ "use client";
1026
+
1027
+ import _TransText from "next-translate/TransText";
1028
+
1029
+ export const TransText = _TransText;
1030
+
1031
+ export default TransText;
1032
+ `;
1033
+
1034
+ var getT = () => `
1035
+ import _getT from "next-translate/getT";
1036
+ import type { I18n } from "./types";
1037
+
1038
+ export type GetT = <
1039
+ TNamespace extends I18n.TranslateNamespace | undefined = undefined,
1040
+ >(
1041
+ locale?: I18n.Locale,
1042
+ namespace?: TNamespace,
1043
+ ) => Promise<I18n.Translate<TNamespace>>;
1044
+
1045
+ export const getT = _getT as GetT;
1046
+
1047
+ export default getT;
1048
+ `;
1049
+
1050
+ var nextTranslateI18n = ({ config }) => `
1051
+ /**
1052
+ * Get 'next-translate' configuration
1053
+ *
1054
+ * @see https://github.com/vinissimus/next-translate#how-are-translations-loaded
1055
+ *
1056
+ * @param {Omit<Partial<import("next-translate").I18nConfig>, "pages"> & { pages: Record<string, import("./types").I18n.TranslateNamespace[]> }} config
1057
+ */
1058
+ module.exports = (config = { pages: {} }) => {
1059
+ return {
1060
+ locales: [${config.locales.map((l) => `"${l}"`).join(", ")}],
1061
+ defaultLocale: "${config.defaultLocale}",
1062
+ logBuild: false,
1063
+ // logger: () => void 0,
1064
+ loadLocaleFrom: (locale, namespace) => import(\`./translations/\${locale}/\${namespace}.json\`).then((m) => m.default),
1065
+ ...config,
1066
+ };
1067
+ }
1068
+ `;
1069
+
1070
+ var useT = () => `
1071
+ "use client";
1072
+
1073
+ import { useMemo } from "react";
1074
+ import useTranslation from "next-translate/useTranslation";
1075
+ import type { I18n } from "./types";
1076
+
1077
+ /**
1078
+ * Wrap next-translate useTranslations for type safety and adds TranslationShortcut
1079
+ * as second/thir argument.
1080
+ *
1081
+ * @see https://github.com/vinissimus/next-translate/issues/513#issuecomment-779826418
1082
+ *
1083
+ * About the typescript support for translation strings see:
1084
+ * - https://github.com/vinissimus/next-translate/issues/721
1085
+ */
1086
+ export const useT = <TNamespace extends I18n.TranslateNamespace>(namespace: TNamespace) => {
1087
+ const t = useTranslation().t;
1088
+ const tMemoized = useMemo(
1089
+ () =>
1090
+ function <
1091
+ TPath extends I18n.TranslationsPaths<I18n.TranslationsDictionary[TNamespace]>,
1092
+ TReturn = I18n.TranslationAtPathFromNamespace<TNamespace, TPath>,
1093
+ >(s: TPath, q?: I18n.TranslationQuery, o?: I18n.TranslationOptions): TReturn {
1094
+ return t(
1095
+ (namespace ? namespace + ":" + s : s) as string,
1096
+ q === "obj" || q === "" ? null : q,
1097
+ q === "obj" || o === "obj"
1098
+ ? { returnObjects: true }
1099
+ : q === "" || o === ""
1100
+ ? { fallback: "" }
1101
+ : o,
1102
+ ) as TReturn;
1103
+ // ) as TReturn extends (undefined | never | unknown) ? TranslateReturn<I18n.TranslationQuery, I18n.TranslationOptions> : TReturn;
1104
+ // );
1105
+ },
1106
+ [t, namespace],
1107
+ );
1108
+ return tMemoized;
1109
+ };
1110
+
1111
+ export default useT;
1112
+ `;
1113
+
1114
+ const adapter$1 = () => {
1115
+ return {
1116
+ dependsOn: ["next"],
1117
+ needsTranslationsFiles: true,
1118
+ files: [
1119
+ {
1120
+ name: "DynamicNamespaces",
1121
+ fn: DynamicNamespaces,
1122
+ ext: "tsx",
1123
+ index: true,
1124
+ },
1125
+ { name: "getT", fn: getT, ext: "ts", index: true },
1126
+ { name: "nextTranslateI18n", fn: nextTranslateI18n, ext: "js" },
1127
+ { name: "T", fn: T, ext: "tsx", index: true },
1128
+ { name: "TransText", fn: TransText, ext: "tsx", index: true },
1129
+ { name: "useT", fn: useT, ext: "ts", index: true },
1130
+ ],
1131
+ };
1132
+ };
1133
+
1134
+ function transformPathname(rawPathnameOrTemplate, wildcard) {
1135
+ return ("/" +
1136
+ rawPathnameOrTemplate
1137
+ .split("/")
1138
+ .filter(Boolean)
1139
+ .map((part) => {
1140
+ if (part.startsWith("[[...")) {
1141
+ return `:${encodeURIComponent(part.slice(5, -2))}`;
1142
+ }
1143
+ if (part.startsWith("[[")) {
1144
+ return `:${encodeURIComponent(part.slice(2, -2))}`;
1145
+ }
1146
+ if (part.startsWith("[")) {
1147
+ return `:${encodeURIComponent(part.slice(1, -1))}`;
1148
+ }
1149
+ return `${encodeURIComponent(part)}`;
1150
+ })
1151
+ .join("/") +
1152
+ (wildcard ? "/:wildcard*" : ""));
1153
+ }
1154
+
1155
+ function generatePathRedirect(arg) {
1156
+ const { localeSource, localeDestination, template, pathname, permanent } = arg;
1157
+ const sourcePrefix = localeSource ? `${localeSource}/` : "";
1158
+ const source = formatRoutePathname(sourcePrefix + template);
1159
+ const destinationPrefix = localeDestination ? `${localeDestination}/` : "";
1160
+ const destination = formatRoutePathname(destinationPrefix + pathname);
1161
+ if (source === destination)
1162
+ return;
1163
+ const redirect = {
1164
+ source,
1165
+ destination,
1166
+ permanent: Boolean(permanent),
1167
+ };
1168
+ return redirect;
1169
+ }
1170
+ let generateRedirects = (config, routes, options, localeParam = "", permanent = false) => {
1171
+ const { defaultLocale, hideDefaultLocaleInUrl } = config;
1172
+ const regexIdDelimiter = new RegExp(escapeRegExp(options.tokens.idDelimiter), "g");
1173
+ const redirects = [];
1174
+ for (const routeId in routes) {
1175
+ const route = routes[routeId];
1176
+ const pathnamesByLocale = routes[routeId].pathnames;
1177
+ for (const locale in pathnamesByLocale) {
1178
+ const localisedPathname = pathnamesByLocale[locale];
1179
+ const template = transformPathname(routeId.replace(regexIdDelimiter, "/"), route.wildcard);
1180
+ const pathname = transformPathname(localisedPathname, route.wildcard);
1181
+ if (route.inWildcard)
1182
+ break;
1183
+ const isDefaultLocale = locale === defaultLocale;
1184
+ const isVisibleDefaultLocale = isDefaultLocale && !hideDefaultLocaleInUrl;
1185
+ const isHiddenDefaultLocale = isDefaultLocale && hideDefaultLocaleInUrl;
1186
+ const arg = { template, pathname, permanent };
1187
+ if (localeParam) {
1188
+ if (isVisibleDefaultLocale) {
1189
+ redirects.push(generatePathRedirect({ ...arg, localeDestination: locale }));
1190
+ }
1191
+ else if (isHiddenDefaultLocale) {
1192
+ redirects.push(generatePathRedirect({ ...arg, localeSource: locale }));
1193
+ }
1194
+ else if (!isDefaultLocale) {
1195
+ redirects.push(generatePathRedirect({
1196
+ ...arg,
1197
+ localeSource: locale,
1198
+ localeDestination: locale,
1199
+ }));
1200
+ }
1201
+ else {
1202
+ redirects.push(generatePathRedirect(arg));
1203
+ }
1204
+ }
1205
+ else {
1206
+ if (pathname !== template) {
1207
+ if (isVisibleDefaultLocale) {
1208
+ redirects.push(generatePathRedirect({ ...arg, localeDestination: locale }));
1209
+ }
1210
+ else if (!isDefaultLocale) {
1211
+ redirects.push(generatePathRedirect({
1212
+ ...arg,
1213
+ localeSource: locale,
1214
+ localeDestination: locale,
1215
+ }));
1216
+ }
1217
+ else {
1218
+ redirects.push(generatePathRedirect(arg));
1219
+ }
1220
+ }
1221
+ }
1222
+ }
1223
+ }
1224
+ const cleaned = arrayUniqueByProperties(redirects.filter(Boolean), ["source", "destination"])
1225
+ .sort((a, b) => a.source.localeCompare(b.source))
1226
+ .map((rewrite) => (localeParam ? rewrite : { ...rewrite, locale: false }));
1227
+ return cleaned;
1228
+ };
1229
+
1230
+ var nextRedirects = ({ config, routes, options }) => {
1231
+ const value = JSON.stringify(generateRedirects(config, routes.byId, options.routes), null, 2);
1232
+ return `module.exports = ${value}`;
1233
+ };
1234
+
1235
+ function generatePathRewrite(arg) {
1236
+ const { localeSource, localeDestination, template, pathname } = arg;
1237
+ let sourcePrefix = "";
1238
+ if (localeSource)
1239
+ sourcePrefix = `/${localeSource}`;
1240
+ const source = formatRoutePathname(sourcePrefix + pathname);
1241
+ let destinationPrefix = "";
1242
+ if (localeDestination)
1243
+ destinationPrefix = `/${localeDestination}`;
1244
+ const destination = formatRoutePathname(destinationPrefix + template);
1245
+ if (source === destination)
1246
+ return;
1247
+ const rewrite = { source, destination };
1248
+ return rewrite;
1249
+ }
1250
+ const generateRewriteForPathname = (config, localeParam = "", locale, template, pathname, rewrites) => {
1251
+ const { defaultLocale, hideDefaultLocaleInUrl } = config;
1252
+ const isDefaultLocale = locale === defaultLocale;
1253
+ const isHiddenDefaultLocale = isDefaultLocale && hideDefaultLocaleInUrl;
1254
+ const isHiddenLocale = isHiddenDefaultLocale;
1255
+ const arg = { config, template, pathname };
1256
+ if (localeParam) {
1257
+ if (isHiddenLocale) {
1258
+ rewrites.push(generatePathRewrite({ ...arg, localeDestination: locale }));
1259
+ }
1260
+ else {
1261
+ rewrites.push(generatePathRewrite({ ...arg, localeSource: locale, localeDestination: locale }));
1262
+ }
1263
+ }
1264
+ else {
1265
+ if (pathname !== template) {
1266
+ if (isHiddenLocale) {
1267
+ rewrites.push(generatePathRewrite(arg));
1268
+ }
1269
+ else {
1270
+ rewrites.push({
1271
+ ...generatePathRewrite({ ...arg, localeSource: locale }),
1272
+ locale: false,
1273
+ });
1274
+ }
1275
+ }
1276
+ }
1277
+ };
1278
+ let generateRewrites = (config, routes, options, localeParam = "") => {
1279
+ const regexIdDelimiter = new RegExp(escapeRegExp(options.tokens.idDelimiter), "g");
1280
+ const rewrites = [];
1281
+ for (const routeId in routes) {
1282
+ const route = routes[routeId];
1283
+ const pathnamesByLocale = routes[routeId].pathnames;
1284
+ for (const locale in pathnamesByLocale) {
1285
+ const localisedPathname = pathnamesByLocale[locale];
1286
+ const routeIdAsTemplate = routeId.replace(regexIdDelimiter, "/");
1287
+ if (route.inWildcard)
1288
+ break;
1289
+ generateRewriteForPathname(config, localeParam, locale, transformPathname(routeIdAsTemplate), transformPathname(localisedPathname), rewrites);
1290
+ if (route.wildcard) {
1291
+ generateRewriteForPathname(config, localeParam, locale, transformPathname(routeIdAsTemplate, route.wildcard), transformPathname(localisedPathname, route.wildcard), rewrites);
1292
+ }
1293
+ }
1294
+ }
1295
+ const cleaned = arrayUniqueByProperties(rewrites.filter(Boolean), ["source", "destination"]).sort((a, b) => {
1296
+ return a.source.localeCompare(b.source);
1297
+ });
1298
+ return cleaned;
1299
+ };
1300
+
1301
+ var nextRewrites = ({ config, routes, options }) => {
1302
+ const value = JSON.stringify(generateRewrites(config, routes.byId, options.routes), null, 2);
1303
+ return `module.exports = ${value}`;
1304
+ };
1305
+
1306
+ var useCurrentLocalisedPathnames = () => `
1307
+ "use client";
1308
+
1309
+ import { useEffect, useState } from "react";
1310
+ import { deriveLocalisedPathnames, type LocalisedPathnames } from "./deriveLocalisedPathnames";
1311
+ import { useRouteId } from "./useRouteId";
1312
+
1313
+ export function useCurrentLocalisedPathnames() {
1314
+ const routeId = useRouteId();
1315
+ const [urls, setUrls] = useState<LocalisedPathnames>({} as LocalisedPathnames);
1316
+
1317
+ useEffect(() => {
1318
+ setUrls(deriveLocalisedPathnames(routeId, location));
1319
+ }, [routeId]);
1320
+
1321
+ return urls;
1322
+ }
1323
+
1324
+ export default useCurrentLocalisedPathnames;
1325
+ `;
1326
+
1327
+ var useLocale = ({ config }) => `
1328
+ import { useRouter } from "next/router";
1329
+ import type { I18n } from "./types";
1330
+
1331
+ export const useLocale = () => (useRouter().locale as I18n.Locale) || "${config.defaultLocale}";
1332
+
1333
+ export default useLocale;
1334
+ `;
1335
+
1336
+ var useRouteId = () => `
1337
+ import { useRouter } from "next/router";
1338
+ import type { I18n } from "./types";
1339
+ import { pathnameToRouteId } from "./pathnameToRouteId";
1340
+
1341
+ export const useRouteId = () =>
1342
+ pathnameToRouteId(useRouter().pathname) as I18n.RouteId;
1343
+
1344
+ export default useRouteId;
1345
+ `;
1346
+
1347
+ var useTo = () => `
1348
+ "use client";
1349
+
1350
+ import { to } from "./to";
1351
+ import type { I18n } from "./types";
1352
+ import { useLocale } from "./useLocale";
1353
+
1354
+ export type UseToReturn = ReturnType<typeof useTo>;
1355
+
1356
+ export const useTo = () => {
1357
+ const locale = useLocale();
1358
+ return <Id extends I18n.RouteId>(
1359
+ routeId: Id,
1360
+ ...args: Id extends I18n.RouteIdDynamic
1361
+ ? [params: I18n.RouteParams[Id]]
1362
+ : []
1363
+ ) =>
1364
+ args[0]
1365
+ ? // @ts-expect-error nevermind
1366
+ (to(routeId, args[0], locale) as I18n.RoutePathnames[Id])
1367
+ : // @ts-expect-error nevermind
1368
+ (to(routeId, locale) as I18n.RoutePathnames[Id]);
1369
+ };
1370
+
1371
+ export default useTo;
1372
+ `;
1373
+
1374
+ var useToSpa = () => `
1375
+ "use client";
1376
+
1377
+ import { toSpa } from "./toSpa";
1378
+ import type { I18n } from "./types";
1379
+ import { useLocale } from "./useLocale";
1380
+
1381
+ export type UseToSpaReturn = ReturnType<typeof useToSpa>;
1382
+
1383
+ export const useToSpa = () => {
1384
+ const locale = useLocale();
1385
+ return <
1386
+ Root extends keyof I18n.RouteSpa,
1387
+ Path extends Extract<keyof I18n.RouteSpa[Root], string>,
1388
+ >(
1389
+ root: Root,
1390
+ path: Path,
1391
+ ...args: I18n.RouteJoinedId<Root, Path> extends I18n.RouteIdDynamic
1392
+ ? [params: I18n.RouteParams[I18n.RouteJoinedId<Root, Path>]]
1393
+ : I18n.RouteJoinedId<Root, Path> extends I18n.RouteIdStatic
1394
+ ? []
1395
+ : never
1396
+ ) => {
1397
+ const [params] = args;
1398
+ return (
1399
+ // prettier-ignore
1400
+ // @ts-expect-error FIXME: types
1401
+ (params ? toSpa(root, path, params, locale) : toSpa(root, path, locale)) as I18n.RouteSpa[Root][Path]
1402
+ );
1403
+ };
1404
+ };
1405
+
1406
+ export default useToSpa;
1407
+ `;
1408
+
1409
+ const adapter = () => {
1410
+ return {
1411
+ dependsOn: ["js"],
1412
+ files: [
1413
+ { name: "next-redirects", fn: nextRedirects, ext: "js" },
1414
+ { name: "next-rewrites", fn: nextRewrites, ext: "js" },
1415
+ {
1416
+ name: "useCurrentLocalisedPathnames",
1417
+ fn: useCurrentLocalisedPathnames,
1418
+ ext: "ts",
1419
+ index: true,
1420
+ },
1421
+ { name: "useLocale", fn: useLocale, ext: "ts", index: true },
1422
+ { name: "useRouteId", fn: useRouteId, ext: "ts", index: true },
1423
+ { name: "useTo", fn: useTo, ext: "ts", index: true },
1424
+ { name: "useToSpa", fn: useToSpa, ext: "ts", index: true },
1425
+ ],
1426
+ };
1427
+ };
1428
+
1429
+ const adaptersMap = {
1430
+ js: adapter$2,
1431
+ next: adapter,
1432
+ "next-translate": adapter$1,
1433
+ };
1434
+ const getIndexFile = (generatedFiles) => {
1435
+ let output = "";
1436
+ generatedFiles
1437
+ .filter((generatedFile) => generatedFile.index)
1438
+ .sort((a, b) => a.name.localeCompare(b.name))
1439
+ .forEach((generatedFile) => {
1440
+ output += `export * from "./${generatedFile.name}";\n`;
1441
+ });
1442
+ return output;
1443
+ };
1444
+ const getAdapters = async (adapterArg, adapterName, adapters = []) => {
1445
+ const adapterCreator = adaptersMap[adapterName];
1446
+ const adapterToResolve = adapterCreator(adapterArg);
1447
+ const adapter = isPromise(adapterToResolve)
1448
+ ? await adapterToResolve
1449
+ : adapterToResolve;
1450
+ adapters = adapters.concat(adapter);
1451
+ if (adapter.dependsOn) {
1452
+ await Promise.all(adapter.dependsOn.map(async (adapaterName) => {
1453
+ adapters = adapters.concat(await getAdapters(adapterArg, adapaterName));
1454
+ }));
1455
+ }
1456
+ return adapters;
1457
+ };
1458
+ const generateCodeFromAdapters = (data, options, adapters) => {
1459
+ const { outputFiles } = options;
1460
+ const files = adapters.reduce((allFiles, adapter) => [...allFiles, ...adapter.files], []);
1461
+ const generatedFiles = files.map((file) => {
1462
+ const { fn, ...rest } = file;
1463
+ const name = outputFiles?.[rest.name] || rest.name;
1464
+ return { ...rest, name, content: fn(data) };
1465
+ });
1466
+ const indexFileContent = getIndexFile(generatedFiles);
1467
+ if (indexFileContent) {
1468
+ generatedFiles.push({
1469
+ name: "index",
1470
+ ext: "ts",
1471
+ content: getIndexFile(generatedFiles),
1472
+ });
1473
+ }
1474
+ return {
1475
+ files: generatedFiles,
1476
+ needsTranslationsFiles: adapters.some((adapter) => adapter.needsTranslationsFiles),
1477
+ };
1478
+ };
1479
+ let generateCode = async (data, options) => generateCodeFromAdapters(data, options, await getAdapters(data, options.adapter));
1480
+
1481
+ let tsCompile = (cwd, output, relativePaths, tsOptions) => {
1482
+ const rootNames = Array.from(relativePaths)
1483
+ .filter((p) => p.endsWith(".ts") || p.endsWith(".tsx"))
1484
+ .map((relativePath) => join(cwd, output, relativePath));
1485
+ const compilerOptions = {
1486
+ noEmitOnError: true,
1487
+ noImplicitAny: true,
1488
+ declaration: true,
1489
+ target: ts.ScriptTarget.ESNext,
1490
+ module: ts.ModuleKind.ESNext,
1491
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
1492
+ resolveJsonModule: true,
1493
+ allowJs: false,
1494
+ esModuleInterop: true,
1495
+ jsx: ts.JsxEmit.ReactJSX,
1496
+ outDir: join(cwd, output),
1497
+ skipLibCheck: true,
1498
+ noEmitHelpers: true,
1499
+ importHelpers: true,
1500
+ ...(tsOptions || {}),
1501
+ };
1502
+ const program = ts.createProgram(rootNames, compilerOptions);
1503
+ const emitResult = program.emit();
1504
+ const allDiagnostics = ts
1505
+ .getPreEmitDiagnostics(program)
1506
+ .concat(emitResult.diagnostics);
1507
+ allDiagnostics.forEach((diagnostic) => {
1508
+ if (diagnostic.file) {
1509
+ const { line, character } = ts.getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);
1510
+ const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
1511
+ console.log(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`);
1512
+ }
1513
+ else {
1514
+ console.log(ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"));
1515
+ }
1516
+ });
1517
+ return emitResult;
1518
+ };
1519
+
1520
+ const writeCodeFiles = async (cwd, output, codeGenerated, files) => Promise.all(codeGenerated.files.map(async ({ name, ext, content }) => {
1521
+ const relativePath = `${name}.${ext}`;
1522
+ const filepath = join(cwd, output, relativePath);
1523
+ await fsWrite(filepath, content);
1524
+ files.add(relativePath);
1525
+ }));
1526
+ const writeCompiledTypescriptFiles = (cwd, output, files) => {
1527
+ const tsFiles = Array.from(files).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
1528
+ tsCompile(cwd, output, tsFiles);
1529
+ tsFiles.forEach((relativePath) => {
1530
+ files.add(relativePath.replace(/\.tsx?$/, ".js"));
1531
+ files.add(relativePath.replace(/\.tsx?$/, ".d.ts"));
1532
+ files.delete(relativePath);
1533
+ rmSync(join(cwd, output, relativePath), { force: true });
1534
+ });
1535
+ };
1536
+ const writeTranslationsFiles = async (cwd, output, { translationFiles }, folders) => Promise.all(translationFiles.map(async ({ data, locale, path }) => {
1537
+ const relativePath = join("translations", locale);
1538
+ await fsWrite(join(cwd, output, relativePath, path), JSON.stringify(data));
1539
+ folders.add(relativePath);
1540
+ }));
1541
+ const getWriteGitgnoreArgs = (cwd, output, folders, files) => [
1542
+ join(cwd, output, ".gitignore"),
1543
+ Array.from(new Set([...folders, ...files]))
1544
+ .sort()
1545
+ .map((relativePath) => `/${relativePath}`)
1546
+ .join(`\n`),
1547
+ ];
1548
+ let writeCode = async (options, data) => {
1549
+ const { cwd = process.cwd(), output, skipTsCompile, skipGitignore, skipTranslations, ...generateOptions } = options;
1550
+ const code = await generateCode(data, generateOptions);
1551
+ const files = new Set();
1552
+ const folders = new Set();
1553
+ await writeCodeFiles(cwd, output, code, files);
1554
+ if (!skipTsCompile) {
1555
+ writeCompiledTypescriptFiles(cwd, output, files);
1556
+ }
1557
+ if (code.needsTranslationsFiles && !skipTranslations) {
1558
+ await writeTranslationsFiles(cwd, output, data.input, folders);
1559
+ }
1560
+ if (!skipGitignore) {
1561
+ await fsWrite(...getWriteGitgnoreArgs(cwd, output, folders, files));
1562
+ }
1563
+ return;
1564
+ };
1565
+
1566
+ const codeDataRoutesOptions = {
1567
+ translationJsonFileName: "~.json",
1568
+ fnsPrefix: "",
1569
+ tokens: {
1570
+ parentReference: "^",
1571
+ idDelimiter: ".",
1572
+ catchAll: {
1573
+ start: "[...",
1574
+ end: "]",
1575
+ },
1576
+ optionalCatchAll: {
1577
+ start: "[[...",
1578
+ end: "]]",
1579
+ },
1580
+ },
1581
+ };
1582
+ const getCodeDataRoutesUtils = (config, options) => {
1583
+ const { idDelimiter, parentReference, catchAll, optionalCatchAll } = options.tokens;
1584
+ return {
1585
+ ...config,
1586
+ ...options,
1587
+ reg: {
1588
+ trailingDelimiter: new RegExp(`${escapeRegExp(idDelimiter)}+$`),
1589
+ indexEnd: new RegExp(`${escapeRegExp(idDelimiter)}index$`),
1590
+ parentReference: new RegExp(`^/${escapeRegExp(parentReference)}`),
1591
+ catchAll: new RegExp(`${escapeRegExp(catchAll.start)}(.+)${escapeRegExp(catchAll.end)}$`),
1592
+ optionalCatchAll: new RegExp(`${escapeRegExp(optionalCatchAll.start)}(.+)${escapeRegExp(optionalCatchAll.end)}$`),
1593
+ },
1594
+ };
1595
+ };
1596
+ const parseUserDefinedRouteId = (userRouteId, { reg }) => {
1597
+ let routeId = userRouteId.replace(reg.indexEnd, "");
1598
+ const isOptionalCatchAll = reg.optionalCatchAll.test(userRouteId);
1599
+ const isCatchAll = reg.catchAll.test(userRouteId);
1600
+ if (isOptionalCatchAll)
1601
+ routeId = routeId.replace(reg.optionalCatchAll, "");
1602
+ if (isCatchAll)
1603
+ routeId = routeId.replace(reg.catchAll, "");
1604
+ routeId = routeId.replace(reg.trailingDelimiter, "");
1605
+ return {
1606
+ routeId,
1607
+ isCatchAll,
1608
+ isOptionalCatchAll,
1609
+ };
1610
+ };
1611
+ const normaliseUserDefinedRoutePathname = (routePathname) => formatRoutePathname(routePathname
1612
+ .replace(/\*/g, "")
1613
+ .replace(/[[{]{1,2}(.*?)[\]}]{1,2}/g, (_search, replaceValue) => `[${replaceValue.trim()}]`));
1614
+ const extractRouteParamsFromRouteId = (routeId) => {
1615
+ const matches = routeId.match(/\[.*?\]/g);
1616
+ if (matches) {
1617
+ return matches
1618
+ .map((match) => match.slice(1, -1).trim())
1619
+ .reduce((map, paramName) => {
1620
+ map[paramName] = "stringOrNumber";
1621
+ return map;
1622
+ }, {});
1623
+ }
1624
+ return;
1625
+ };
1626
+ const replaceRouteParentTokens = (dataRoutes, utils, locale, id, continueWhen) => {
1627
+ const { tokens: { parentReference, idDelimiter }, reg, } = utils;
1628
+ const route = dataRoutes.byId[id];
1629
+ let pathname = route.pathnames[locale];
1630
+ if (pathname.startsWith(`/${parentReference}`)) {
1631
+ pathname = pathname.replace(reg.parentReference, "");
1632
+ const parentId = id.split(idDelimiter).slice(0, -1).join(idDelimiter);
1633
+ if (!continueWhen || (continueWhen && continueWhen(route, parentId))) {
1634
+ if (parentId) {
1635
+ pathname =
1636
+ replaceRouteParentTokens(dataRoutes, utils, locale, parentId, continueWhen) + pathname;
1637
+ }
1638
+ else {
1639
+ throw Error("Used a parent route token reference without a matching parent route");
1640
+ }
1641
+ }
1642
+ }
1643
+ return pathname;
1644
+ };
1645
+ const replaceRoutesPathnamesParentTokens = (dataRoutes, utils) => {
1646
+ for (const routeId in dataRoutes.byId) {
1647
+ for (const locale in dataRoutes.byId[routeId].pathnames) {
1648
+ dataRoutes.byId[routeId].pathnames[locale] = replaceRouteParentTokens(dataRoutes, utils, locale, routeId);
1649
+ }
1650
+ }
1651
+ };
1652
+ const manageRoutesSpaPathnames = (dataRoutes, utils) => {
1653
+ const continueWhen = (currentRoute, parentRouteId) => currentRoute.inWildcard && !dataRoutes.wildcardIds.includes(parentRouteId);
1654
+ for (const routeId in dataRoutes.byId) {
1655
+ const { inWildcard } = dataRoutes.byId[routeId];
1656
+ if (inWildcard && dataRoutes.byId[routeId].pathnamesSpa) {
1657
+ for (const locale in dataRoutes.byId[routeId].pathnamesSpa) {
1658
+ dataRoutes.byId[routeId].pathnamesSpa[locale] =
1659
+ replaceRouteParentTokens(dataRoutes, utils, locale, routeId, continueWhen);
1660
+ }
1661
+ }
1662
+ else {
1663
+ delete dataRoutes.byId[routeId].pathnamesSpa;
1664
+ }
1665
+ }
1666
+ };
1667
+ const addRoutesSlimPathnames = (dataRoutes, utils) => {
1668
+ const { defaultLocale, locales } = utils;
1669
+ for (const routeId in dataRoutes.byId) {
1670
+ const pathnamesPerLocale = dataRoutes.byId[routeId].pathnames;
1671
+ const defaultLocalePathname = pathnamesPerLocale[defaultLocale];
1672
+ const pathnamesSlim = {};
1673
+ for (const locale in pathnamesPerLocale) {
1674
+ const url = pathnamesPerLocale[locale];
1675
+ if (url !== defaultLocalePathname) {
1676
+ pathnamesSlim[locale] = url;
1677
+ }
1678
+ }
1679
+ if (Object.keys(pathnamesSlim).length === locales.length - 1) ;
1680
+ else if (Object.keys(pathnamesSlim).length >= 1) {
1681
+ pathnamesSlim[defaultLocale] = defaultLocalePathname;
1682
+ dataRoutes.byId[routeId].pathnamesSlim = objectSortByKeysMatching(pathnamesSlim, defaultLocale);
1683
+ }
1684
+ else {
1685
+ dataRoutes.byId[routeId].pathnamesSlim = defaultLocalePathname;
1686
+ }
1687
+ }
1688
+ };
1689
+ const addInWildcardFlags = (dataRoutes) => {
1690
+ if (dataRoutes.wildcardIds.length) {
1691
+ for (const routeId in dataRoutes.byId) {
1692
+ const inWildcard = dataRoutes.wildcardIds.some((wildcardRouteId) => routeId.startsWith(wildcardRouteId) && wildcardRouteId !== routeId);
1693
+ if (inWildcard)
1694
+ dataRoutes.byId[routeId].inWildcard = true;
1695
+ }
1696
+ }
1697
+ };
1698
+ const buildDataRoutesFromJsonData = (json, locale, utils, data) => {
1699
+ const routes = objectFlat(json, utils.tokens.idDelimiter);
1700
+ for (const _key in routes) {
1701
+ const key = _key;
1702
+ const routePathname = routes[key];
1703
+ const { routeId, isCatchAll, isOptionalCatchAll } = parseUserDefinedRouteId(key, utils);
1704
+ if (!data.byId[routeId]) {
1705
+ data.byId[routeId] = data.byId[routeId] || {};
1706
+ const params = extractRouteParamsFromRouteId(routeId);
1707
+ data.byId[routeId].id = routeId;
1708
+ if (params)
1709
+ data.byId[routeId].params = params;
1710
+ if (isCatchAll || isOptionalCatchAll) {
1711
+ data.byId[routeId].wildcard = true;
1712
+ data.wildcardIds.push(routeId);
1713
+ }
1714
+ }
1715
+ data.byId[routeId].pathnames = data.byId[routeId].pathnames || {};
1716
+ data.byId[routeId].pathnames[locale] = normaliseUserDefinedRoutePathname(routePathname);
1717
+ data.byId[routeId].pathnames = objectSortByKeysMatching(data.byId[routeId].pathnames, utils.defaultLocale);
1718
+ data.byId[routeId].pathnamesSpa = { ...data.byId[routeId].pathnames };
1719
+ }
1720
+ data.byId = objectSort(data.byId);
1721
+ };
1722
+ let getCodeDataRoutes = (config, options, { translationFiles }) => {
1723
+ const dataRoutes = { byId: {}, wildcardIds: [] };
1724
+ const utils = getCodeDataRoutesUtils(config, options);
1725
+ for (let i = 0; i < translationFiles.length; i++) {
1726
+ const { path, locale, data } = translationFiles[i];
1727
+ if (path === options.translationJsonFileName) {
1728
+ buildDataRoutesFromJsonData(data, locale, utils, dataRoutes);
1729
+ }
1730
+ }
1731
+ addInWildcardFlags(dataRoutes);
1732
+ manageRoutesSpaPathnames(dataRoutes, utils);
1733
+ replaceRoutesPathnamesParentTokens(dataRoutes, utils);
1734
+ addRoutesSlimPathnames(dataRoutes, utils);
1735
+ return dataRoutes;
1736
+ };
1737
+
1738
+ const codeDataTranslationsOptions = {
1739
+ ignorePaths: [],
1740
+ dynamicDelimiters: {
1741
+ start: "{{",
1742
+ end: "}}",
1743
+ },
1744
+ fnsAsDataCodes: true,
1745
+ fnsPrefix: "",
1746
+ createArrayIndexBasedFns: false,
1747
+ };
1748
+ const slashRegex = new RegExp(sep, "g");
1749
+ const normaliseTranslationKey = (key) => {
1750
+ const replaced = key
1751
+ .replace(/~/g, "$")
1752
+ .replace(/-/g, "_")
1753
+ .replace(slashRegex, "_")
1754
+ .replace(/_+/g, "_")
1755
+ .replace(/[^a-zA-Z0-9_$]/gi, "");
1756
+ return /^[0-9]/.test(replaced) ? "$" + replaced : replaced;
1757
+ };
1758
+ const extractTranslationParamsFromPrimitive = (options, value) => {
1759
+ if (isString(value)) {
1760
+ const { start, end } = options.dynamicDelimiters;
1761
+ const regex = new RegExp(`${start}(.*?)${end}`, "gm");
1762
+ const matches = value.match(regex);
1763
+ if (matches) {
1764
+ return matches
1765
+ .map((match) => match.replace(start, "").replace(end, "").trim())
1766
+ .reduce((map, paramName) => {
1767
+ map[paramName] = "stringOrNumber";
1768
+ return map;
1769
+ }, {});
1770
+ }
1771
+ }
1772
+ return;
1773
+ };
1774
+ const extractTranslationParamsFromValue = (options, value, params = {}) => {
1775
+ if (isPrimitive(value)) {
1776
+ const extracted = extractTranslationParamsFromPrimitive(options, value);
1777
+ if (extracted)
1778
+ params = { ...params, ...extracted };
1779
+ return params;
1780
+ }
1781
+ else if (isArray(value)) {
1782
+ for (let i = 0; i < value.length; i++) {
1783
+ const extracted = extractTranslationParamsFromPrimitive(options, value[i]);
1784
+ if (extracted)
1785
+ params = { ...params, ...extracted };
1786
+ }
1787
+ }
1788
+ else {
1789
+ for (const key in value) {
1790
+ const extracted = extractTranslationParamsFromValue(options, value[key], params);
1791
+ if (extracted)
1792
+ params = { ...params, ...extracted };
1793
+ }
1794
+ }
1795
+ return {};
1796
+ };
1797
+ const manageDataTranslationsPlurals = (options, dataTranslations) => {
1798
+ const pluralTranslationIds = Object.keys(dataTranslations).filter(isPluralKey);
1799
+ pluralTranslationIds.forEach((translationIdPluralised) => {
1800
+ const id = removePluralSuffix(translationIdPluralised);
1801
+ const pluralSuffix = getPluralSuffix(translationIdPluralised);
1802
+ dataTranslations[id] = dataTranslations[id] || {};
1803
+ if (dataTranslations[translationIdPluralised]) {
1804
+ const values = dataTranslations[id].values || {};
1805
+ forin(dataTranslations[translationIdPluralised].values, (locale, value) => {
1806
+ values[locale] = isObject(values[locale]) ? values[locale] : {};
1807
+ values[locale][pluralSuffix] = value;
1808
+ const params = extractTranslationParamsFromValue(options, value);
1809
+ if (params) {
1810
+ dataTranslations[id].params = {
1811
+ ...(dataTranslations[id].params || {}),
1812
+ ...params,
1813
+ };
1814
+ }
1815
+ });
1816
+ if (Object.keys(values).length) {
1817
+ dataTranslations[id].values = values;
1818
+ dataTranslations[id].plural = true;
1819
+ }
1820
+ delete dataTranslations[translationIdPluralised];
1821
+ }
1822
+ });
1823
+ return dataTranslations;
1824
+ };
1825
+ const addDataTranslationEntry = (options, id, locale, value, dataTranslations) => {
1826
+ if (isPrimitive(value)) {
1827
+ dataTranslations[id] = dataTranslations[id] || {};
1828
+ dataTranslations[id].values = dataTranslations[id].values || {};
1829
+ dataTranslations[id].values[locale] = value;
1830
+ dataTranslations[id].typeValue = "Primitive";
1831
+ const params = extractTranslationParamsFromPrimitive(options, value);
1832
+ if (params)
1833
+ dataTranslations[id].params = params;
1834
+ }
1835
+ else {
1836
+ if (options.fnsAsDataCodes) {
1837
+ const typeValue = isArray(value) ? "Array" : "Object";
1838
+ dataTranslations[id] = dataTranslations[id] || {};
1839
+ dataTranslations[id].values = dataTranslations[id].values || {};
1840
+ dataTranslations[id].values[locale] = value;
1841
+ dataTranslations[id].typeValue = typeValue;
1842
+ }
1843
+ if (isArray(value)) {
1844
+ if (options.createArrayIndexBasedFns) {
1845
+ for (let i = 0; i < value.length; i++) {
1846
+ addDataTranslationEntry(options, id + "_" + i, locale, value[i], dataTranslations);
1847
+ }
1848
+ }
1849
+ }
1850
+ else {
1851
+ for (const tKey in value) {
1852
+ addDataTranslationEntry(options, id + "_" + normaliseTranslationKey(tKey), locale, value[tKey], dataTranslations);
1853
+ }
1854
+ }
1855
+ }
1856
+ return dataTranslations;
1857
+ };
1858
+ const getCodeDataTranslationsFromFile = (options, file, dataTranslations) => {
1859
+ const { locale, path } = file;
1860
+ const filename = join(dirname(path), basename(path, extname(path)));
1861
+ for (const tKey in file.data) {
1862
+ const tValue = file.data[tKey];
1863
+ const id = normaliseTranslationKey(filename + (tKey ? "_" + tKey : ""));
1864
+ addDataTranslationEntry(options, id, locale, tValue, dataTranslations);
1865
+ }
1866
+ return dataTranslations;
1867
+ };
1868
+ let getCodeDataTranslations = (_config, options, { translationFiles }) => {
1869
+ const { ignorePaths } = options;
1870
+ let dataTranslations = {};
1871
+ for (let i = 0; i < translationFiles.length; i++) {
1872
+ if (!ignorePaths ||
1873
+ (ignorePaths &&
1874
+ ignorePaths.every((glob) => !minimatch(translationFiles[i].path, glob)))) {
1875
+ getCodeDataTranslationsFromFile(options, translationFiles[i], dataTranslations);
1876
+ }
1877
+ }
1878
+ dataTranslations = manageDataTranslationsPlurals(options, dataTranslations);
1879
+ dataTranslations = objectSort(dataTranslations);
1880
+ return dataTranslations;
1881
+ };
1882
+
1883
+ const codeDataOptions = {
1884
+ routes: codeDataRoutesOptions,
1885
+ translations: codeDataTranslationsOptions,
1886
+ };
1887
+ let getCodeData = (config, options, input) => {
1888
+ const optionsSafe = objectMergeWithDefaults(codeDataOptions, options);
1889
+ optionsSafe.translations.ignorePaths.push(optionsSafe.routes.translationJsonFileName);
1890
+ input = {
1891
+ ...input,
1892
+ localesFolders: input.localesFolders.sort((a, b) => config.defaultLocale ? -1 : a.localeCompare(b)),
1893
+ };
1894
+ return {
1895
+ config,
1896
+ options: optionsSafe,
1897
+ input,
1898
+ routes: getCodeDataRoutes(config, optionsSafe.routes, input),
1899
+ translations: getCodeDataTranslations(config, optionsSafe.translations, input),
1900
+ };
1901
+ };
1902
+
1903
+ const configDefaults = {
1904
+ locales: ["en"],
1905
+ defaultLocale: "en",
1906
+ hideDefaultLocaleInUrl: true,
1907
+ };
1908
+ let getConfig = (dataInput, options = {}) => {
1909
+ options.locales = options.locales || dataInput.localesFolders;
1910
+ options.defaultLocale = options.defaultLocale || options.locales?.[0];
1911
+ options.hideDefaultLocaleInUrl = !!options.hideDefaultLocaleInUrl;
1912
+ const merged = objectMergeWithDefaults(configDefaults, options);
1913
+ merged.locales = merged.locales.sort((a, b) => merged.defaultLocale ? -1 : a.localeCompare(b));
1914
+ return merged;
1915
+ };
1916
+
1917
+ const getWriteInputArgs = (options, data) => {
1918
+ const { cwd = process.cwd(), output, pretty } = options;
1919
+ return [
1920
+ join(cwd, output),
1921
+ pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data),
1922
+ ];
1923
+ };
1924
+ let writeInput = async (options, data) => {
1925
+ await fsWrite(...getWriteInputArgs(options, data));
1926
+ };
1927
+
1928
+ const getLocalesFolders = (options) => {
1929
+ const { cwd, ignore } = options;
1930
+ const folders = glob
1931
+ .sync("*", {
1932
+ cwd,
1933
+ withFileTypes: true,
1934
+ ignore: [...ignore, "node_modules/**"],
1935
+ })
1936
+ .filter((folder) => folder.isDirectory())
1937
+ .map((path) => path.relative());
1938
+ return folders.sort((a, b) => a.localeCompare(b));
1939
+ };
1940
+ let getInputDataLocal = async (options) => {
1941
+ const { cwd = process.cwd(), ignore = [], source } = options;
1942
+ const path = join(cwd, source);
1943
+ const localesFolders = getLocalesFolders({ cwd: path, ignore });
1944
+ const translationFiles = [];
1945
+ await Promise.all(localesFolders.map(async (locale) => {
1946
+ const jsonFiles = await glob("**/*.json", {
1947
+ cwd: join(path, locale),
1948
+ ignore: options.ignore,
1949
+ });
1950
+ await Promise.all(jsonFiles.map(async (relativePath) => {
1951
+ const fullPath = join(path, locale, relativePath);
1952
+ const rawContent = await readFile(fullPath, "utf8");
1953
+ if (rawContent) {
1954
+ translationFiles.push({
1955
+ path: relativePath,
1956
+ data: JSON.parse(rawContent),
1957
+ locale: locale,
1958
+ });
1959
+ }
1960
+ }));
1961
+ }));
1962
+ return {
1963
+ localesFolders,
1964
+ translationFiles,
1965
+ };
1966
+ };
1967
+
1968
+ const GITHUB_RAW_URL = "https://raw.githubusercontent.com";
1969
+ let getInputDataRemote = async (options) => new Promise((resolve, reject) => {
1970
+ const { ignore = [], source } = options;
1971
+ const isGithubUrl = source.startsWith(GITHUB_RAW_URL);
1972
+ let result = "";
1973
+ const req = request(source, isGithubUrl
1974
+ ? {}
1975
+ : {
1976
+ headers: {
1977
+ Accept: "application/json",
1978
+ },
1979
+ }, (res) => {
1980
+ res.setEncoding("utf8");
1981
+ res.on("data", (chunk) => {
1982
+ result += chunk;
1983
+ });
1984
+ res.on("end", () => {
1985
+ try {
1986
+ const dataInput = (isGithubUrl ? JSON.parse(result) : result);
1987
+ resolve({
1988
+ ...dataInput,
1989
+ localesFolders: ignore.length
1990
+ ? dataInput.localesFolders.filter((folder) => ignore.every((glob) => !minimatch(folder, glob)))
1991
+ : dataInput.localesFolders,
1992
+ translationFiles: ignore.length
1993
+ ? dataInput.translationFiles.filter((file) => ignore.every((glob) => !minimatch(file.path, glob)))
1994
+ : dataInput.translationFiles,
1995
+ });
1996
+ }
1997
+ catch (e) {
1998
+ throw Error(`Failed to parse JSON from ${source}`);
1999
+ }
2000
+ });
2001
+ });
2002
+ req.on("error", (e) => {
2003
+ console.error(e);
2004
+ reject("");
2005
+ });
2006
+ req.end();
2007
+ });
2008
+
2009
+ let getInputData = async (options) => {
2010
+ const { source } = options;
2011
+ if (isAbsoluteUrl(source)) {
2012
+ return await getInputDataRemote(options);
2013
+ }
2014
+ return await getInputDataLocal(options);
2015
+ };
2016
+
2017
+ const getWords = (value, options = {}) => {
2018
+ let out = "";
2019
+ if (value && typeof value === "string") {
2020
+ out += " " + value.trim();
2021
+ }
2022
+ else if (Array.isArray(value)) {
2023
+ for (let i = 0; i < value.length; i++) {
2024
+ out += getWords(value[i], options);
2025
+ }
2026
+ }
2027
+ else if (typeof value === "object") {
2028
+ for (const _key in value) {
2029
+ const key = _key;
2030
+ const single = value[key];
2031
+ out += getWords(single, options);
2032
+ }
2033
+ }
2034
+ return out;
2035
+ };
2036
+ const getSummaryDataEntry = (sourceUrl, file) => {
2037
+ const { locale, path } = file;
2038
+ const url = `${sourceUrl}/${locale}/${path}`;
2039
+ const words = getWords(file.data);
2040
+ const wordsCount = words.split(" ").filter(Boolean).length;
2041
+ const characters = words.split(" ").filter(Boolean).join("").length;
2042
+ return {
2043
+ characters,
2044
+ locale: locale,
2045
+ path,
2046
+ url,
2047
+ words: wordsCount,
2048
+ };
2049
+ };
2050
+ const summaryDataOptions = {};
2051
+ let getSummaryData = (config, options, { translationFiles }) => {
2052
+ const { defaultLocale } = config;
2053
+ let dataSummary = {};
2054
+ for (let i = 0; i < translationFiles.length; i++) {
2055
+ const file = translationFiles[i];
2056
+ const { locale } = file;
2057
+ const entry = getSummaryDataEntry(options.sourceUrl, file);
2058
+ dataSummary[locale] = dataSummary[locale] || {};
2059
+ dataSummary[locale].files = dataSummary[locale].files || [];
2060
+ dataSummary[locale].files.push(entry);
2061
+ }
2062
+ dataSummary = objectSortByKeysMatching(dataSummary, defaultLocale);
2063
+ forin(dataSummary, (locale, dataPerLocale) => {
2064
+ dataSummary[locale].characters = arraySum(dataPerLocale.files.map((f) => f.characters));
2065
+ dataSummary[locale].files = dataSummary[locale].files.sort((a, b) => a.path.localeCompare(b.path));
2066
+ dataSummary[locale].words = arraySum(dataPerLocale.files.map((f) => f.words));
2067
+ dataSummary[locale] = objectSort(dataSummary[locale]);
2068
+ });
2069
+ return dataSummary;
2070
+ };
2071
+
2072
+ const getSummaryDataByPath = (data) => {
2073
+ let out = {};
2074
+ forin(data, (locale, dataPerLocale) => {
2075
+ const { files } = dataPerLocale;
2076
+ for (let i = 0; i < files.length; i++) {
2077
+ const file = files[i];
2078
+ const { path } = file;
2079
+ out[path] = out[path] || {};
2080
+ out[path][locale] = file;
2081
+ }
2082
+ });
2083
+ out = objectSort(out);
2084
+ return out;
2085
+ };
2086
+ const generateSummaryMarkdownByPath = (data) => {
2087
+ const dataByPath = getSummaryDataByPath(data);
2088
+ let output = "";
2089
+ let body = "";
2090
+ const locales = [];
2091
+ const styleBorder = `style="border-right:1px solid grey"`;
2092
+ forin(dataByPath, (path, dataPerPath) => {
2093
+ body += `<tr>`;
2094
+ body += `<td ${styleBorder}>${path}</td>`;
2095
+ forin(dataPerPath, (locale, file) => {
2096
+ const { characters, words, url } = file;
2097
+ if (!locales.includes(locale))
2098
+ locales.push(locale);
2099
+ body += `<td><a href="${url}">${locale}</a></td>`;
2100
+ body += `<td>${words}</td>`;
2101
+ body += `<td ${styleBorder}>${characters}</td>`;
2102
+ });
2103
+ body += `</tr>`;
2104
+ });
2105
+ output += `<table><thead><tr>`;
2106
+ output += `<th ${styleBorder}>file path</th>`;
2107
+ output += locales
2108
+ .map(() => `<th>lang</th><th>words</th><th ${styleBorder}>chars</th>`)
2109
+ .join("");
2110
+ output += `</tr></thead><tbody>${body}</tbody></table>\n`;
2111
+ return output;
2112
+ };
2113
+ const generateSummaryMarkdownByLocale = (data, options) => {
2114
+ let output = "";
2115
+ let body = "";
2116
+ forin(data, (locale, dataPerLocale) => {
2117
+ const { files, characters, words } = dataPerLocale;
2118
+ const url = `${options.sourceUrl}/${locale}`;
2119
+ body += `<tr>`;
2120
+ body += `<th><a href="${url}">${locale}</a></th>`;
2121
+ body += `<td>${files.length}</td>`;
2122
+ body += `<td>${words}</td>`;
2123
+ body += `<td>${characters}</td>`;
2124
+ body += `</tr>`;
2125
+ });
2126
+ output += `<table><thead><tr>`;
2127
+ output += `<th>locale</th><th>files</th><th>words</th><th>chars</th>`;
2128
+ output += `</tr></thead><tbody>${body}</tbody></table>\n`;
2129
+ return output;
2130
+ };
2131
+ const generateSummaryMarkdown = (data, options) => {
2132
+ let output = "# Summary\n";
2133
+ output += "\n### By locale\n\n";
2134
+ output += generateSummaryMarkdownByLocale(data, options);
2135
+ output += "\n### By file path\n\n";
2136
+ output += generateSummaryMarkdownByPath(data);
2137
+ return output;
2138
+ };
2139
+ let generateSummary = (data, options) => {
2140
+ const markdown = generateSummaryMarkdown(data, objectMergeWithDefaults(summaryDataOptions, options));
2141
+ return markdown;
2142
+ };
2143
+
2144
+ const getWriteSummaryJsonArgs = (options, data, cwd, outputJson) => [
2145
+ join(cwd, outputJson),
2146
+ options.pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data),
2147
+ ];
2148
+ const getWriteSummaryMdArgs = (options, data, cwd, outputMarkdown) => [join(cwd, outputMarkdown), generateSummary(data, options)];
2149
+ let writeSummary = async (options, data) => {
2150
+ const { cwd = process.cwd(), outputJson, outputMarkdown, ...rest } = options;
2151
+ if (outputJson) {
2152
+ await fsWrite(...getWriteSummaryJsonArgs(options, data, cwd, outputJson));
2153
+ }
2154
+ if (outputMarkdown) {
2155
+ await fsWrite(...getWriteSummaryMdArgs(rest, data, cwd, outputMarkdown));
2156
+ }
2157
+ return data;
2158
+ };
2159
+
2160
+ let i18nCompiler = async (options) => {
2161
+ const { input: optsInput, code: optsCode, summary: optsSummary, ...configOptions } = options;
2162
+ const writables = [];
2163
+ const input = await getInputData(optsInput);
2164
+ const config = getConfig(input, configOptions);
2165
+ const code = getCodeData(config, optsCode, input);
2166
+ if (optsInput?.write) {
2167
+ writables.push(writeInput(optsInput.write, input));
2168
+ }
2169
+ if (optsCode?.write) {
2170
+ writables.push(writeCode({ ...optsCode, ...optsCode.write }, code));
2171
+ }
2172
+ if (optsSummary?.write) {
2173
+ writables.push(writeSummary({ ...optsSummary, ...optsSummary.write }, getSummaryData(config, optsSummary, input)));
2174
+ }
2175
+ await Promise.all(writables);
2176
+ return { config, input, code };
2177
+ };
2178
+
2179
+ export { generateRewrites as a, generateRedirects as g, i18nCompiler as i };