@depup/next-intl 4.8.3-depup.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +33 -0
  3. package/changes.json +18 -0
  4. package/config.d.ts +4 -0
  5. package/dist/cjs/development/ExtractorCodec-D9Tw618d.cjs +7 -0
  6. package/dist/cjs/development/JSONCodec-rhejs716.cjs +50 -0
  7. package/dist/cjs/development/POCodec-Cv3VdXQG.cjs +105 -0
  8. package/dist/cjs/development/plugin-YzBhQjAo.cjs +1457 -0
  9. package/dist/cjs/development/plugin.cjs +13 -0
  10. package/dist/esm/development/config.js +5 -0
  11. package/dist/esm/development/extractor/ExtractionCompiler.js +38 -0
  12. package/dist/esm/development/extractor/catalog/CatalogLocales.js +84 -0
  13. package/dist/esm/development/extractor/catalog/CatalogManager.js +370 -0
  14. package/dist/esm/development/extractor/catalog/CatalogPersister.js +63 -0
  15. package/dist/esm/development/extractor/catalog/SaveScheduler.js +85 -0
  16. package/dist/esm/development/extractor/catalogLoader.js +96 -0
  17. package/dist/esm/development/extractor/extractMessages.js +15 -0
  18. package/dist/esm/development/extractor/extractionLoader.js +26 -0
  19. package/dist/esm/development/extractor/extractor/LRUCache.js +30 -0
  20. package/dist/esm/development/extractor/extractor/MessageExtractor.js +66 -0
  21. package/dist/esm/development/extractor/format/ExtractorCodec.js +5 -0
  22. package/dist/esm/development/extractor/format/codecs/JSONCodec.js +42 -0
  23. package/dist/esm/development/extractor/format/codecs/POCodec.js +93 -0
  24. package/dist/esm/development/extractor/format/index.js +44 -0
  25. package/dist/esm/development/extractor/source/SourceFileFilter.js +29 -0
  26. package/dist/esm/development/extractor/source/SourceFileScanner.js +31 -0
  27. package/dist/esm/development/extractor/source/SourceFileWatcher.js +132 -0
  28. package/dist/esm/development/extractor/utils.js +46 -0
  29. package/dist/esm/development/extractor.js +2 -0
  30. package/dist/esm/development/index.react-client.js +4 -0
  31. package/dist/esm/development/index.react-server.js +9 -0
  32. package/dist/esm/development/middleware/getAlternateLinksHeaderValue.js +91 -0
  33. package/dist/esm/development/middleware/middleware.js +168 -0
  34. package/dist/esm/development/middleware/resolveLocale.js +129 -0
  35. package/dist/esm/development/middleware/syncCookie.js +27 -0
  36. package/dist/esm/development/middleware/utils.js +194 -0
  37. package/dist/esm/development/middleware.js +1 -0
  38. package/dist/esm/development/navigation/react-client/createNavigation.js +80 -0
  39. package/dist/esm/development/navigation/react-client/useBasePathname.js +36 -0
  40. package/dist/esm/development/navigation/react-server/createNavigation.js +22 -0
  41. package/dist/esm/development/navigation/react-server/getServerLocale.js +12 -0
  42. package/dist/esm/development/navigation/shared/BaseLink.js +51 -0
  43. package/dist/esm/development/navigation/shared/createSharedNavigationFns.js +105 -0
  44. package/dist/esm/development/navigation/shared/syncLocaleCookie.js +47 -0
  45. package/dist/esm/development/navigation/shared/utils.js +165 -0
  46. package/dist/esm/development/navigation.react-client.js +1 -0
  47. package/dist/esm/development/navigation.react-server.js +1 -0
  48. package/dist/esm/development/plugin/config.js +10 -0
  49. package/dist/esm/development/plugin/createNextIntlPlugin.js +26 -0
  50. package/dist/esm/development/plugin/declaration/createMessagesDeclaration.js +70 -0
  51. package/dist/esm/development/plugin/extractor/initExtractionCompiler.js +59 -0
  52. package/dist/esm/development/plugin/getNextConfig.js +267 -0
  53. package/dist/esm/development/plugin/nextFlags.js +32 -0
  54. package/dist/esm/development/plugin/utils.js +26 -0
  55. package/dist/esm/development/plugin/watchFile.js +21 -0
  56. package/dist/esm/development/plugin.js +1 -0
  57. package/dist/esm/development/react-client/index.js +34 -0
  58. package/dist/esm/development/react-server/NextIntlClientProviderServer.js +34 -0
  59. package/dist/esm/development/react-server/useConfig.js +21 -0
  60. package/dist/esm/development/react-server/useExtracted.js +9 -0
  61. package/dist/esm/development/react-server/useFormatter.js +9 -0
  62. package/dist/esm/development/react-server/useLocale.js +8 -0
  63. package/dist/esm/development/react-server/useMessages.js +9 -0
  64. package/dist/esm/development/react-server/useNow.js +12 -0
  65. package/dist/esm/development/react-server/useTimeZone.js +8 -0
  66. package/dist/esm/development/react-server/useTranslations.js +9 -0
  67. package/dist/esm/development/routing/config.js +26 -0
  68. package/dist/esm/development/routing/defineRouting.js +25 -0
  69. package/dist/esm/development/routing.js +1 -0
  70. package/dist/esm/development/server/react-client/index.js +32 -0
  71. package/dist/esm/development/server/react-server/RequestLocale.js +36 -0
  72. package/dist/esm/development/server/react-server/RequestLocaleCache.js +18 -0
  73. package/dist/esm/development/server/react-server/createRequestConfig.js +2 -0
  74. package/dist/esm/development/server/react-server/getConfig.js +59 -0
  75. package/dist/esm/development/server/react-server/getConfigNow.js +10 -0
  76. package/dist/esm/development/server/react-server/getDefaultNow.js +9 -0
  77. package/dist/esm/development/server/react-server/getExtracted.js +24 -0
  78. package/dist/esm/development/server/react-server/getFormats.js +10 -0
  79. package/dist/esm/development/server/react-server/getFormatter.js +21 -0
  80. package/dist/esm/development/server/react-server/getLocale.js +10 -0
  81. package/dist/esm/development/server/react-server/getMessages.js +19 -0
  82. package/dist/esm/development/server/react-server/getNow.js +8 -0
  83. package/dist/esm/development/server/react-server/getRequestConfig.js +8 -0
  84. package/dist/esm/development/server/react-server/getServerExtractor.js +38 -0
  85. package/dist/esm/development/server/react-server/getServerFormatter.js +17 -0
  86. package/dist/esm/development/server/react-server/getServerTranslator.js +12 -0
  87. package/dist/esm/development/server/react-server/getTimeZone.js +13 -0
  88. package/dist/esm/development/server/react-server/getTranslations.js +28 -0
  89. package/dist/esm/development/server/react-server/validateLocale.js +12 -0
  90. package/dist/esm/development/server.react-client.js +1 -0
  91. package/dist/esm/development/server.react-server.js +9 -0
  92. package/dist/esm/development/shared/NextIntlClientProvider.js +18 -0
  93. package/dist/esm/development/shared/constants.js +4 -0
  94. package/dist/esm/development/shared/use.js +13 -0
  95. package/dist/esm/development/shared/utils.js +140 -0
  96. package/dist/esm/production/config.js +1 -0
  97. package/dist/esm/production/extractor/ExtractionCompiler.js +1 -0
  98. package/dist/esm/production/extractor/catalog/CatalogLocales.js +1 -0
  99. package/dist/esm/production/extractor/catalog/CatalogManager.js +1 -0
  100. package/dist/esm/production/extractor/catalog/CatalogPersister.js +1 -0
  101. package/dist/esm/production/extractor/catalog/SaveScheduler.js +1 -0
  102. package/dist/esm/production/extractor/catalogLoader.js +1 -0
  103. package/dist/esm/production/extractor/extractMessages.js +1 -0
  104. package/dist/esm/production/extractor/extractionLoader.js +1 -0
  105. package/dist/esm/production/extractor/extractor/LRUCache.js +1 -0
  106. package/dist/esm/production/extractor/extractor/MessageExtractor.js +1 -0
  107. package/dist/esm/production/extractor/format/ExtractorCodec.js +1 -0
  108. package/dist/esm/production/extractor/format/codecs/JSONCodec.js +1 -0
  109. package/dist/esm/production/extractor/format/codecs/POCodec.js +1 -0
  110. package/dist/esm/production/extractor/format/index.js +1 -0
  111. package/dist/esm/production/extractor/source/SourceFileFilter.js +1 -0
  112. package/dist/esm/production/extractor/source/SourceFileScanner.js +1 -0
  113. package/dist/esm/production/extractor/source/SourceFileWatcher.js +1 -0
  114. package/dist/esm/production/extractor/utils.js +1 -0
  115. package/dist/esm/production/extractor.js +1 -0
  116. package/dist/esm/production/index.react-client.js +1 -0
  117. package/dist/esm/production/index.react-server.js +1 -0
  118. package/dist/esm/production/middleware/getAlternateLinksHeaderValue.js +1 -0
  119. package/dist/esm/production/middleware/middleware.js +1 -0
  120. package/dist/esm/production/middleware/resolveLocale.js +1 -0
  121. package/dist/esm/production/middleware/syncCookie.js +1 -0
  122. package/dist/esm/production/middleware/utils.js +1 -0
  123. package/dist/esm/production/middleware.js +1 -0
  124. package/dist/esm/production/navigation/react-client/createNavigation.js +1 -0
  125. package/dist/esm/production/navigation/react-client/useBasePathname.js +1 -0
  126. package/dist/esm/production/navigation/react-server/createNavigation.js +1 -0
  127. package/dist/esm/production/navigation/react-server/getServerLocale.js +1 -0
  128. package/dist/esm/production/navigation/shared/BaseLink.js +2 -0
  129. package/dist/esm/production/navigation/shared/createSharedNavigationFns.js +1 -0
  130. package/dist/esm/production/navigation/shared/syncLocaleCookie.js +1 -0
  131. package/dist/esm/production/navigation/shared/utils.js +1 -0
  132. package/dist/esm/production/navigation.react-client.js +1 -0
  133. package/dist/esm/production/navigation.react-server.js +1 -0
  134. package/dist/esm/production/plugin/config.js +1 -0
  135. package/dist/esm/production/plugin/createNextIntlPlugin.js +1 -0
  136. package/dist/esm/production/plugin/declaration/createMessagesDeclaration.js +1 -0
  137. package/dist/esm/production/plugin/extractor/initExtractionCompiler.js +1 -0
  138. package/dist/esm/production/plugin/getNextConfig.js +1 -0
  139. package/dist/esm/production/plugin/nextFlags.js +1 -0
  140. package/dist/esm/production/plugin/utils.js +1 -0
  141. package/dist/esm/production/plugin/watchFile.js +1 -0
  142. package/dist/esm/production/plugin.js +1 -0
  143. package/dist/esm/production/react-client/index.js +1 -0
  144. package/dist/esm/production/react-server/NextIntlClientProviderServer.js +1 -0
  145. package/dist/esm/production/react-server/useConfig.js +1 -0
  146. package/dist/esm/production/react-server/useExtracted.js +1 -0
  147. package/dist/esm/production/react-server/useFormatter.js +1 -0
  148. package/dist/esm/production/react-server/useLocale.js +1 -0
  149. package/dist/esm/production/react-server/useMessages.js +1 -0
  150. package/dist/esm/production/react-server/useNow.js +1 -0
  151. package/dist/esm/production/react-server/useTimeZone.js +1 -0
  152. package/dist/esm/production/react-server/useTranslations.js +1 -0
  153. package/dist/esm/production/routing/config.js +1 -0
  154. package/dist/esm/production/routing/defineRouting.js +1 -0
  155. package/dist/esm/production/routing.js +1 -0
  156. package/dist/esm/production/server/react-client/index.js +1 -0
  157. package/dist/esm/production/server/react-server/RequestLocale.js +1 -0
  158. package/dist/esm/production/server/react-server/RequestLocaleCache.js +1 -0
  159. package/dist/esm/production/server/react-server/createRequestConfig.js +1 -0
  160. package/dist/esm/production/server/react-server/getConfig.js +1 -0
  161. package/dist/esm/production/server/react-server/getConfigNow.js +1 -0
  162. package/dist/esm/production/server/react-server/getDefaultNow.js +1 -0
  163. package/dist/esm/production/server/react-server/getExtracted.js +1 -0
  164. package/dist/esm/production/server/react-server/getFormats.js +1 -0
  165. package/dist/esm/production/server/react-server/getFormatter.js +1 -0
  166. package/dist/esm/production/server/react-server/getLocale.js +1 -0
  167. package/dist/esm/production/server/react-server/getMessages.js +1 -0
  168. package/dist/esm/production/server/react-server/getNow.js +1 -0
  169. package/dist/esm/production/server/react-server/getRequestConfig.js +1 -0
  170. package/dist/esm/production/server/react-server/getServerExtractor.js +1 -0
  171. package/dist/esm/production/server/react-server/getServerFormatter.js +1 -0
  172. package/dist/esm/production/server/react-server/getServerTranslator.js +1 -0
  173. package/dist/esm/production/server/react-server/getTimeZone.js +1 -0
  174. package/dist/esm/production/server/react-server/getTranslations.js +1 -0
  175. package/dist/esm/production/server.react-client.js +1 -0
  176. package/dist/esm/production/server.react-server.js +1 -0
  177. package/dist/esm/production/shared/NextIntlClientProvider.js +2 -0
  178. package/dist/esm/production/shared/constants.js +1 -0
  179. package/dist/esm/production/shared/use.js +1 -0
  180. package/dist/esm/production/shared/utils.js +1 -0
  181. package/dist/types/config.d.ts +1 -0
  182. package/dist/types/extractor/ExtractionCompiler.d.ts +15 -0
  183. package/dist/types/extractor/catalog/CatalogLocales.d.ts +29 -0
  184. package/dist/types/extractor/catalog/CatalogManager.d.ts +59 -0
  185. package/dist/types/extractor/catalog/CatalogPersister.d.ts +20 -0
  186. package/dist/types/extractor/catalog/SaveScheduler.d.ts +18 -0
  187. package/dist/types/extractor/extractMessages.d.ts +2 -0
  188. package/dist/types/extractor/extractor/KeyGenerator.d.ts +3 -0
  189. package/dist/types/extractor/extractor/LRUCache.d.ts +7 -0
  190. package/dist/types/extractor/extractor/MessageExtractor.bench.d.ts +1 -0
  191. package/dist/types/extractor/extractor/MessageExtractor.d.ts +21 -0
  192. package/dist/types/extractor/format/ExtractorCodec.d.ts +33 -0
  193. package/dist/types/extractor/format/codecs/JSONCodec.d.ts +2 -0
  194. package/dist/types/extractor/format/codecs/POCodec.d.ts +2 -0
  195. package/dist/types/extractor/format/codecs/fixtures/JSONCodecStructured.d.ts +2 -0
  196. package/dist/types/extractor/format/codecs/fixtures/POCodecSourceMessageKey.d.ts +2 -0
  197. package/dist/types/extractor/format/index.d.ts +15 -0
  198. package/dist/types/extractor/format/types.d.ts +8 -0
  199. package/dist/types/extractor/index.d.ts +2 -0
  200. package/dist/types/extractor/source/SourceFileFilter.d.ts +8 -0
  201. package/dist/types/extractor/source/SourceFileScanner.d.ts +4 -0
  202. package/dist/types/extractor/source/SourceFileWatcher.d.ts +15 -0
  203. package/dist/types/extractor/types.d.ts +28 -0
  204. package/dist/types/extractor/utils.d.ts +7 -0
  205. package/dist/types/extractor.d.ts +1 -0
  206. package/dist/types/index.react-client.d.ts +8 -0
  207. package/dist/types/index.react-server.d.ts +1 -0
  208. package/dist/types/middleware/getAlternateLinksHeaderValue.d.ts +13 -0
  209. package/dist/types/middleware/index.d.ts +4 -0
  210. package/dist/types/middleware/middleware.d.ts +4 -0
  211. package/dist/types/middleware/resolveLocale.d.ts +9 -0
  212. package/dist/types/middleware/syncCookie.d.ts +7 -0
  213. package/dist/types/middleware/utils.d.ts +26 -0
  214. package/dist/types/middleware.d.ts +1 -0
  215. package/dist/types/navigation/react-client/createNavigation.d.ts +464 -0
  216. package/dist/types/navigation/react-client/index.d.ts +2 -0
  217. package/dist/types/navigation/react-client/useBasePathname.d.ts +5 -0
  218. package/dist/types/navigation/react-server/createNavigation.d.ts +384 -0
  219. package/dist/types/navigation/react-server/getServerLocale.d.ts +5 -0
  220. package/dist/types/navigation/react-server/index.d.ts +2 -0
  221. package/dist/types/navigation/shared/BaseLink.d.ts +11 -0
  222. package/dist/types/navigation/shared/StrictParams.d.ts +8 -0
  223. package/dist/types/navigation/shared/createSharedNavigationFns.d.ts +370 -0
  224. package/dist/types/navigation/shared/syncLocaleCookie.d.ts +8 -0
  225. package/dist/types/navigation/shared/utils.d.ts +53 -0
  226. package/dist/types/navigation.react-client.d.ts +1 -0
  227. package/dist/types/navigation.react-server.d.ts +1 -0
  228. package/dist/types/plugin/catalog/catalogLoader.d.ts +10 -0
  229. package/dist/types/plugin/config.d.ts +3 -0
  230. package/dist/types/plugin/createNextIntlPlugin.d.ts +3 -0
  231. package/dist/types/plugin/declaration/createMessagesDeclaration.d.ts +1 -0
  232. package/dist/types/plugin/declaration/index.d.ts +1 -0
  233. package/dist/types/plugin/extractor/extractionLoader.d.ts +3 -0
  234. package/dist/types/plugin/extractor/initExtractionCompiler.d.ts +2 -0
  235. package/dist/types/plugin/getNextConfig.d.ts +3 -0
  236. package/dist/types/plugin/index.d.ts +1 -0
  237. package/dist/types/plugin/nextFlags.d.ts +2 -0
  238. package/dist/types/plugin/types.d.ts +29 -0
  239. package/dist/types/plugin/utils.d.ts +8 -0
  240. package/dist/types/plugin/watchFile.d.ts +6 -0
  241. package/dist/types/plugin.d.ts +1 -0
  242. package/dist/types/react-client/index.d.ts +16 -0
  243. package/dist/types/react-server/NextIntlClientProviderServer.d.ts +5 -0
  244. package/dist/types/react-server/index.d.ts +16 -0
  245. package/dist/types/react-server/testUtils.d.ts +2 -0
  246. package/dist/types/react-server/useConfig.d.ts +2 -0
  247. package/dist/types/react-server/useExtracted.d.ts +2 -0
  248. package/dist/types/react-server/useFormatter.d.ts +2 -0
  249. package/dist/types/react-server/useLocale.d.ts +2 -0
  250. package/dist/types/react-server/useMessages.d.ts +2 -0
  251. package/dist/types/react-server/useNow.d.ts +2 -0
  252. package/dist/types/react-server/useTimeZone.d.ts +2 -0
  253. package/dist/types/react-server/useTranslations.d.ts +2 -0
  254. package/dist/types/routing/config.d.ts +73 -0
  255. package/dist/types/routing/defineRouting.d.ts +3 -0
  256. package/dist/types/routing/index.d.ts +3 -0
  257. package/dist/types/routing/types.d.ts +24 -0
  258. package/dist/types/routing.d.ts +1 -0
  259. package/dist/types/server/react-client/index.d.ts +10 -0
  260. package/dist/types/server/react-server/RequestLocale.d.ts +1 -0
  261. package/dist/types/server/react-server/RequestLocaleCache.d.ts +3 -0
  262. package/dist/types/server/react-server/createRequestConfig.d.ts +3 -0
  263. package/dist/types/server/react-server/getConfig.d.ts +13 -0
  264. package/dist/types/server/react-server/getConfigNow.d.ts +4 -0
  265. package/dist/types/server/react-server/getDefaultNow.d.ts +3 -0
  266. package/dist/types/server/react-server/getExtracted.d.ts +10 -0
  267. package/dist/types/server/react-server/getFormats.d.ts +3 -0
  268. package/dist/types/server/react-server/getFormatter.d.ts +10 -0
  269. package/dist/types/server/react-server/getLocale.d.ts +4 -0
  270. package/dist/types/server/react-server/getMessages.d.ts +6 -0
  271. package/dist/types/server/react-server/getNow.d.ts +4 -0
  272. package/dist/types/server/react-server/getRequestConfig.d.ts +35 -0
  273. package/dist/types/server/react-server/getServerExtractor.d.ts +5 -0
  274. package/dist/types/server/react-server/getServerFormatter.d.ts +25 -0
  275. package/dist/types/server/react-server/getServerTranslator.d.ts +4 -0
  276. package/dist/types/server/react-server/getTimeZone.d.ts +4 -0
  277. package/dist/types/server/react-server/getTranslations.d.ts +8 -0
  278. package/dist/types/server/react-server/index.d.ts +12 -0
  279. package/dist/types/server/react-server/validateLocale.d.ts +1 -0
  280. package/dist/types/server.react-client.d.ts +1 -0
  281. package/dist/types/server.react-server.d.ts +1 -0
  282. package/dist/types/shared/NextIntlClientProvider.d.ts +9 -0
  283. package/dist/types/shared/constants.d.ts +1 -0
  284. package/dist/types/shared/types.d.ts +5 -0
  285. package/dist/types/shared/use.d.ts +3 -0
  286. package/dist/types/shared/utils.d.ts +20 -0
  287. package/extractor/catalogLoader.d.ts +4 -0
  288. package/extractor/extractionLoader.d.ts +4 -0
  289. package/extractor.d.ts +2 -0
  290. package/middleware.d.ts +4 -0
  291. package/navigation.d.ts +2 -0
  292. package/package.json +175 -0
  293. package/plugin.d.cts +3 -0
  294. package/plugin.d.ts +4 -0
  295. package/routing.d.ts +2 -0
  296. package/server.d.ts +2 -0
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ var plugin = require('./plugin-YzBhQjAo.cjs');
4
+ require('fs/promises');
5
+ require('path');
6
+ require('@parcel/watcher');
7
+ require('fs');
8
+ require('module');
9
+ require('@swc/core');
10
+
11
+
12
+
13
+ module.exports = plugin.createNextIntlPlugin;
@@ -0,0 +1,5 @@
1
+ function getConfig() {
2
+ throw new Error("Couldn't find next-intl config file. Please follow the instructions at https://next-intl.dev/docs/getting-started/app-router");
3
+ }
4
+
5
+ export { getConfig as default };
@@ -0,0 +1,38 @@
1
+ import CatalogManager from './catalog/CatalogManager.js';
2
+ import MessageExtractor from './extractor/MessageExtractor.js';
3
+
4
+ class ExtractionCompiler {
5
+ constructor(config, opts = {}) {
6
+ const extractor = opts.extractor ?? new MessageExtractor(opts);
7
+ this.manager = new CatalogManager(config, {
8
+ ...opts,
9
+ extractor
10
+ });
11
+ this[Symbol.dispose] = this[Symbol.dispose].bind(this);
12
+ this.installExitHandlers();
13
+ }
14
+ async extractAll() {
15
+ // We can't rely on all files being compiled (e.g. due to persistent
16
+ // caching), so loading the messages initially is necessary.
17
+ await this.manager.loadMessages();
18
+ await this.manager.save();
19
+ }
20
+ [Symbol.dispose]() {
21
+ this.uninstallExitHandlers();
22
+ this.manager[Symbol.dispose]();
23
+ }
24
+ installExitHandlers() {
25
+ const cleanup = this[Symbol.dispose];
26
+ process.on('exit', cleanup);
27
+ process.on('SIGINT', cleanup);
28
+ process.on('SIGTERM', cleanup);
29
+ }
30
+ uninstallExitHandlers() {
31
+ const cleanup = this[Symbol.dispose];
32
+ process.off('exit', cleanup);
33
+ process.off('SIGINT', cleanup);
34
+ process.off('SIGTERM', cleanup);
35
+ }
36
+ }
37
+
38
+ export { ExtractionCompiler as default };
@@ -0,0 +1,84 @@
1
+ import fs$1 from 'fs';
2
+ import fs from 'fs/promises';
3
+ import path from 'path';
4
+
5
+ class CatalogLocales {
6
+ onChangeCallbacks = new Set();
7
+ constructor(params) {
8
+ this.messagesDir = params.messagesDir;
9
+ this.sourceLocale = params.sourceLocale;
10
+ this.extension = params.extension;
11
+ this.locales = params.locales;
12
+ }
13
+ async getTargetLocales() {
14
+ if (this.targetLocales) {
15
+ return this.targetLocales;
16
+ }
17
+ if (this.locales === 'infer') {
18
+ this.targetLocales = await this.readTargetLocales();
19
+ } else {
20
+ this.targetLocales = this.locales.filter(locale => locale !== this.sourceLocale);
21
+ }
22
+ return this.targetLocales;
23
+ }
24
+ async readTargetLocales() {
25
+ try {
26
+ const files = await fs.readdir(this.messagesDir);
27
+ return files.filter(file => file.endsWith(this.extension)).map(file => path.basename(file, this.extension)).filter(locale => locale !== this.sourceLocale);
28
+ } catch {
29
+ return [];
30
+ }
31
+ }
32
+ subscribeLocalesChange(callback) {
33
+ this.onChangeCallbacks.add(callback);
34
+ if (this.locales === 'infer' && !this.watcher) {
35
+ void this.startWatcher();
36
+ }
37
+ }
38
+ unsubscribeLocalesChange(callback) {
39
+ this.onChangeCallbacks.delete(callback);
40
+ if (this.onChangeCallbacks.size === 0) {
41
+ this.stopWatcher();
42
+ }
43
+ }
44
+ async startWatcher() {
45
+ if (this.watcher) {
46
+ return;
47
+ }
48
+ await fs.mkdir(this.messagesDir, {
49
+ recursive: true
50
+ });
51
+ this.watcher = fs$1.watch(this.messagesDir, {
52
+ persistent: false,
53
+ recursive: false
54
+ }, (event, filename) => {
55
+ const isCatalogFile = filename != null && filename.endsWith(this.extension) && !filename.includes(path.sep);
56
+ if (isCatalogFile) {
57
+ void this.onChange();
58
+ }
59
+ });
60
+ }
61
+ stopWatcher() {
62
+ if (this.watcher) {
63
+ this.watcher.close();
64
+ this.watcher = undefined;
65
+ }
66
+ }
67
+ async onChange() {
68
+ const oldLocales = new Set(this.targetLocales || []);
69
+ this.targetLocales = await this.readTargetLocales();
70
+ const newLocalesSet = new Set(this.targetLocales);
71
+ const added = this.targetLocales.filter(locale => !oldLocales.has(locale));
72
+ const removed = Array.from(oldLocales).filter(locale => !newLocalesSet.has(locale));
73
+ if (added.length > 0 || removed.length > 0) {
74
+ for (const callback of this.onChangeCallbacks) {
75
+ callback({
76
+ added,
77
+ removed
78
+ });
79
+ }
80
+ }
81
+ }
82
+ }
83
+
84
+ export { CatalogLocales as default };
@@ -0,0 +1,370 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { resolveCodec, getFormatExtension } from '../format/index.js';
4
+ import SourceFileScanner from '../source/SourceFileScanner.js';
5
+ import SourceFileWatcher from '../source/SourceFileWatcher.js';
6
+ import { getDefaultProjectRoot, normalizePathToPosix, compareReferences } from '../utils.js';
7
+ import CatalogLocales from './CatalogLocales.js';
8
+ import CatalogPersister from './CatalogPersister.js';
9
+ import SaveScheduler from './SaveScheduler.js';
10
+
11
+ class CatalogManager {
12
+ /**
13
+ * The source of truth for which messages are used.
14
+ * NOTE: Should be mutated in place to keep `messagesById` and `messagesByFile` in sync.
15
+ */
16
+ messagesByFile = new Map();
17
+
18
+ /**
19
+ * Fast lookup for messages by ID across all files,
20
+ * contains the same messages as `messagesByFile`.
21
+ * NOTE: Should be mutated in place to keep `messagesById` and `messagesByFile` in sync.
22
+ */
23
+ messagesById = new Map();
24
+
25
+ /**
26
+ * This potentially also includes outdated ones that were initially available,
27
+ * but are not used anymore. This allows to restore them if they are used again.
28
+ **/
29
+ translationsByTargetLocale = new Map();
30
+ lastWriteByLocale = new Map();
31
+
32
+ // Cached instances
33
+
34
+ // Resolves when all catalogs are loaded
35
+
36
+ // Resolves when the initial project scan and processing is complete
37
+
38
+ constructor(config, opts) {
39
+ this.config = config;
40
+ this.saveScheduler = new SaveScheduler(50);
41
+ this.projectRoot = opts.projectRoot ?? getDefaultProjectRoot();
42
+ this.isDevelopment = opts.isDevelopment ?? false;
43
+ this.extractor = opts.extractor;
44
+ if (this.isDevelopment) {
45
+ // We kick this off as early as possible, so we get notified about changes
46
+ // that happen during the initial project scan (while awaiting it to
47
+ // complete though)
48
+ this.sourceWatcher = new SourceFileWatcher(this.getSrcPaths(), this.handleFileEvents.bind(this));
49
+ void this.sourceWatcher.start();
50
+ }
51
+ }
52
+ async getCodec() {
53
+ if (!this.codec) {
54
+ this.codec = await resolveCodec(this.config.messages.format, this.projectRoot);
55
+ }
56
+ return this.codec;
57
+ }
58
+ async getPersister() {
59
+ if (this.persister) {
60
+ return this.persister;
61
+ } else {
62
+ this.persister = new CatalogPersister({
63
+ messagesPath: this.config.messages.path,
64
+ codec: await this.getCodec(),
65
+ extension: getFormatExtension(this.config.messages.format)
66
+ });
67
+ return this.persister;
68
+ }
69
+ }
70
+ getCatalogLocales() {
71
+ if (this.catalogLocales) {
72
+ return this.catalogLocales;
73
+ } else {
74
+ const messagesDir = path.join(this.projectRoot, this.config.messages.path);
75
+ this.catalogLocales = new CatalogLocales({
76
+ messagesDir,
77
+ sourceLocale: this.config.sourceLocale,
78
+ extension: getFormatExtension(this.config.messages.format),
79
+ locales: this.config.messages.locales
80
+ });
81
+ return this.catalogLocales;
82
+ }
83
+ }
84
+ async getTargetLocales() {
85
+ return this.getCatalogLocales().getTargetLocales();
86
+ }
87
+ getSrcPaths() {
88
+ return (Array.isArray(this.config.srcPath) ? this.config.srcPath : [this.config.srcPath]).map(srcPath => path.join(this.projectRoot, srcPath));
89
+ }
90
+ async loadMessages() {
91
+ const sourceDiskMessages = await this.loadSourceMessages();
92
+ this.loadCatalogsPromise = this.loadTargetMessages();
93
+ await this.loadCatalogsPromise;
94
+ this.scanCompletePromise = (async () => {
95
+ const sourceFiles = await SourceFileScanner.getSourceFiles(this.getSrcPaths());
96
+ await Promise.all(Array.from(sourceFiles).map(async filePath => this.processFile(filePath)));
97
+ this.mergeSourceDiskMetadata(sourceDiskMessages);
98
+ })();
99
+ await this.scanCompletePromise;
100
+ if (this.isDevelopment) {
101
+ const catalogLocales = this.getCatalogLocales();
102
+ catalogLocales.subscribeLocalesChange(this.onLocalesChange);
103
+ }
104
+ }
105
+ async loadSourceMessages() {
106
+ // Load source catalog to hydrate metadata (e.g. flags) later without
107
+ // treating catalog entries as source of truth.
108
+ const diskMessages = await this.loadLocaleMessages(this.config.sourceLocale);
109
+ const byId = new Map();
110
+ for (const diskMessage of diskMessages) {
111
+ byId.set(diskMessage.id, diskMessage);
112
+ }
113
+ return byId;
114
+ }
115
+ async loadLocaleMessages(locale) {
116
+ const persister = await this.getPersister();
117
+ const messages = await persister.read(locale);
118
+ const fileTime = await persister.getLastModified(locale);
119
+ this.lastWriteByLocale.set(locale, fileTime);
120
+ return messages;
121
+ }
122
+ async loadTargetMessages() {
123
+ const targetLocales = await this.getTargetLocales();
124
+ await Promise.all(targetLocales.map(locale => this.reloadLocaleCatalog(locale)));
125
+ }
126
+ async reloadLocaleCatalog(locale) {
127
+ const diskMessages = await this.loadLocaleMessages(locale);
128
+ if (locale === this.config.sourceLocale) {
129
+ // For source: Merge additional properties like flags
130
+ for (const diskMessage of diskMessages) {
131
+ const prev = this.messagesById.get(diskMessage.id);
132
+ if (prev) {
133
+ // Mutate the existing object instead of creating a copy
134
+ // to keep messagesById and messagesByFile in sync.
135
+ // Unknown properties (like flags): disk wins
136
+ // Known properties: existing (from extraction) wins
137
+ for (const key of Object.keys(diskMessage)) {
138
+ if (!['id', 'message', 'description', 'references'].includes(key)) {
139
+ // For unknown properties (like flags), disk wins
140
+ prev[key] = diskMessage[key];
141
+ }
142
+ }
143
+ }
144
+ }
145
+ } else {
146
+ // For target: disk wins completely, BUT preserve existing translations
147
+ // if we read empty (likely a write in progress by an external tool
148
+ // that causes the file to temporarily be empty)
149
+ const existingTranslations = this.translationsByTargetLocale.get(locale);
150
+ const hasExistingTranslations = existingTranslations && existingTranslations.size > 0;
151
+ if (diskMessages.length > 0) {
152
+ // We got content from disk, replace with it
153
+ const translations = new Map();
154
+ for (const message of diskMessages) {
155
+ translations.set(message.id, message);
156
+ }
157
+ this.translationsByTargetLocale.set(locale, translations);
158
+ } else if (hasExistingTranslations) ; else {
159
+ // We read empty and have no existing translations
160
+ const translations = new Map();
161
+ this.translationsByTargetLocale.set(locale, translations);
162
+ }
163
+ }
164
+ }
165
+ mergeSourceDiskMetadata(diskMessages) {
166
+ for (const [id, diskMessage] of diskMessages) {
167
+ const existing = this.messagesById.get(id);
168
+ if (!existing) continue;
169
+
170
+ // Mutate the existing object instead of creating a copy.
171
+ // This keeps `messagesById` and `messagesByFile` in sync since
172
+ // they reference the same object instance.
173
+ for (const key of Object.keys(diskMessage)) {
174
+ if (existing[key] == null) {
175
+ existing[key] = diskMessage[key];
176
+ }
177
+ }
178
+ }
179
+ }
180
+ async processFile(absoluteFilePath) {
181
+ let messages = [];
182
+ try {
183
+ const content = await fs.readFile(absoluteFilePath, 'utf8');
184
+ let extraction;
185
+ try {
186
+ extraction = await this.extractor.extract(absoluteFilePath, content);
187
+ } catch {
188
+ return false;
189
+ }
190
+ messages = extraction.messages;
191
+ } catch (err) {
192
+ if (err.code !== 'ENOENT') {
193
+ throw err;
194
+ }
195
+ // ENOENT -> treat as no messages
196
+ }
197
+ const prevFileMessages = this.messagesByFile.get(absoluteFilePath);
198
+ const relativeFilePath = normalizePathToPosix(path.relative(this.projectRoot, absoluteFilePath));
199
+
200
+ // Init with all previous ones
201
+ const idsToRemove = Array.from(prevFileMessages?.keys() ?? []);
202
+
203
+ // Replace existing messages with new ones
204
+ const fileMessages = new Map();
205
+ for (let message of messages) {
206
+ const prevMessage = this.messagesById.get(message.id);
207
+
208
+ // Merge with previous message if it exists
209
+ if (prevMessage) {
210
+ message = {
211
+ ...message
212
+ };
213
+ if (message.references) {
214
+ message.references = this.mergeReferences(prevMessage.references ?? [], relativeFilePath, message.references);
215
+ }
216
+
217
+ // Merge other properties like description, or unknown
218
+ // attributes like flags that are opaque to us
219
+ for (const key of Object.keys(prevMessage)) {
220
+ if (message[key] == null) {
221
+ message[key] = prevMessage[key];
222
+ }
223
+ }
224
+ }
225
+ this.messagesById.set(message.id, message);
226
+ fileMessages.set(message.id, message);
227
+
228
+ // This message continues to exist in this file
229
+ const index = idsToRemove.indexOf(message.id);
230
+ if (index !== -1) idsToRemove.splice(index, 1);
231
+ }
232
+
233
+ // Clean up removed messages from `messagesById`
234
+ idsToRemove.forEach(id => {
235
+ const message = this.messagesById.get(id);
236
+ if (!message) return;
237
+ const hasOtherReferences = message.references?.some(ref => ref.path !== relativeFilePath);
238
+ if (!hasOtherReferences) {
239
+ // No other references, delete the message entirely
240
+ this.messagesById.delete(id);
241
+ } else {
242
+ // Message is used elsewhere, remove this file from references
243
+ // Mutate the existing object to keep `messagesById` and `messagesByFile` in sync
244
+ message.references = message.references?.filter(ref => ref.path !== relativeFilePath);
245
+ }
246
+ });
247
+
248
+ // Update the stored messages
249
+ if (messages.length > 0) {
250
+ this.messagesByFile.set(absoluteFilePath, fileMessages);
251
+ } else {
252
+ this.messagesByFile.delete(absoluteFilePath);
253
+ }
254
+ const changed = this.haveMessagesChangedForFile(prevFileMessages, fileMessages);
255
+ return changed;
256
+ }
257
+ mergeReferences(existing, currentFilePath, currentFileRefs) {
258
+ // Keep refs from other files, replace all refs from the current file
259
+ const otherFileRefs = existing.filter(ref => ref.path !== currentFilePath);
260
+ const merged = [...otherFileRefs, ...currentFileRefs];
261
+ return merged.sort(compareReferences);
262
+ }
263
+ haveMessagesChangedForFile(beforeMessages, afterMessages) {
264
+ // If one exists and the other doesn't, there's a change
265
+ if (!beforeMessages) {
266
+ return afterMessages.size > 0;
267
+ }
268
+
269
+ // Different sizes means changes
270
+ if (beforeMessages.size !== afterMessages.size) {
271
+ return true;
272
+ }
273
+
274
+ // Check differences in beforeMessages vs afterMessages
275
+ for (const [id, msg1] of beforeMessages) {
276
+ const msg2 = afterMessages.get(id);
277
+ if (!msg2 || !this.areMessagesEqual(msg1, msg2)) {
278
+ return true; // Early exit on first difference
279
+ }
280
+ }
281
+ return false;
282
+ }
283
+ areMessagesEqual(msg1, msg2) {
284
+ // Note: We intentionally don't compare references here.
285
+ // References are aggregated metadata from multiple files and comparing
286
+ // them would cause false positives due to parallel extraction order.
287
+ return msg1.id === msg2.id && msg1.message === msg2.message && msg1.description === msg2.description;
288
+ }
289
+ async save() {
290
+ return this.saveScheduler.schedule(() => this.saveImpl());
291
+ }
292
+ async saveImpl() {
293
+ await this.saveLocale(this.config.sourceLocale);
294
+ const targetLocales = await this.getTargetLocales();
295
+ await Promise.all(targetLocales.map(locale => this.saveLocale(locale)));
296
+ }
297
+ async saveLocale(locale) {
298
+ await this.loadCatalogsPromise;
299
+ const messages = Array.from(this.messagesById.values());
300
+ const persister = await this.getPersister();
301
+ const isSourceLocale = locale === this.config.sourceLocale;
302
+
303
+ // Check if file was modified externally (poll-at-save is cheaper than
304
+ // watchers here since stat() is fast and avoids continuous overhead)
305
+ const lastWriteTime = this.lastWriteByLocale.get(locale);
306
+ const currentFileTime = await persister.getLastModified(locale);
307
+ if (currentFileTime && lastWriteTime && currentFileTime > lastWriteTime) {
308
+ await this.reloadLocaleCatalog(locale);
309
+ }
310
+ const localeMessages = isSourceLocale ? this.messagesById : this.translationsByTargetLocale.get(locale);
311
+ const messagesToPersist = messages.map(message => {
312
+ const localeMessage = localeMessages?.get(message.id);
313
+ return {
314
+ ...localeMessage,
315
+ id: message.id,
316
+ description: message.description,
317
+ references: message.references,
318
+ message: isSourceLocale ? message.message : localeMessage?.message ?? ''
319
+ };
320
+ });
321
+ await persister.write(messagesToPersist, {
322
+ locale,
323
+ sourceMessagesById: this.messagesById
324
+ });
325
+
326
+ // Update timestamps
327
+ const newTime = await persister.getLastModified(locale);
328
+ this.lastWriteByLocale.set(locale, newTime);
329
+ }
330
+ onLocalesChange = async params => {
331
+ // Chain to existing promise
332
+ this.loadCatalogsPromise = Promise.all([this.loadCatalogsPromise, ...params.added.map(locale => this.reloadLocaleCatalog(locale))]);
333
+ for (const locale of params.added) {
334
+ await this.saveLocale(locale);
335
+ }
336
+ for (const locale of params.removed) {
337
+ this.translationsByTargetLocale.delete(locale);
338
+ this.lastWriteByLocale.delete(locale);
339
+ }
340
+ };
341
+ async handleFileEvents(events) {
342
+ if (this.loadCatalogsPromise) {
343
+ await this.loadCatalogsPromise;
344
+ }
345
+
346
+ // Wait for initial scan to complete to avoid race conditions
347
+ if (this.scanCompletePromise) {
348
+ await this.scanCompletePromise;
349
+ }
350
+ let changed = false;
351
+ const expandedEvents = await this.sourceWatcher.expandDirectoryDeleteEvents(events, Array.from(this.messagesByFile.keys()));
352
+ for (const event of expandedEvents) {
353
+ const hasChanged = await this.processFile(event.path);
354
+ changed ||= hasChanged;
355
+ }
356
+ if (changed) {
357
+ await this.save();
358
+ }
359
+ }
360
+ [Symbol.dispose]() {
361
+ this.sourceWatcher?.stop();
362
+ this.sourceWatcher = undefined;
363
+ this.saveScheduler[Symbol.dispose]();
364
+ if (this.catalogLocales && this.isDevelopment) {
365
+ this.catalogLocales.unsubscribeLocalesChange(this.onLocalesChange);
366
+ }
367
+ }
368
+ }
369
+
370
+ export { CatalogManager as default };
@@ -0,0 +1,63 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+
4
+ class CatalogPersister {
5
+ constructor(params) {
6
+ this.messagesPath = params.messagesPath;
7
+ this.codec = params.codec;
8
+ this.extension = params.extension;
9
+ }
10
+ getFileName(locale) {
11
+ return locale + this.extension;
12
+ }
13
+ getFilePath(locale) {
14
+ return path.join(this.messagesPath, this.getFileName(locale));
15
+ }
16
+ async read(locale) {
17
+ const filePath = this.getFilePath(locale);
18
+ let content;
19
+ try {
20
+ content = await fs.readFile(filePath, 'utf8');
21
+ } catch (error) {
22
+ if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
23
+ return [];
24
+ }
25
+ throw new Error(`Error while reading ${this.getFileName(locale)}:\n> ${error}`, {
26
+ cause: error
27
+ });
28
+ }
29
+ try {
30
+ return this.codec.decode(content, {
31
+ locale
32
+ });
33
+ } catch (error) {
34
+ throw new Error(`Error while decoding ${this.getFileName(locale)}:\n> ${error}`, {
35
+ cause: error
36
+ });
37
+ }
38
+ }
39
+ async write(messages, context) {
40
+ const filePath = this.getFilePath(context.locale);
41
+ const content = this.codec.encode(messages, context);
42
+ try {
43
+ const outputDir = path.dirname(filePath);
44
+ await fs.mkdir(outputDir, {
45
+ recursive: true
46
+ });
47
+ await fs.writeFile(filePath, content);
48
+ } catch (error) {
49
+ console.error(`❌ Failed to write catalog: ${error}`);
50
+ }
51
+ }
52
+ async getLastModified(locale) {
53
+ const filePath = this.getFilePath(locale);
54
+ try {
55
+ const stats = await fs.stat(filePath);
56
+ return stats.mtime;
57
+ } catch {
58
+ return undefined;
59
+ }
60
+ }
61
+ }
62
+
63
+ export { CatalogPersister as default };
@@ -0,0 +1,85 @@
1
+ /**
2
+ * De-duplicates excessive save invocations,
3
+ * while keeping a single one instant.
4
+ */
5
+ class SaveScheduler {
6
+ isSaving = false;
7
+ pendingResolvers = [];
8
+ constructor(delayMs = 50) {
9
+ this.delayMs = delayMs;
10
+ }
11
+ async schedule(saveTask) {
12
+ return new Promise((resolve, reject) => {
13
+ this.pendingResolvers.push({
14
+ resolve,
15
+ reject
16
+ });
17
+ this.nextSaveTask = saveTask;
18
+ if (!this.isSaving && !this.saveTimeout) {
19
+ // Not currently saving and no scheduled save, save immediately
20
+ this.executeSave();
21
+ } else if (this.saveTimeout) {
22
+ // A save is already scheduled, reschedule to debounce
23
+ this.scheduleSave();
24
+ }
25
+ // If isSaving is true and no timeout is scheduled, the current save
26
+ // will check for pending resolvers when it completes and schedule
27
+ // another save if needed (see finally block in executeSave)
28
+ });
29
+ }
30
+ scheduleSave() {
31
+ if (this.saveTimeout) {
32
+ clearTimeout(this.saveTimeout);
33
+ }
34
+ this.saveTimeout = setTimeout(() => {
35
+ this.saveTimeout = undefined;
36
+ this.executeSave();
37
+ }, this.delayMs);
38
+ }
39
+ async executeSave() {
40
+ if (this.isSaving) {
41
+ return;
42
+ }
43
+ const saveTask = this.nextSaveTask;
44
+ if (!saveTask) {
45
+ return;
46
+ }
47
+
48
+ // Capture current pending resolvers for this save
49
+ const resolversForThisSave = this.pendingResolvers;
50
+ this.pendingResolvers = [];
51
+ this.nextSaveTask = undefined;
52
+ this.isSaving = true;
53
+ try {
54
+ const result = await saveTask();
55
+
56
+ // Resolve only the promises that were pending when this save started
57
+ resolversForThisSave.forEach(({
58
+ resolve
59
+ }) => resolve(result));
60
+ } catch (error) {
61
+ // Reject only the promises that were pending when this save started
62
+ resolversForThisSave.forEach(({
63
+ reject
64
+ }) => reject(error));
65
+ } finally {
66
+ this.isSaving = false;
67
+
68
+ // If new saves were requested during this save, schedule another
69
+ if (this.pendingResolvers.length > 0) {
70
+ this.scheduleSave();
71
+ }
72
+ }
73
+ }
74
+ [Symbol.dispose]() {
75
+ if (this.saveTimeout) {
76
+ clearTimeout(this.saveTimeout);
77
+ this.saveTimeout = undefined;
78
+ }
79
+ this.pendingResolvers = [];
80
+ this.nextSaveTask = undefined;
81
+ this.isSaving = false;
82
+ }
83
+ }
84
+
85
+ export { SaveScheduler as default };