next-intl 4.11.2 → 4.12.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 (38) hide show
  1. package/dist/cjs/development/{JSONCodec-B-lAnRTg.cjs → JSONCodec-CzA8ubPy.cjs} +4 -2
  2. package/dist/cjs/development/{POCodec-0XdsL-1F.cjs → POCodec-CWGHK-Gp.cjs} +16 -10
  3. package/dist/cjs/development/{plugin-0S9vVrVM.cjs → plugin-DlFYUFWh.cjs} +281 -150
  4. package/dist/cjs/development/plugin.cjs +1 -1
  5. package/dist/esm/development/extractor/catalog/CatalogManager.js +146 -95
  6. package/dist/esm/development/extractor/extractMessages.js +9 -2
  7. package/dist/esm/development/extractor/format/codecs/JSONCodec.js +3 -1
  8. package/dist/esm/development/extractor/format/codecs/POCodec.js +15 -9
  9. package/dist/esm/development/extractor/normalizeExtractorConfig.js +70 -0
  10. package/dist/esm/development/extractor/source/SourceFileWatcher.js +1 -1
  11. package/dist/esm/development/extractor/utils.js +29 -5
  12. package/dist/esm/development/plugin/createNextIntlPlugin.js +13 -2
  13. package/dist/esm/development/plugin/extractor/initExtractionCompiler.js +3 -8
  14. package/dist/esm/development/plugin/getNextConfig.js +21 -34
  15. package/dist/esm/development/server/react-server/getServerExtractor.js +3 -3
  16. package/dist/esm/production/extractor/catalog/CatalogManager.js +1 -1
  17. package/dist/esm/production/extractor/extractMessages.js +1 -1
  18. package/dist/esm/production/extractor/format/codecs/JSONCodec.js +1 -1
  19. package/dist/esm/production/extractor/format/codecs/POCodec.js +1 -1
  20. package/dist/esm/production/extractor/normalizeExtractorConfig.js +1 -0
  21. package/dist/esm/production/extractor/source/SourceFileWatcher.js +1 -1
  22. package/dist/esm/production/extractor/utils.js +1 -1
  23. package/dist/esm/production/plugin/createNextIntlPlugin.js +1 -1
  24. package/dist/esm/production/plugin/extractor/initExtractionCompiler.js +1 -1
  25. package/dist/esm/production/plugin/getNextConfig.js +1 -1
  26. package/dist/esm/production/server/react-server/getServerExtractor.js +1 -1
  27. package/dist/types/extractor/ExtractionCompiler.d.ts +2 -1
  28. package/dist/types/extractor/catalog/CatalogLocales.d.ts +2 -2
  29. package/dist/types/extractor/catalog/CatalogManager.d.ts +27 -10
  30. package/dist/types/extractor/extractMessages.d.ts +2 -2
  31. package/dist/types/extractor/extractor/MessageExtractor.d.ts +2 -6
  32. package/dist/types/extractor/normalizeExtractorConfig.d.ts +5 -0
  33. package/dist/types/extractor/types.d.ts +62 -11
  34. package/dist/types/extractor/utils.d.ts +2 -1
  35. package/dist/types/plugin/extractor/initExtractionCompiler.d.ts +2 -2
  36. package/dist/types/plugin/getNextConfig.d.ts +2 -1
  37. package/dist/types/plugin/types.d.ts +10 -14
  38. package/package.json +5 -5
@@ -1,13 +1,13 @@
1
1
  import ExtractionCompiler from '../../extractor/ExtractionCompiler.js';
2
+ import { hasLocalesToExtract } from '../../extractor/utils.js';
2
3
  import { isDevelopment, isNextBuild } from '../config.js';
3
4
  import { once } from '../utils.js';
4
5
 
5
6
  // Single compiler instance, initialized once per process
6
7
  let compiler;
7
8
  const runOnce = once('_NEXT_INTL_EXTRACT');
8
- function initExtractionCompiler(pluginConfig) {
9
- const experimental = pluginConfig.experimental;
10
- if (!experimental?.extract) {
9
+ function initExtractionCompiler(extractorConfig) {
10
+ if (!extractorConfig || !hasLocalesToExtract(extractorConfig)) {
11
11
  return;
12
12
  }
13
13
 
@@ -26,11 +26,6 @@ function initExtractionCompiler(pluginConfig) {
26
26
  const shouldRun = isDevelopment || isNextBuild;
27
27
  if (!shouldRun) return;
28
28
  runOnce(() => {
29
- const extractorConfig = {
30
- srcPath: experimental.srcPath,
31
- sourceLocale: experimental.extract.sourceLocale,
32
- messages: experimental.messages
33
- };
34
29
  compiler = new ExtractionCompiler(extractorConfig, {
35
30
  isDevelopment,
36
31
  projectRoot: process.cwd()
@@ -2,6 +2,7 @@ import fs from 'fs';
2
2
  import { createRequire } from 'module';
3
3
  import path from 'path';
4
4
  import { getFormatExtension } from '../extractor/format/index.js';
5
+ import { normalizeMessagesCatalogPaths } from '../extractor/normalizeExtractorConfig.js';
5
6
  import SourceFileFilter from '../extractor/source/SourceFileFilter.js';
6
7
  import { isDevelopmentOrNextBuild } from './config.js';
7
8
  import { isNextJs16OrHigher, hasStableTurboConfig } from './nextFlags.js';
@@ -52,7 +53,7 @@ function resolveI18nPath(providedPath, cwd) {
52
53
  }
53
54
  }
54
55
  }
55
- function getNextConfig(pluginConfig, nextConfig) {
56
+ function getNextConfig(pluginConfig, nextConfig, extractorConfig) {
56
57
  const useTurbo = process.env.TURBOPACK != null;
57
58
 
58
59
  // `experimental-analyze` doesn’t set the TURBOPACK env param. Since Next.js
@@ -60,25 +61,27 @@ function getNextConfig(pluginConfig, nextConfig) {
60
61
  // always configure Turbopack just in case.
61
62
  const shouldConfigureTurbo = useTurbo || isNextJs16OrHigher();
62
63
  const nextIntlConfig = {};
63
- function getExtractMessagesLoaderConfig() {
64
- const experimental = pluginConfig.experimental;
65
- if (!experimental.srcPath || !pluginConfig.experimental?.messages) {
66
- throwError('`srcPath` and `messages` are required when using `extractor`.');
67
- }
64
+ let messageLoadPaths = [];
65
+ if (pluginConfig.experimental?.messages) {
66
+ messageLoadPaths = normalizeMessagesCatalogPaths(pluginConfig.experimental.messages.path);
67
+ }
68
+ function getExtractMessagesLoaderConfig(config) {
68
69
  return {
69
70
  loader: 'next-intl/extractor/extractionLoader',
70
- options: {
71
- srcPath: experimental.srcPath,
72
- sourceLocale: experimental.extract.sourceLocale,
73
- messages: pluginConfig.experimental.messages
74
- }
71
+ options: config
75
72
  };
76
73
  }
77
74
  function getCatalogLoaderConfig() {
75
+ const messages = pluginConfig.experimental.messages;
78
76
  return {
79
77
  loader: 'next-intl/extractor/catalogLoader',
80
78
  options: {
81
- messages: pluginConfig.experimental.messages
79
+ messages: {
80
+ format: messages.format,
81
+ ...(messages.precompile !== undefined && {
82
+ precompile: messages.precompile
83
+ })
84
+ }
82
85
  }
83
86
  };
84
87
  }
@@ -99,18 +102,6 @@ function getNextConfig(pluginConfig, nextConfig) {
99
102
  rules[glob] = rule;
100
103
  }
101
104
  }
102
-
103
- // Validate messages config
104
- if (pluginConfig.experimental?.messages) {
105
- const messages = pluginConfig.experimental.messages;
106
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- For non-TS consumers
107
- if (!messages.format) {
108
- throwError('`format` is required when using `messages`.');
109
- }
110
- if (!messages.path) {
111
- throwError('`path` is required when using `messages`.');
112
- }
113
- }
114
105
  if (shouldConfigureTurbo) {
115
106
  if (pluginConfig.requestConfig && path.isAbsolute(pluginConfig.requestConfig)) {
116
107
  throwError("Turbopack support for next-intl currently does not support absolute paths, please provide a relative one (e.g. './src/i18n/config.ts').\n\nFound: " + pluginConfig.requestConfig);
@@ -147,13 +138,11 @@ function getNextConfig(pluginConfig, nextConfig) {
147
138
  throwError('Message extraction requires Next.js 16 or higher.');
148
139
  }
149
140
  rules ??= getTurboRules();
150
- const srcPaths = (Array.isArray(pluginConfig.experimental.srcPath) ? pluginConfig.experimental.srcPath : [pluginConfig.experimental.srcPath]).map(srcPath => srcPath.endsWith('/') ? srcPath.slice(0, -1) : srcPath);
151
141
  addTurboRule(rules, `*.{${SourceFileFilter.EXTENSIONS.join(',')}}`, {
152
- loaders: [getExtractMessagesLoaderConfig()],
142
+ loaders: [getExtractMessagesLoaderConfig(extractorConfig)],
153
143
  condition: {
154
- // Note: We don't need `not: 'foreign'`, because this is
155
- // implied by the filter based on `srcPath`.
156
- path: `{${srcPaths.join(',')}}` + '/**/*',
144
+ // We don't filter for `path` here to allow transformation
145
+ // of `useExtracted` calls in external packages (e.g. monorepos)
157
146
  content: /(useExtracted|getExtracted)/
158
147
  }
159
148
  });
@@ -169,7 +158,7 @@ function getNextConfig(pluginConfig, nextConfig) {
169
158
  addTurboRule(rules, `*${extension}`, {
170
159
  loaders: [getCatalogLoaderConfig()],
171
160
  condition: {
172
- path: `${pluginConfig.experimental.messages.path}/**/*`
161
+ path: `{${messageLoadPaths.join(',')}}/**/*`
173
162
  },
174
163
  as: '*.js'
175
164
  });
@@ -227,11 +216,9 @@ function getNextConfig(pluginConfig, nextConfig) {
227
216
  if (pluginConfig.experimental?.extract) {
228
217
  if (!config.module) config.module = {};
229
218
  if (!config.module.rules) config.module.rules = [];
230
- const srcPath = pluginConfig.experimental.srcPath;
231
219
  config.module.rules.push({
232
220
  test: new RegExp(`\\.(${SourceFileFilter.EXTENSIONS.join('|')})$`),
233
- include: Array.isArray(srcPath) ? srcPath.map(cur => path.resolve(config.context, cur)) : path.resolve(config.context, srcPath || ''),
234
- use: [getExtractMessagesLoaderConfig()]
221
+ use: [getExtractMessagesLoaderConfig(extractorConfig)]
235
222
  });
236
223
  }
237
224
 
@@ -242,7 +229,7 @@ function getNextConfig(pluginConfig, nextConfig) {
242
229
  const extension = getFormatExtension(pluginConfig.experimental.messages.format);
243
230
  config.module.rules.push({
244
231
  test: new RegExp(`${extension.replace(/\./g, '\\.')}$`),
245
- include: path.resolve(config.context, pluginConfig.experimental.messages.path),
232
+ include: messageLoadPaths.map(dirPath => path.resolve(config.context, dirPath)),
246
233
  use: [getCatalogLoaderConfig()],
247
234
  type: 'javascript/auto'
248
235
  });
@@ -13,17 +13,17 @@ function getServerExtractorImpl(config, namespace) {
13
13
  function translateFn(...[message, values, formats]) {
14
14
  return t(undefined, values, formats,
15
15
  // @ts-expect-error -- Secret fallback parameter
16
- message );
16
+ message);
17
17
  }
18
18
  translateFn.rich = function translateRichFn(...[message, values, formats]) {
19
19
  return t.rich(undefined, values, formats,
20
20
  // @ts-expect-error -- Secret fallback parameter
21
- message );
21
+ message);
22
22
  };
23
23
  translateFn.markup = function translateMarkupFn(...[message, values, formats]) {
24
24
  return t.markup(undefined, values, formats,
25
25
  // @ts-expect-error -- Secret fallback parameter
26
- message );
26
+ message);
27
27
  };
28
28
  translateFn.has = function translateHasFn(
29
29
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -1 +1 @@
1
- import e from"fs/promises";import s from"path";import{resolveCodec as t,getFormatExtension as a}from"../format/index.js";import o from"../source/SourceFileScanner.js";import i from"../source/SourceFileWatcher.js";import{getDefaultProjectRoot as r,normalizePathToPosix as c,compareReferences as l}from"../utils.js";import n from"./CatalogLocales.js";import h from"./CatalogPersister.js";import g from"./SaveScheduler.js";class m{messagesByFile=new Map;messagesById=new Map;translationsByTargetLocale=new Map;lastWriteByLocale=new Map;constructor(e,s){this.config=e,this.saveScheduler=new g(50),this.projectRoot=s.projectRoot??r(),this.isDevelopment=s.isDevelopment??!1,this.extractor=s.extractor,this.isDevelopment&&(this.sourceWatcher=new i(this.getSrcPaths(),this.handleFileEvents.bind(this)),this.sourceWatcher.start())}async getCodec(){return this.codec||(this.codec=await t(this.config.messages.format,this.projectRoot)),this.codec}async getPersister(){return this.persister||(this.persister=new h({messagesPath:this.config.messages.path,codec:await this.getCodec(),extension:a(this.config.messages.format)})),this.persister}getCatalogLocales(){if(this.catalogLocales)return this.catalogLocales;{const e=s.join(this.projectRoot,this.config.messages.path);return this.catalogLocales=new n({messagesDir:e,sourceLocale:this.config.sourceLocale,extension:a(this.config.messages.format),locales:this.config.messages.locales}),this.catalogLocales}}async getTargetLocales(){return this.getCatalogLocales().getTargetLocales()}getSrcPaths(){return(Array.isArray(this.config.srcPath)?this.config.srcPath:[this.config.srcPath]).map((e=>s.join(this.projectRoot,e)))}async loadMessages(){const e=await this.loadSourceMessages();if(this.loadCatalogsPromise=this.loadTargetMessages(),await this.loadCatalogsPromise,this.scanCompletePromise=(async()=>{const s=await o.getSourceFiles(this.getSrcPaths());await Promise.all(Array.from(s).map((async e=>this.processFile(e)))),this.mergeSourceDiskMetadata(e)})(),await this.scanCompletePromise,this.isDevelopment){this.getCatalogLocales().subscribeLocalesChange(this.onLocalesChange)}}async loadSourceMessages(){const e=await this.loadLocaleMessages(this.config.sourceLocale),s=new Map;for(const t of e)s.set(t.id,t);return s}async loadLocaleMessages(e){const s=await this.getPersister(),t=await s.read(e),a=await s.getLastModified(e);return this.lastWriteByLocale.set(e,a),t}async loadTargetMessages(){const e=await this.getTargetLocales();await Promise.all(e.map((e=>this.reloadLocaleCatalog(e))))}async reloadLocaleCatalog(e){const s=await this.loadLocaleMessages(e);if(e===this.config.sourceLocale)for(const e of s){const s=this.messagesById.get(e.id);if(s)for(const t of Object.keys(e))["id","message","description","references"].includes(t)||(s[t]=e[t])}else{const t=this.translationsByTargetLocale.get(e),a=t&&t.size>0;if(s.length>0){const t=new Map;for(const e of s)t.set(e.id,e);this.translationsByTargetLocale.set(e,t)}else if(a);else{const s=new Map;this.translationsByTargetLocale.set(e,s)}}}mergeSourceDiskMetadata(e){for(const[s,t]of e){const e=this.messagesById.get(s);if(e)for(const s of Object.keys(t))null==e[s]&&(e[s]=t[s])}}async processFile(t){let a=[];try{const s=await e.readFile(t,"utf8");let o;try{o=await this.extractor.extract(t,s)}catch{return!1}a=o.messages}catch(e){if("ENOENT"!==e.code)throw e}const o=this.messagesByFile.get(t),i=c(s.relative(this.projectRoot,t)),r=Array.from(o?.keys()??[]),l=new Map;for(let e of a){const s=this.messagesById.get(e.id);if(s){e={...e},e.references&&(e.references=this.mergeReferences(s.references??[],i,e.references));for(const t of Object.keys(s))null==e[t]&&(e[t]=s[t])}this.messagesById.set(e.id,e),l.set(e.id,e);const t=r.indexOf(e.id);-1!==t&&r.splice(t,1)}r.forEach((e=>{const s=this.messagesById.get(e);if(!s)return;const t=s.references?.some((e=>e.path!==i));t?s.references=s.references?.filter((e=>e.path!==i)):this.messagesById.delete(e)})),a.length>0?this.messagesByFile.set(t,l):this.messagesByFile.delete(t);return this.haveMessagesChangedForFile(o,l)}mergeReferences(e,s,t){return[...e.filter((e=>e.path!==s)),...t].sort(l)}haveMessagesChangedForFile(e,s){if(!e)return s.size>0;if(e.size!==s.size)return!0;for(const[t,a]of e){const e=s.get(t);if(!e||!this.areMessagesEqual(a,e))return!0}return!1}areMessagesEqual(e,s){return e.id===s.id&&e.message===s.message&&e.description===s.description}async save(){return this.saveScheduler.schedule((()=>this.saveImpl()))}async saveImpl(){await this.saveLocale(this.config.sourceLocale);const e=await this.getTargetLocales();await Promise.all(e.map((e=>this.saveLocale(e))))}async saveLocale(e){await this.loadCatalogsPromise;const s=Array.from(this.messagesById.values()),t=await this.getPersister(),a=e===this.config.sourceLocale,o=this.lastWriteByLocale.get(e),i=await t.getLastModified(e);i&&o&&i>o&&await this.reloadLocaleCatalog(e);const r=a?this.messagesById:this.translationsByTargetLocale.get(e),c=s.map((e=>{const s=r?.get(e.id);return{...s,id:e.id,description:e.description,references:e.references,message:a?e.message:s?.message??""}}));await t.write(c,{locale:e,sourceMessagesById:this.messagesById});const l=await t.getLastModified(e);this.lastWriteByLocale.set(e,l)}onLocalesChange=async e=>{this.loadCatalogsPromise=Promise.all([this.loadCatalogsPromise,...e.added.map((e=>this.reloadLocaleCatalog(e)))]);for(const s of e.added)await this.saveLocale(s);for(const s of e.removed)this.translationsByTargetLocale.delete(s),this.lastWriteByLocale.delete(s)};async handleFileEvents(e){this.loadCatalogsPromise&&await this.loadCatalogsPromise,this.scanCompletePromise&&await this.scanCompletePromise;let s=!1;const t=await this.sourceWatcher.expandDirectoryDeleteEvents(e,Array.from(this.messagesByFile.keys()));for(const e of t){const t=await this.processFile(e.path);s||=t}s&&await this.save()}[Symbol.dispose](){this.sourceWatcher?.stop(),this.sourceWatcher=void 0,this.saveScheduler[Symbol.dispose](),this.catalogLocales&&this.isDevelopment&&this.catalogLocales.unsubscribeLocalesChange(this.onLocalesChange)}}export{m as default};
1
+ import e from"fs/promises";import s from"path";import{resolveCodec as t,getFormatExtension as a}from"../format/index.js";import o from"../source/SourceFileScanner.js";import r from"../source/SourceFileWatcher.js";import{getDefaultProjectRoot as i,localeCompare as c,compareReferences as n}from"../utils.js";import l from"./CatalogLocales.js";import h from"./CatalogPersister.js";import g from"./SaveScheduler.js";class d{static extractorOwnedAggregatorKeys=new Set(["description","id","message","references"]);sourceMessagesByFile=new Map;sourceMessagesById=new Map;messagesById=new Map;translationsByTargetLocale=new Map;lastWriteByLocale=new Map;constructor(e,s){this.config=e,this.saveScheduler=new g(s.saveDebounceMs??50),this.projectRoot=s.projectRoot??i(),this.isDevelopment=s.isDevelopment??!1,this.extractor=s.extractor,this.isDevelopment&&(this.sourceWatcher=new r(this.getSrcPaths(),this.handleFileEvents.bind(this)),this.sourceWatcher.start())}async getCodec(){return this.codec||(this.codec=await t(this.config.messages.format,this.projectRoot)),this.codec}async getPersister(){return this.persister||(this.persister=new h({messagesPath:this.config.extract.path,codec:await this.getCodec(),extension:a(this.config.messages.format)})),this.persister}getCatalogLocales(){if(this.catalogLocales)return this.catalogLocales;{const e=s.join(this.projectRoot,this.config.extract.path);return this.catalogLocales=new l({messagesDir:e,extension:a(this.config.messages.format),locales:this.config.extract.locales,sourceLocale:this.config.extract.sourceLocale}),this.catalogLocales}}async getTargetLocales(){return this.getCatalogLocales().getTargetLocales()}getSrcPaths(){return(Array.isArray(this.config.extract.srcPath)?this.config.extract.srcPath:[this.config.extract.srcPath]).map((e=>s.join(this.projectRoot,e)))}async loadMessages(){const e=await this.loadSourceMessages();if(this.loadCatalogsPromise=this.loadTargetMessages(),await this.loadCatalogsPromise,this.scanCompletePromise=(async()=>{const s=Array.from(await o.getSourceFiles(this.getSrcPaths())).toSorted(c),t=await Promise.all(s.map((async e=>({filePath:e,messages:await this.extractFile(e)}))));for(const{filePath:e,messages:s}of t)s&&this.applyFileMessages(e,s);this.mergeSourceDiskMetadata(e)})(),await this.scanCompletePromise,this.isDevelopment){this.getCatalogLocales().subscribeLocalesChange(this.onLocalesChange)}}async loadSourceMessages(){const e=await this.loadLocaleMessages(this.config.extract.sourceLocale),s=new Map;for(const t of e)s.set(t.id,t);return s}async loadLocaleMessages(e){const s=await this.getPersister(),t=await s.read(e),a=await s.getLastModified(e);return this.lastWriteByLocale.set(e,a),t}async loadTargetMessages(){const e=await this.getTargetLocales();await Promise.all(e.map((e=>this.reloadLocaleCatalog(e))))}async reloadLocaleCatalog(e){const s=await this.loadLocaleMessages(e);if(e===this.config.extract.sourceLocale)for(const e of s){const s=this.messagesById.get(e.id);if(s)for(const t of Object.keys(e))d.extractorOwnedAggregatorKeys.has(t)||(s[t]=e[t])}else{const t=this.translationsByTargetLocale.get(e),a=t&&t.size>0;if(s.length>0){const t=new Map;for(const e of s)t.set(e.id,e);this.translationsByTargetLocale.set(e,t)}else if(a);else{const s=new Map;this.translationsByTargetLocale.set(e,s)}}}mergeSourceDiskMetadata(e){for(const[s,t]of e){const e=this.messagesById.get(s);if(e)for(const s of Object.keys(t))d.extractorOwnedAggregatorKeys.has(s)||null!=e[s]||(e[s]=t[s])}}async processFile(e){const s=await this.extractFile(e);return!!s&&this.applyFileMessages(e,s)}async extractFile(s){let t=[];try{const a=await e.readFile(s,"utf8");let o;try{o=await this.extractor.extract(s,a)}catch{return}t=o.messages}catch(e){if("ENOENT"!==e.code)throw e}return t}applyFileMessages(e,s){const t=this.sourceMessagesByFile.get(e),a=this.groupSourceMessagesById(s),o=new Set([...t?.keys()??[],...a.keys()]);a.size>0?this.sourceMessagesByFile.set(e,a):this.sourceMessagesByFile.delete(e);for(const s of o){const t=this.sourceMessagesById.get(s);t&&(t.delete(e),0===t.size&&this.sourceMessagesById.delete(s));const o=a.get(s);if(o){let t=this.sourceMessagesById.get(s);t||(t=new Map,this.sourceMessagesById.set(s,t)),t.set(e,o)}this.rebuildMessageById(s)}return this.haveMessagesChangedForFile(t,a)}groupSourceMessagesById(e){const s=new Map;for(const t of e){const e=s.get(t.id);e?e.push(t):s.set(t.id,[t])}return s}rebuildMessageById(e){const s=Array.from(this.sourceMessagesById.get(e)?.values()??[]).flat();if(0===s.length)return void this.messagesById.delete(e);const t=this.messagesById.get(e),a={description:this.mergeDescriptions(s),id:e,message:s[0].message,references:s.map((e=>e.reference)).sort(n)};if(t)for(const e of Object.keys(t))d.extractorOwnedAggregatorKeys.has(e)||null!=a[e]||(a[e]=t[e]);this.messagesById.set(e,a)}mergeDescriptions(e){const s=e.toSorted(((e,s)=>n(e.reference,s.reference))),t=[];for(const e of s){const{description:s}=e;null==s||t.includes(s)||t.push(s)}return t}haveMessagesChangedForFile(e,s){if(!e)return s.size>0;if(e.size!==s.size)return!0;for(const[t,a]of e){const e=s.get(t);if(!e)return!0;if(!this.areSourceMessageArraysEqual(a,e))return!0}return!1}areSourceMessageArraysEqual(e,s){return e.length===s.length&&e.every(((e,t)=>this.areSourceMessagesEqual(e,s[t])))}areSourceMessagesEqual(e,s){return e.id===s.id&&e.message===s.message&&e.description===s.description&&e.reference.path===s.reference.path&&e.reference.line===s.reference.line}async save(){return this.saveScheduler.schedule((()=>this.saveImpl()))}async saveImpl(){await this.saveLocale(this.config.extract.sourceLocale);const e=await this.getTargetLocales();await Promise.all(e.map((e=>this.saveLocale(e))))}async saveLocale(e){await this.loadCatalogsPromise;const s=Array.from(this.messagesById.values()),t=await this.getPersister(),a=e===this.config.extract.sourceLocale,o=this.lastWriteByLocale.get(e),r=await t.getLastModified(e);r&&o&&r>o&&await this.reloadLocaleCatalog(e);const i=a?this.messagesById:this.translationsByTargetLocale.get(e),c=s.map((e=>{const s=i?.get(e.id);return{...s,id:e.id,description:e.description,references:e.references,message:a?e.message:s?.message??""}}));await t.write(c,{locale:e,sourceMessagesById:this.messagesById});const n=await t.getLastModified(e);this.lastWriteByLocale.set(e,n)}onLocalesChange=async e=>{this.loadCatalogsPromise=Promise.all([this.loadCatalogsPromise,...e.added.map((e=>this.reloadLocaleCatalog(e)))]);for(const s of e.added)await this.saveLocale(s);for(const s of e.removed)this.translationsByTargetLocale.delete(s),this.lastWriteByLocale.delete(s)};async handleFileEvents(e){this.loadCatalogsPromise&&await this.loadCatalogsPromise,this.scanCompletePromise&&await this.scanCompletePromise;let s=!1;const t=await this.sourceWatcher.expandDirectoryDeleteEvents(e,Array.from(this.sourceMessagesByFile.keys()));for(const e of t.toSorted(((e,s)=>c(e.path,s.path)))){const t=await this.processFile(e.path);s||=t}s&&await this.save()}[Symbol.dispose](){this.sourceWatcher?.stop(),this.sourceWatcher=void 0,this.saveScheduler[Symbol.dispose](),this.catalogLocales&&this.isDevelopment&&this.catalogLocales.unsubscribeLocalesChange(this.onLocalesChange)}}export{d as default};
@@ -1 +1 @@
1
- import t from"./ExtractionCompiler.js";import o from"./extractor/MessageExtractor.js";import{getDefaultProjectRoot as r}from"./utils.js";async function e(e){const a=new t(e,{extractor:new o({isDevelopment:!1,projectRoot:r()})});await a.extractAll()}export{e as default};
1
+ import{warn as t}from"../plugin/utils.js";import o from"./ExtractionCompiler.js";import r from"./extractor/MessageExtractor.js";import e from"./normalizeExtractorConfig.js";import{hasLocalesToExtract as s,getDefaultProjectRoot as i}from"./utils.js";async function a(a){const m=e(a);if(!s(m))return void t("`messages.locales` is empty, so no messages were updated.");const n=new o(m,{extractor:new r({isDevelopment:!1,projectRoot:i()})});await n.extractAll()}export{a as default};
@@ -1 +1 @@
1
- import{getSortedMessages as e,setNestedProperty as r,isForbiddenObjectKey as s}from"../../utils.js";import{defineCodec as t}from"../ExtractorCodec.js";var o=t((()=>({decode(e){const r=JSON.parse(e),s=[];return a(r,((e,r)=>{s.push({id:r,message:e})})),s},encode(s){const t={};for(const o of e(s))r(t,o.id,o.message);return JSON.stringify(t,null,2)+"\n"},toJSONString:e=>e})));function a(e,r,t=""){for(const o of Object.keys(e)){if(s(o))throw new Error(`Invalid message catalog key: \`${o}\`.`);const n=t?t+"."+o:o,i=e[o];if("string"==typeof i)r(i,n);else{if(Array.isArray(i))throw new Error(`Message at \`${n}\` resolved to an array, but only strings are supported. See https://next-intl.dev/docs/usage/translations#arrays-of-messages`);"object"==typeof i&&a(i,r,n)}}}export{o as default};
1
+ import{getSortedMessages as e,setNestedProperty as r,isForbiddenObjectKey as s}from"../../utils.js";import{defineCodec as t}from"../ExtractorCodec.js";var o=t((()=>({decode(e){const r=JSON.parse(e),s=[];return n(r,((e,r)=>{s.push({id:r,message:e,references:[],description:[]})})),s},encode(s){const t={};for(const o of e(s))r(t,o.id,o.message);return JSON.stringify(t,null,2)+"\n"},toJSONString:e=>e})));function n(e,r,t=""){for(const o of Object.keys(e)){if(s(o))throw new Error(`Invalid message catalog key: \`${o}\`.`);const a=t?t+"."+o:o,i=e[o];if("string"==typeof i)r(i,a);else{if(Array.isArray(i))throw new Error(`Message at \`${a}\` resolved to an array, but only strings are supported. See https://next-intl.dev/docs/usage/translations#arrays-of-messages`);"object"==typeof i&&n(i,r,a)}}}export{o as default};
@@ -1 +1 @@
1
- import e from"po-parser";import{setNestedProperty as t,getSortedMessages as s}from"../../utils.js";import{defineCodec as r}from"../ExtractorCodec.js";var n=r((()=>{const r={"Content-Type":"text/plain; charset=utf-8","Content-Transfer-Encoding":"8bit","X-Generator":"next-intl","X-Crowdin-SourceKey":"msgstr"},n=".",o=new Map;return{decode(t,s){const r=e.parse(t);r.meta&&o.set(s.locale,r.meta);return(r.messages||[]).map((e=>{const{extractedComments:t,msgctxt:s,msgid:r,msgstr:o,...a}=e;if(t&&t.length>1)throw new Error(`Multiple extracted comments are not supported. Found ${t.length} comments for msgid "${r}".`);return{...a,id:s?[s,r].join(n):r,message:o,...t&&t.length>0&&{description:t[0]}}}))},encode(t,a){const m=s(t).map((e=>{const{description:t,id:s,message:r,...o}=e,a=s.lastIndexOf(n),m=s.includes(n);return{msgid:m?s.slice(a+1):s,msgstr:r,...t&&{extractedComments:[t]},...m&&{msgctxt:s.slice(0,a)},...o}}));return e.serialize({meta:{Language:a.locale,...r,...o.get(a.locale)},messages:m})},toJSONString(e,s){const r=this.decode(e,s),n={};for(const e of r)t(n,e.id,e.message);return JSON.stringify(n)}}}));export{n as default};
1
+ import e from"po-parser";import{setNestedProperty as t,getSortedMessages as s}from"../../utils.js";import{defineCodec as r}from"../ExtractorCodec.js";var n=r((()=>{const r={"Content-Type":"text/plain; charset=utf-8","Content-Transfer-Encoding":"8bit","X-Generator":"next-intl","X-Crowdin-SourceKey":"msgstr"},n=".",o=new Map;return{decode(t,s){const r=e.parse(t);r.meta&&o.set(s.locale,r.meta);return(r.messages||[]).map((e=>{const{extractedComments:t,msgctxt:s,msgid:r,msgstr:o,references:a,...c}=e;return{...c,id:s?[s,r].join(n):r,message:o,description:t??[],references:a??[]}}))},encode(t,a){const c=s(t).map((e=>{const{description:t=[],id:s,message:r,references:o,...a}=e,c=s.lastIndexOf(n),m=s.includes(n),i=m?s.slice(c+1):s,d=[...new Set(o.map((e=>e.path)))].map((e=>({path:e})));return{msgid:i,msgstr:r,...t.length>0&&{extractedComments:t},...m&&{msgctxt:s.slice(0,c)},...d.length>0&&{references:d},...a}}));return e.serialize({meta:{Language:a.locale,...r,...o.get(a.locale)},messages:c})},toJSONString(e,s){const r=this.decode(e,s),n={};for(const e of r)t(n,e.id,e.message);return JSON.stringify(n)}}}));export{n as default};
@@ -0,0 +1 @@
1
+ import{throwError as e,warn as s}from"../plugin/utils.js";function a(e){return e.endsWith("/")?e.slice(0,-1):e}function t(e){return(Array.isArray(e)?e:[e]).map((e=>a(String(e).trim()))).filter((e=>e.length>0))}function r(r){null==r.messages&&e("`messages` is required when extracting messages.");const c=r.extract;let o,i;void 0!==c&&!0!==c&&(c.sourceLocale&&(s("`extract.sourceLocale` is deprecated in favor of `messages.sourceLocale`."),i=c.sourceLocale),c.path&&(o=a(c.path)));const n=r.messages.locales;n||e("`messages.locales` is required when extracting messages."),r.messages.sourceLocale&&(i=r.messages.sourceLocale),i||e("`messages.sourceLocale` is required when extracting messages.");const l=r.srcPath;null==l&&e("`srcPath` is required when extracting messages.");const g=Array.isArray(r.messages.path),m=t(r.messages.path);return 0===m.length&&e("`messages.path` must not be empty."),null==o&&(g&&e("When `messages.path` is an array, `extract.path` is required to select the writable catalog directory."),o=m[0]),{extract:{locales:n,path:o,sourceLocale:i,srcPath:l},messages:{format:r.messages.format,path:m}}}export{r as default,t as normalizeMessagesCatalogPaths};
@@ -1 +1 @@
1
- import t from"fs/promises";import s from"path";import{subscribe as e}from"@parcel/watcher";import o from"./SourceFileFilter.js";import r from"./SourceFileScanner.js";class i{subscriptions=[];constructor(t,s){this.roots=t,this.onChange=s}async start(){if(this.subscriptions.length>0)return;const t=o.IGNORED_DIRECTORIES.map((t=>`**/${t}/**`));for(const s of this.roots){const o=await e(s,(async(t,s)=>{if(t)return void console.error(t);const e=await this.normalizeEvents(s);e.length>0&&this.onChange(e)}),{ignore:t});this.subscriptions.push(o)}}async normalizeEvents(s){const e=[],i=[];await Promise.all(s.map((async s=>{if("create"===s.type)try{if((await t.stat(s.path)).isDirectory())return void e.push(s.path)}catch{}i.push(s)})));let a=[];if(e.length>0)try{const t=await r.getSourceFiles(e);a=Array.from(t).map((t=>({type:"create",path:t})))}catch{}const n=[...i,...a],c=new Set,p=[];for(const t of n){const s=`${t.type}:${t.path}`;c.has(s)||(c.add(s),p.push(t))}return p.filter((t=>"delete"===t.type||o.isSourceFile(t.path)))}async expandDirectoryDeleteEvents(t,e){const r=[];for(const i of t)if("delete"!==i.type||o.isSourceFile(i.path))r.push(i);else{const t=s.resolve(i.path),a=[];for(const s of e)o.isWithinPath(s,t)&&a.push(s);if(a.length>0)for(const t of a)r.push({type:"delete",path:t});else r.push(i)}return r}async stop(){await Promise.all(this.subscriptions.map((t=>t.unsubscribe()))),this.subscriptions=[]}[Symbol.dispose](){this.stop()}}export{i as default};
1
+ import t from"fs/promises";import s from"path";import{subscribe as e}from"@parcel/watcher";import o from"./SourceFileFilter.js";import r from"./SourceFileScanner.js";class i{subscriptions=[];constructor(t,s){this.roots=t,this.onChange=s}async start(){if(this.subscriptions.length>0)return;const t=o.IGNORED_DIRECTORIES.map((t=>`**/${t}/**`));for(const s of this.roots){const o=await e(s,(async(t,s)=>{if(t)return void console.error(t);const e=await this.normalizeEvents(s);e.length>0&&await this.onChange(e)}),{ignore:t});this.subscriptions.push(o)}}async normalizeEvents(s){const e=[],i=[];await Promise.all(s.map((async s=>{if("create"===s.type)try{if((await t.stat(s.path)).isDirectory())return void e.push(s.path)}catch{}i.push(s)})));let a=[];if(e.length>0)try{const t=await r.getSourceFiles(e);a=Array.from(t).map((t=>({type:"create",path:t})))}catch{}const n=[...i,...a],c=new Set,p=[];for(const t of n){const s=`${t.type}:${t.path}`;c.has(s)||(c.add(s),p.push(t))}return p.filter((t=>"delete"===t.type||o.isSourceFile(t.path)))}async expandDirectoryDeleteEvents(t,e){const r=[];for(const i of t)if("delete"!==i.type||o.isSourceFile(i.path))r.push(i);else{const t=s.resolve(i.path),a=[];for(const s of e)o.isWithinPath(s,t)&&a.push(s);if(a.length>0)for(const t of a)r.push({type:"delete",path:t});else r.push(i)}return r}async stop(){await Promise.all(this.subscriptions.map((t=>t.unsubscribe()))),this.subscriptions=[]}[Symbol.dispose](){this.stop()}}export{i as default};
@@ -1 +1 @@
1
- import t from"path";function e(e){return t.posix.normalize(e.split(t.win32.sep).join(t.posix.sep))}const n=new Set(["__proto__","constructor","prototype"]);function o(t){return n.has(t)}function r(t,e,n){const r=e.split(".");for(const t of r)if(o(t))throw new Error(`Invalid message id segment: ${t}`);let c=t;for(let t=0;t<r.length-1;t++){const e=r[t];Object.prototype.hasOwnProperty.call(c,e)&&"object"==typeof c[e]&&null!==c[e]||(c[e]=Object.create(null)),c=c[e]}c[r[r.length-1]]=n}function c(t){return t.toSorted(((t,e)=>{const n=t.references?.[0],o=e.references?.[0];return n&&o?i(n,o):0}))}function s(t,e){return t.localeCompare(e,"en")}function i(t,e){const n=s(t.path,e.path);return 0!==n?n:(t.line??0)-(e.line??0)}function p(){return process.cwd()}export{i as compareReferences,p as getDefaultProjectRoot,c as getSortedMessages,o as isForbiddenObjectKey,s as localeCompare,e as normalizePathToPosix,r as setNestedProperty};
1
+ import e from"path";import{warn as n}from"../plugin/utils.js";function t(n){return e.posix.normalize(n.split(e.win32.sep).join(e.posix.sep))}const o=new Set(["__proto__","constructor","prototype"]);function r(e){return o.has(e)}function c(e){const{locales:n}=e.extract;return"infer"===n||n.length>0}function l(e,n,t){const o=n.split(".");for(const e of o)if(r(e))throw new Error(`Invalid message id segment: ${e}`);let c=e;for(let e=0;e<o.length-1;e++){const n=o[e];Object.prototype.hasOwnProperty.call(c,n)&&"object"==typeof c[n]&&null!==c[n]||(c[n]=Object.create(null)),c=c[n]}c[o[o.length-1]]=t}function s(e){const n=new Set;return e.toSorted(((e,t)=>{const o=e.references[0],r=t.references[0];return null==o&&i(e.id,n),null==r&&i(t.id,n),null==o||null==r?0:f(o,r)}))}function i(e,t){t.has(e)||(t.add(e),n(`Missing file reference for extracted message: ${e}`))}function u(e,n){return e.localeCompare(n,"en")}function f(e,n){const t=u(e.path,n.path);return 0!==t?t:(e.line??0)-(n.line??0)}function p(){return process.cwd()}export{f as compareReferences,p as getDefaultProjectRoot,s as getSortedMessages,c as hasLocalesToExtract,r as isForbiddenObjectKey,u as localeCompare,t as normalizePathToPosix,l as setNestedProperty};
@@ -1 +1 @@
1
- import e from"./extractor/initExtractionCompiler.js";import t from"./getNextConfig.js";import{warn as r}from"./utils.js";import o from"./declaration/createMessagesDeclaration.js";function n(n,i){null!=i?.i18n&&r("An `i18n` property was found in your Next.js config. This likely causes conflicts and should therefore be removed if you use the App Router.\n\nIf you're in progress of migrating from the Pages Router, you can refer to this example: https://next-intl.dev/examples#app-router-migration\n");const s=function(){const e=process.argv[1];if(!e)return!1;return e.replace(/\\/g,"/").includes("/telemetry/detached-flush")}(),a=n.experimental?.createMessagesDeclaration;return a&&!s&&o("string"==typeof a?[a]:a),s||e(n),t(n,i)}function i(e={}){const t="string"==typeof e?{requestConfig:e}:e;return function(e){return n(t,e)}}export{i as default};
1
+ import e from"../extractor/normalizeExtractorConfig.js";import t from"./extractor/initExtractionCompiler.js";import r from"./getNextConfig.js";import{warn as o}from"./utils.js";import n from"./declaration/createMessagesDeclaration.js";function s(s,i){null!=i?.i18n&&o("An `i18n` property was found in your Next.js config. This likely causes conflicts and should therefore be removed if you use the App Router.\n\nIf you're in progress of migrating from the Pages Router, you can refer to this example: https://next-intl.dev/examples#app-router-migration\n");const a=function(){const e=process.argv[1];if(!e)return!1;return e.replace(/\\/g,"/").includes("/telemetry/detached-flush")}(),c=s.experimental?.createMessagesDeclaration;let f;c&&!a&&n("string"==typeof c?[c]:c);const u=s.experimental,l=u?.extract;return l&&(f=e({extract:l,messages:u.messages,srcPath:u.srcPath})),a||t(f),r(s,i,f)}function i(e={}){const t="string"==typeof e?{requestConfig:e}:e;return function(e){return s(t,e)}}export{i as default};
@@ -1 +1 @@
1
- import o from"../../extractor/ExtractionCompiler.js";import{isDevelopment as e,isNextBuild as t}from"../config.js";import{once as s}from"../utils.js";let r;const c=s("_NEXT_INTL_EXTRACT");function n(s){const n=s.experimental;if(!n?.extract)return;(e||t)&&c((()=>{const t={srcPath:n.srcPath,sourceLocale:n.extract.sourceLocale,messages:n.messages};function s(){r&&(r[Symbol.dispose](),r=void 0)}r=new o(t,{isDevelopment:e,projectRoot:process.cwd()}),r.extractAll(),process.on("exit",s),process.on("SIGINT",s),process.on("SIGTERM",s)}))}export{n as default};
1
+ import o from"../../extractor/ExtractionCompiler.js";import{hasLocalesToExtract as t}from"../../extractor/utils.js";import{isDevelopment as r,isNextBuild as e}from"../config.js";import{once as s}from"../utils.js";let i;const c=s("_NEXT_INTL_EXTRACT");function n(s){if(!s||!t(s))return;(r||e)&&c((()=>{function t(){i&&(i[Symbol.dispose](),i=void 0)}i=new o(s,{isDevelopment:r,projectRoot:process.cwd()}),i.extractAll(),process.on("exit",t),process.on("SIGINT",t),process.on("SIGTERM",t)}))}export{n as default};
@@ -1 +1 @@
1
- import e from"fs";import{createRequire as t}from"module";import s from"path";import{getFormatExtension as r}from"../extractor/format/index.js";import o from"../extractor/source/SourceFileFilter.js";import{isDevelopmentOrNextBuild as n}from"./config.js";import{isNextJs16OrHigher as a,hasStableTurboConfig as i}from"./nextFlags.js";import{throwError as l}from"./utils.js";const u=t(import.meta.url);function c(e){return[`${e}.ts`,`${e}.tsx`,`${e}.js`,`${e}.jsx`]}function m(t,r){function o(t){return e.existsSync(function(e){const t=[];return r&&t.push(r),t.push(e),s.resolve(...t)}(t))}if(t)return n&&!o(t)&&l(`Could not find i18n config at ${t}, please provide a valid path.`),t;for(const e of[...c("./i18n/request"),...c("./src/i18n/request")])if(o(e))return e;return n&&l("Could not locate request configuration module.\n\nThis path is supported by default: ./(src/)i18n/request.{js,jsx,ts,tsx}\n\nAlternatively, you can specify a custom location in your Next.js config:\n\nconst withNextIntl = createNextIntlPlugin(\n './path/to/i18n/request.tsx'\n);"),o("./src")?"./src/i18n/request.ts":"./i18n/request.ts"}function p(e,t){const n=null!=process.env.TURBOPACK,c=n||a(),p={};function x(){const t=e.experimental;return t.srcPath&&e.experimental?.messages||l("`srcPath` and `messages` are required when using `extractor`."),{loader:"next-intl/extractor/extractionLoader",options:{srcPath:t.srcPath,sourceLocale:t.extract.sourceLocale,messages:e.experimental.messages}}}function f(){return{loader:"next-intl/extractor/catalogLoader",options:{messages:e.experimental.messages}}}function g(){return t?.turbopack?.rules||t?.experimental?.turbo?.rules||{}}function d(e,t,s){e[t]?Array.isArray(e[t])?e[t].push(s):e[t]=[e[t],s]:e[t]=s}if(e.experimental?.messages){const t=e.experimental.messages;t.format||l("`format` is required when using `messages`."),t.path||l("`path` is required when using `messages`.")}if(c){e.requestConfig&&s.isAbsolute(e.requestConfig)&&l("Turbopack support for next-intl currently does not support absolute paths, please provide a relative one (e.g. './src/i18n/config.ts').\n\nFound: "+e.requestConfig);const n={"next-intl/config":m(e.requestConfig)};if(e.experimental?.messages?.precompile){let e=s.relative(process.cwd(),u.resolve("use-intl/format-message/format-only"));e.startsWith(".")||(e=`./${e}`),n["use-intl/format-message"]=e.replace(/\\/g,"/")}let c;if(e.experimental?.extract){a()||l("Message extraction requires Next.js 16 or higher."),c??=g();const t=(Array.isArray(e.experimental.srcPath)?e.experimental.srcPath:[e.experimental.srcPath]).map((e=>e.endsWith("/")?e.slice(0,-1):e));d(c,`*.{${o.EXTENSIONS.join(",")}}`,{loaders:[x()],condition:{path:`{${t.join(",")}}/**/*`,content:/(useExtracted|getExtracted)/}})}if(e.experimental?.messages){a()||l("Message catalog loading requires Next.js 16 or higher."),c??=g();d(c,`*${r(e.experimental.messages.format)}`,{loaders:[f()],condition:{path:`${e.experimental.messages.path}/**/*`},as:"*.js"})}i()&&!t?.experimental?.turbo?p.turbopack={...t?.turbopack,...c&&{rules:c},resolveAlias:{...t?.turbopack?.resolveAlias,...n}}:p.experimental={...t?.experimental,turbo:{...t?.experimental?.turbo,...c&&{rules:c},resolveAlias:{...t?.experimental?.turbo?.resolveAlias,...n}}}}return n||(p.webpack=function(n,a){if(n.resolve||(n.resolve={}),n.resolve.alias||(n.resolve.alias={}),n.resolve.alias["next-intl/config"]=s.resolve(n.context,m(e.requestConfig,n.context)),e.experimental?.messages?.precompile&&(n.resolve.alias["use-intl/format-message"]=u.resolve("use-intl/format-message/format-only")),e.experimental?.extract){n.module||(n.module={}),n.module.rules||(n.module.rules=[]);const t=e.experimental.srcPath;n.module.rules.push({test:new RegExp(`\\.(${o.EXTENSIONS.join("|")})$`),include:Array.isArray(t)?t.map((e=>s.resolve(n.context,e))):s.resolve(n.context,t||""),use:[x()]})}if(e.experimental?.messages){n.module||(n.module={}),n.module.rules||(n.module.rules=[]);const t=r(e.experimental.messages.format);n.module.rules.push({test:new RegExp(`${t.replace(/\./g,"\\.")}$`),include:s.resolve(n.context,e.experimental.messages.path),use:[f()],type:"javascript/auto"})}return"function"==typeof t?.webpack?t.webpack(n,a):n}),t?.trailingSlash&&(p.env={...t.env,_next_intl_trailing_slash:"true"}),Object.assign({},t,p)}export{p as default};
1
+ import e from"fs";import{createRequire as t}from"module";import r from"path";import{getFormatExtension as s}from"../extractor/format/index.js";import{normalizeMessagesCatalogPaths as o}from"../extractor/normalizeExtractorConfig.js";import n from"../extractor/source/SourceFileFilter.js";import{isDevelopmentOrNextBuild as i}from"./config.js";import{isNextJs16OrHigher as a,hasStableTurboConfig as l}from"./nextFlags.js";import{throwError as u}from"./utils.js";const c=t(import.meta.url);function m(e){return[`${e}.ts`,`${e}.tsx`,`${e}.js`,`${e}.jsx`]}function p(t,s){function o(t){return e.existsSync(function(e){const t=[];return s&&t.push(s),t.push(e),r.resolve(...t)}(t))}if(t)return i&&!o(t)&&u(`Could not find i18n config at ${t}, please provide a valid path.`),t;for(const e of[...m("./i18n/request"),...m("./src/i18n/request")])if(o(e))return e;return i&&u("Could not locate request configuration module.\n\nThis path is supported by default: ./(src/)i18n/request.{js,jsx,ts,tsx}\n\nAlternatively, you can specify a custom location in your Next.js config:\n\nconst withNextIntl = createNextIntlPlugin(\n './path/to/i18n/request.tsx'\n);"),o("./src")?"./src/i18n/request.ts":"./i18n/request.ts"}function f(e,t,i){const m=null!=process.env.TURBOPACK,f=m||a(),x={};let g=[];function d(e){return{loader:"next-intl/extractor/extractionLoader",options:e}}function v(){const t=e.experimental.messages;return{loader:"next-intl/extractor/catalogLoader",options:{messages:{format:t.format,...void 0!==t.precompile&&{precompile:t.precompile}}}}}function h(){return t?.turbopack?.rules||t?.experimental?.turbo?.rules||{}}function j(e,t,r){e[t]?Array.isArray(e[t])?e[t].push(r):e[t]=[e[t],r]:e[t]=r}if(e.experimental?.messages&&(g=o(e.experimental.messages.path)),f){e.requestConfig&&r.isAbsolute(e.requestConfig)&&u("Turbopack support for next-intl currently does not support absolute paths, please provide a relative one (e.g. './src/i18n/config.ts').\n\nFound: "+e.requestConfig);const o={"next-intl/config":p(e.requestConfig)};if(e.experimental?.messages?.precompile){let e=r.relative(process.cwd(),c.resolve("use-intl/format-message/format-only"));e.startsWith(".")||(e=`./${e}`),o["use-intl/format-message"]=e.replace(/\\/g,"/")}let m;if(e.experimental?.extract&&(a()||u("Message extraction requires Next.js 16 or higher."),m??=h(),j(m,`*.{${n.EXTENSIONS.join(",")}}`,{loaders:[d(i)],condition:{content:/(useExtracted|getExtracted)/}})),e.experimental?.messages){a()||u("Message catalog loading requires Next.js 16 or higher."),m??=h();j(m,`*${s(e.experimental.messages.format)}`,{loaders:[v()],condition:{path:`{${g.join(",")}}/**/*`},as:"*.js"})}l()&&!t?.experimental?.turbo?x.turbopack={...t?.turbopack,...m&&{rules:m},resolveAlias:{...t?.turbopack?.resolveAlias,...o}}:x.experimental={...t?.experimental,turbo:{...t?.experimental?.turbo,...m&&{rules:m},resolveAlias:{...t?.experimental?.turbo?.resolveAlias,...o}}}}return m||(x.webpack=function(o,a){if(o.resolve||(o.resolve={}),o.resolve.alias||(o.resolve.alias={}),o.resolve.alias["next-intl/config"]=r.resolve(o.context,p(e.requestConfig,o.context)),e.experimental?.messages?.precompile&&(o.resolve.alias["use-intl/format-message"]=c.resolve("use-intl/format-message/format-only")),e.experimental?.extract&&(o.module||(o.module={}),o.module.rules||(o.module.rules=[]),o.module.rules.push({test:new RegExp(`\\.(${n.EXTENSIONS.join("|")})$`),use:[d(i)]})),e.experimental?.messages){o.module||(o.module={}),o.module.rules||(o.module.rules=[]);const t=s(e.experimental.messages.format);o.module.rules.push({test:new RegExp(`${t.replace(/\./g,"\\.")}$`),include:g.map((e=>r.resolve(o.context,e))),use:[v()],type:"javascript/auto"})}return"function"==typeof t?.webpack?t.webpack(o,a):o}),t?.trailingSlash&&(x.env={...t.env,_next_intl_trailing_slash:"true"}),Object.assign({},t,x)}export{f as default};
@@ -1 +1 @@
1
- import{cache as r}from"react";import o from"./getServerTranslator.js";var t=r((function(r,t){const n=o(r,t);function i(...[r,o,t]){return n(void 0,o,t,void 0)}return i.rich=function(...[r,o,t]){return n.rich(void 0,o,t,void 0)},i.markup=function(...[r,o,t]){return n.markup(void 0,o,t,void 0)},i.has=function(...[r]){return!0},i}));export{t as default};
1
+ import{cache as t}from"react";import r from"./getServerTranslator.js";var a=t((function(t,a){throw r(t,a),new Error("[next-intl] `useExtracted` was called in production without compilation. Include modules that call `useExtracted` in `srcPath` and use `transpilePackages` for 3rd-party packages.")}));export{a as default};
@@ -3,10 +3,11 @@ import type { ExtractorConfig } from './types.js';
3
3
  export default class ExtractionCompiler implements Disposable {
4
4
  private manager;
5
5
  constructor(config: ExtractorConfig, opts?: {
6
+ extractor?: MessageExtractor;
6
7
  isDevelopment?: boolean;
7
8
  projectRoot?: string;
9
+ saveDebounceMs?: number;
8
10
  sourceMap?: boolean;
9
- extractor?: MessageExtractor;
10
11
  });
11
12
  extractAll(): Promise<void>;
12
13
  [Symbol.dispose](): void;
@@ -1,4 +1,4 @@
1
- import type { Locale, MessagesConfig } from '../types.js';
1
+ import type { ExtractorConfig, Locale } from '../types.js';
2
2
  type LocaleChangeCallback = (params: {
3
3
  added: Array<Locale>;
4
4
  removed: Array<Locale>;
@@ -7,7 +7,7 @@ type CatalogLocalesParams = {
7
7
  messagesDir: string;
8
8
  sourceLocale: Locale;
9
9
  extension: string;
10
- locales: MessagesConfig['locales'];
10
+ locales: ExtractorConfig['extract']['locales'];
11
11
  };
12
12
  export default class CatalogLocales {
13
13
  private messagesDir;
@@ -1,16 +1,27 @@
1
1
  import type MessageExtractor from '../extractor/MessageExtractor.js';
2
2
  import type { ExtractorConfig } from '../types.js';
3
3
  export default class CatalogManager implements Disposable {
4
+ /**
5
+ * Extraction-derived fields aggregated into `ExtractorMessage`.
6
+ * Source code is the source of truth for these fields, only ancillary
7
+ * codec fields may merge from disk (e.g. flags).
8
+ */
9
+ private static readonly extractorOwnedAggregatorKeys;
4
10
  private config;
5
11
  /**
6
- * The source of truth for which messages are used.
7
- * NOTE: Should be mutated in place to keep `messagesById` and `messagesByFile` in sync.
12
+ * Source of truth for statically extracted source messages,
13
+ * grouped by file and message ID.
14
+ */
15
+ private sourceMessagesByFile;
16
+ /**
17
+ * Reverse index for rebuilding aggregated messages without scanning all files.
18
+ * Contains the same `SourceMessage` arrays as `sourceMessagesByFile` and is
19
+ * kept in sync with it.
8
20
  */
9
- private messagesByFile;
21
+ private sourceMessagesById;
10
22
  /**
11
- * Fast lookup for messages by ID across all files,
12
- * contains the same messages as `messagesByFile`.
13
- * NOTE: Should be mutated in place to keep `messagesById` and `messagesByFile` in sync.
23
+ * Fast lookup for messages by ID, aggregated across all files. This combines
24
+ * metadata from `sourceMessagesById`, e.g. references and descriptions.
14
25
  */
15
26
  private messagesById;
16
27
  /**
@@ -30,10 +41,11 @@ export default class CatalogManager implements Disposable {
30
41
  private loadCatalogsPromise?;
31
42
  private scanCompletePromise?;
32
43
  constructor(config: ExtractorConfig, opts: {
33
- projectRoot?: string;
44
+ extractor: MessageExtractor;
34
45
  isDevelopment?: boolean;
46
+ projectRoot?: string;
47
+ saveDebounceMs?: number;
35
48
  sourceMap?: boolean;
36
- extractor: MessageExtractor;
37
49
  });
38
50
  private getCodec;
39
51
  private getPersister;
@@ -47,9 +59,14 @@ export default class CatalogManager implements Disposable {
47
59
  private reloadLocaleCatalog;
48
60
  private mergeSourceDiskMetadata;
49
61
  private processFile;
50
- private mergeReferences;
62
+ private extractFile;
63
+ private applyFileMessages;
64
+ private groupSourceMessagesById;
65
+ private rebuildMessageById;
66
+ private mergeDescriptions;
51
67
  private haveMessagesChangedForFile;
52
- private areMessagesEqual;
68
+ private areSourceMessageArraysEqual;
69
+ private areSourceMessagesEqual;
53
70
  save(): Promise<void>;
54
71
  private saveImpl;
55
72
  private saveLocale;
@@ -1,2 +1,2 @@
1
- import type { ExtractorConfig } from './types.js';
2
- export default function extractMessages(params: ExtractorConfig): Promise<void>;
1
+ import type { ExtractorConfigInput } from './types.js';
2
+ export default function extractMessages(params: ExtractorConfigInput): Promise<void>;
@@ -1,7 +1,4 @@
1
- import type { ExtractorMessage } from '../types.js';
2
- type StrictExtractedMessage = ExtractorMessage & {
3
- references: NonNullable<ExtractorMessage['references']>;
4
- };
1
+ import type { SourceMessage } from '../types.js';
5
2
  export default class MessageExtractor {
6
3
  private isDevelopment;
7
4
  private projectRoot;
@@ -13,9 +10,8 @@ export default class MessageExtractor {
13
10
  sourceMap?: boolean;
14
11
  });
15
12
  extract(absoluteFilePath: string, source: string): Promise<{
16
- messages: Array<StrictExtractedMessage>;
13
+ messages: Array<SourceMessage>;
17
14
  code: string;
18
15
  map?: string;
19
16
  }>;
20
17
  }
21
- export {};
@@ -0,0 +1,5 @@
1
+ import type { ExtractorConfig, ExtractorConfigInput } from './types.js';
2
+ export declare function normalizeMessagesCatalogPaths(messagesPath: string | Array<string>): Array<string>;
3
+ export default function normalizeExtractorConfig(input: Omit<ExtractorConfigInput, 'messages'> & {
4
+ messages?: ExtractorConfigInput['messages'];
5
+ }): ExtractorConfig;
@@ -4,25 +4,76 @@ export type ExtractorMessageReference = {
4
4
  path: string;
5
5
  line?: number;
6
6
  };
7
+ /** A single statically extracted source-code usage before any aggregation. */
8
+ export type SourceMessage = {
9
+ id: string;
10
+ message: string;
11
+ description: string | null;
12
+ reference: ExtractorMessageReference;
13
+ };
14
+ /** An aggregated message that can be read from or written to a catalog. */
7
15
  export type ExtractorMessage = {
8
16
  id: string;
9
17
  message: string;
10
- description?: string;
11
- references?: Array<ExtractorMessageReference>;
18
+ /**
19
+ * All unique descriptions attached to messages (e.g. multiple `#.` lines in PO).
20
+ * Ordered by source reference (path, then line).
21
+ */
22
+ description: Array<string>;
23
+ /**
24
+ * Source locations for this message (e.g. `#:` lines in PO). Ordered by path, then line.
25
+ * Empty when the catalog format does not store references or none are known.
26
+ */
27
+ references: Array<ExtractorMessageReference>;
12
28
  /** Allows for additional properties like .po flags to be read and later written. */
13
29
  [key: string]: unknown;
14
30
  };
15
- export type MessagesConfig = {
16
- path: string;
17
- format: MessagesFormat;
18
- locales: 'infer' | ReadonlyArray<Locale>;
19
- precompile?: boolean;
31
+ /**
32
+ * External extractor configuration (Next.js plugin, `extractMessages`).
33
+ */
34
+ export type ExtractorConfigInput = {
35
+ /**
36
+ * Relative path(s) to your source code files.
37
+ */
38
+ srcPath?: string | Array<string>;
39
+ messages: {
40
+ /** The format of your messages files. */
41
+ format: MessagesFormat;
42
+ /** Relative path(s) to your messages files. */
43
+ path: string | Array<string>;
44
+ /**
45
+ * Locales kept in sync with [`messages.sourceLocale`](https://next-intl.dev/docs/usage/plugin#messages-source-locale).
46
+ */
47
+ locales: 'infer' | ReadonlyArray<Locale>;
48
+ /** Locale to which extracted source strings are written. */
49
+ sourceLocale?: string;
50
+ };
51
+ /**
52
+ * Enables the usage of `useExtracted`.
53
+ */
54
+ extract?: true | {
55
+ /** Defaults to `messages.path` when it is a single path. */
56
+ path?: string;
57
+ /** @deprecated Prefer `messages.sourceLocale`. */
58
+ sourceLocale?: string;
59
+ };
20
60
  };
61
+ /** Normalized config used internally after `normalizeExtractorConfig`. */
21
62
  export type ExtractorConfig = {
22
- srcPath: string | Array<string>;
23
- sourceLocale: string;
24
- messages: MessagesConfig;
63
+ extract: {
64
+ locales: 'infer' | ReadonlyArray<Locale>;
65
+ path: string;
66
+ sourceLocale: string;
67
+ srcPath: string | Array<string>;
68
+ };
69
+ messages: {
70
+ format: MessagesFormat;
71
+ path: Array<string>;
72
+ };
25
73
  };
26
74
  export type CatalogLoaderConfig = {
27
- messages: MessagesConfig;
75
+ messages: {
76
+ format: MessagesFormat;
77
+ precompile?: boolean;
78
+ };
28
79
  };
@@ -1,6 +1,7 @@
1
- import type { ExtractorMessage, ExtractorMessageReference } from './types.js';
1
+ import type { ExtractorConfig, ExtractorMessage, ExtractorMessageReference } from './types.js';
2
2
  export declare function normalizePathToPosix(filePath: string): string;
3
3
  export declare function isForbiddenObjectKey(key: string): boolean;
4
+ export declare function hasLocalesToExtract(config: Pick<ExtractorConfig, 'extract'>): boolean;
4
5
  export declare function setNestedProperty(obj: Record<string, any>, keyPath: string, value: any): void;
5
6
  export declare function getSortedMessages(messages: Array<ExtractorMessage>): Array<ExtractorMessage>;
6
7
  export declare function localeCompare(a: string, b: string): number;
@@ -1,2 +1,2 @@
1
- import type { PluginConfig } from '../types.js';
2
- export default function initExtractionCompiler(pluginConfig: PluginConfig): void;
1
+ import type { ExtractorConfig } from '../../extractor/types.js';
2
+ export default function initExtractionCompiler(extractorConfig?: ExtractorConfig): void;
@@ -1,3 +1,4 @@
1
1
  import type { NextConfig } from 'next';
2
+ import type { ExtractorConfig } from '../extractor/types.js';
2
3
  import type { PluginConfig } from './types.js';
3
- export default function getNextConfig(pluginConfig: PluginConfig, nextConfig?: NextConfig): NextConfig & Partial<NextConfig>;
4
+ export default function getNextConfig(pluginConfig: PluginConfig, nextConfig?: NextConfig, extractorConfig?: ExtractorConfig): NextConfig & Partial<NextConfig>;
@@ -1,29 +1,25 @@
1
1
  import type { LoaderContext } from 'webpack';
2
- import type { MessagesFormat } from '../extractor/format/types.js';
2
+ import type { ExtractorConfigInput } from '../extractor/types.js';
3
3
  export type PluginConfig = {
4
4
  requestConfig?: string;
5
5
  experimental?: {
6
6
  /** A path to the messages file that you'd like to create a declaration for. In case you want to consider multiple files, you can pass an array of paths. */
7
7
  createMessagesDeclaration?: string | Array<string>;
8
- /** Relative path(s) to your source files, to be used in combination with `extract` and `messages`. */
8
+ /**
9
+ * Relative path(s) to your source code files.
10
+ */
9
11
  srcPath?: string | Array<string>;
10
- /** Configuration about your catalogs of messages, to be used in combination with `srcPath` and `extract`. */
11
- messages?: {
12
- /** Relative path to the directory containing your messages. */
13
- path: string;
14
- /** Defines the format for how your messages are stored. */
15
- format: MessagesFormat;
16
- /** Either automatically infer the locales based on catalog files in `path` or explicitly define them. */
17
- locales: 'infer' | ReadonlyArray<string>;
12
+ /** Configuration about your catalogs of messages */
13
+ messages?: ExtractorConfigInput['messages'] & {
18
14
  /**
19
15
  * When enabled, ICU messages are precompiled at build time, resulting in smaller bundles and faster message formatting.
20
16
  */
21
17
  precompile?: boolean;
22
18
  };
23
- /** Enables the usage of `useExtracted`, to be used in combination with `srcPath` and `messages`. */
24
- extract?: {
25
- sourceLocale: string;
26
- };
19
+ /**
20
+ * Enables the usage of [`useExtracted`](/docs/usage/extraction).
21
+ */
22
+ extract?: ExtractorConfigInput['extract'];
27
23
  };
28
24
  };
29
25
  export type TurbopackLoaderContext<Options> = Pick<LoaderContext<Options>, 'rootContext' | 'sourceMap' | 'getOptions' | 'getResolve' | 'emitWarning' | 'emitError' | 'getLogger' | 'context' | 'loaderIndex' | 'loaders' | 'resourcePath' | 'resourceQuery' | 'resourceFragment' | 'async' | 'callback' | 'cacheable' | 'addDependency' | 'dependency' | 'addContextDependency' | 'addMissingDependency' | 'getDependencies' | 'getContextDependencies' | 'getMissingDependencies' | 'clearDependencies' | 'resource' | 'request' | 'remainingRequest' | 'currentRequest' | 'previousRequest' | 'query' | 'data'>;