next-intl 4.6.1 → 4.7.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.
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var plugin = require('./plugin-DDtWCyPI.cjs');
3
+ var plugin = require('./plugin-B0KIcFlc.cjs');
4
4
  var ExtractorCodec = require('./ExtractorCodec-D9Tw618d.cjs');
5
5
  require('fs/promises');
6
6
  require('path');
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var POParser = require('po-parser');
4
- var plugin = require('./plugin-DDtWCyPI.cjs');
4
+ var plugin = require('./plugin-B0KIcFlc.cjs');
5
5
  var ExtractorCodec = require('./ExtractorCodec-D9Tw618d.cjs');
6
6
  require('fs/promises');
7
7
  require('path');
@@ -122,11 +122,11 @@ export default messages;`;
122
122
 
123
123
  const formats = {
124
124
  json: {
125
- codec: () => Promise.resolve().then(function () { return require('./JSONCodec-L1_VeQBi.cjs'); }),
125
+ codec: () => Promise.resolve().then(function () { return require('./JSONCodec-CusgMbzf.cjs'); }),
126
126
  extension: '.json'
127
127
  },
128
128
  po: {
129
- codec: () => Promise.resolve().then(function () { return require('./POCodec-Be_UL6jy.cjs'); }),
129
+ codec: () => Promise.resolve().then(function () { return require('./POCodec-Bns8JvnL.cjs'); }),
130
130
  extension: '.po'
131
131
  }
132
132
  };
@@ -352,18 +352,24 @@ function setNestedProperty(obj, keyPath, value) {
352
352
  }
353
353
  function getSortedMessages(messages) {
354
354
  return messages.toSorted((messageA, messageB) => {
355
- const pathA = messageA.references?.[0]?.path ?? '';
356
- const pathB = messageB.references?.[0]?.path ?? '';
357
- if (pathA === pathB) {
358
- return localeCompare(messageA.id, messageB.id);
359
- } else {
360
- return localeCompare(pathA, pathB);
361
- }
355
+ const refA = messageA.references?.[0];
356
+ const refB = messageB.references?.[0];
357
+
358
+ // No references: preserve original (extraction) order
359
+ if (!refA || !refB) return 0;
360
+
361
+ // Sort by path, then line. Same path+line: preserve original order
362
+ return compareReferences(refA, refB);
362
363
  });
363
364
  }
364
365
  function localeCompare(a, b) {
365
366
  return a.localeCompare(b, 'en');
366
367
  }
368
+ function compareReferences(refA, refB) {
369
+ const pathCompare = localeCompare(refA.path, refB.path);
370
+ if (pathCompare !== 0) return pathCompare;
371
+ return (refA.line ?? 0) - (refB.line ?? 0);
372
+ }
367
373
  function getDefaultProjectRoot() {
368
374
  return process.cwd();
369
375
  }
@@ -777,6 +783,7 @@ class CatalogManager {
777
783
  // ENOENT -> treat as no messages
778
784
  }
779
785
  const prevFileMessages = this.messagesByFile.get(absoluteFilePath);
786
+ const relativeFilePath = path__default.default.relative(this.projectRoot, absoluteFilePath);
780
787
 
781
788
  // Init with all previous ones
782
789
  const idsToRemove = Array.from(prevFileMessages?.keys() ?? []);
@@ -788,13 +795,12 @@ class CatalogManager {
788
795
 
789
796
  // Merge with previous message if it exists
790
797
  if (prevMessage) {
791
- const validated = prevMessage.references ?? [];
792
798
  message = {
793
- ...message,
794
- references: this.mergeReferences(validated, {
795
- path: path__default.default.relative(this.projectRoot, absoluteFilePath)
796
- })
799
+ ...message
797
800
  };
801
+ if (message.references) {
802
+ message.references = this.mergeReferences(prevMessage.references ?? [], relativeFilePath, message.references);
803
+ }
798
804
 
799
805
  // Merge other properties like description, or unknown
800
806
  // attributes like flags that are opaque to us
@@ -811,7 +817,6 @@ class CatalogManager {
811
817
  const index = idsToRemove.indexOf(message.id);
812
818
  if (index !== -1) idsToRemove.splice(index, 1);
813
819
  }
814
- const relativeFilePath = path__default.default.relative(this.projectRoot, absoluteFilePath);
815
820
 
816
821
  // Clean up removed messages from `messagesById`
817
822
  idsToRemove.forEach(id => {
@@ -837,13 +842,11 @@ class CatalogManager {
837
842
  const changed = this.haveMessagesChangedForFile(prevFileMessages, fileMessages);
838
843
  return changed;
839
844
  }
840
- mergeReferences(existing, current) {
841
- const dedup = new Map();
842
- for (const ref of existing) {
843
- dedup.set(ref.path, ref);
844
- }
845
- dedup.set(current.path, current);
846
- return Array.from(dedup.values()).sort((a, b) => localeCompare(a.path, b.path));
845
+ mergeReferences(existing, currentFilePath, currentFileRefs) {
846
+ // Keep refs from other files, replace all refs from the current file
847
+ const otherFileRefs = existing.filter(ref => ref.path !== currentFilePath);
848
+ const merged = [...otherFileRefs, ...currentFileRefs];
849
+ return merged.sort(compareReferences);
847
850
  }
848
851
  haveMessagesChangedForFile(beforeMessages, afterMessages) {
849
852
  // If one exists and the other doesn't, there's a change
@@ -981,7 +984,7 @@ class LRUCache {
981
984
  }
982
985
  }
983
986
 
984
- const require$1 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('plugin-DDtWCyPI.cjs', document.baseURI).href)));
987
+ const require$1 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('plugin-B0KIcFlc.cjs', document.baseURI).href)));
985
988
  class MessageExtractor {
986
989
  compileCache = (() => new LRUCache(750))();
987
990
  constructor(opts) {
@@ -1082,9 +1085,25 @@ function initExtractionCompiler(pluginConfig) {
1082
1085
  if (!experimental?.extract) {
1083
1086
  return;
1084
1087
  }
1088
+
1089
+ // Avoid rollup's `replace` plugin to compile this away
1090
+ const isDevelopment = process.env['NODE_ENV'.trim()] === 'development';
1091
+
1092
+ // Avoid running for:
1093
+ // - info
1094
+ // - start
1095
+ // - typegen
1096
+ //
1097
+ // Doesn't consult Next.js config anyway:
1098
+ // - telemetry
1099
+ // - lint
1100
+ //
1101
+ // What remains are:
1102
+ // - dev (NODE_ENV=development)
1103
+ // - build (NODE_ENV=production)
1104
+ const shouldRun = isDevelopment || process.argv.includes('build');
1105
+ if (!shouldRun) return;
1085
1106
  runOnce(() => {
1086
- // Avoid rollup's `replace` plugin to compile this away
1087
- const isDevelopment = process.env['NODE_ENV'.trim()] === 'development';
1088
1107
  const extractorConfig = {
1089
1108
  srcPath: experimental.srcPath,
1090
1109
  sourceLocale: experimental.extract.sourceLocale,
@@ -1117,7 +1136,7 @@ function initExtractionCompiler(pluginConfig) {
1117
1136
 
1118
1137
  function getCurrentVersion() {
1119
1138
  try {
1120
- const require$1 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('plugin-DDtWCyPI.cjs', document.baseURI).href)));
1139
+ const require$1 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('plugin-B0KIcFlc.cjs', document.baseURI).href)));
1121
1140
  const pkg = require$1('next/package.json');
1122
1141
  return pkg.version;
1123
1142
  } catch (error) {
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var plugin = require('./plugin-DDtWCyPI.cjs');
3
+ var plugin = require('./plugin-B0KIcFlc.cjs');
4
4
  require('fs/promises');
5
5
  require('path');
6
6
  require('@parcel/watcher');
@@ -3,7 +3,7 @@ import path from 'path';
3
3
  import { resolveCodec, getFormatExtension } from '../format/index.js';
4
4
  import SourceFileScanner from '../source/SourceFileScanner.js';
5
5
  import SourceFileWatcher from '../source/SourceFileWatcher.js';
6
- import { getDefaultProjectRoot, localeCompare } from '../utils.js';
6
+ import { getDefaultProjectRoot, compareReferences } from '../utils.js';
7
7
  import CatalogLocales from './CatalogLocales.js';
8
8
  import CatalogPersister from './CatalogPersister.js';
9
9
  import SaveScheduler from './SaveScheduler.js';
@@ -195,6 +195,7 @@ class CatalogManager {
195
195
  // ENOENT -> treat as no messages
196
196
  }
197
197
  const prevFileMessages = this.messagesByFile.get(absoluteFilePath);
198
+ const relativeFilePath = path.relative(this.projectRoot, absoluteFilePath);
198
199
 
199
200
  // Init with all previous ones
200
201
  const idsToRemove = Array.from(prevFileMessages?.keys() ?? []);
@@ -206,13 +207,12 @@ class CatalogManager {
206
207
 
207
208
  // Merge with previous message if it exists
208
209
  if (prevMessage) {
209
- const validated = prevMessage.references ?? [];
210
210
  message = {
211
- ...message,
212
- references: this.mergeReferences(validated, {
213
- path: path.relative(this.projectRoot, absoluteFilePath)
214
- })
211
+ ...message
215
212
  };
213
+ if (message.references) {
214
+ message.references = this.mergeReferences(prevMessage.references ?? [], relativeFilePath, message.references);
215
+ }
216
216
 
217
217
  // Merge other properties like description, or unknown
218
218
  // attributes like flags that are opaque to us
@@ -229,7 +229,6 @@ class CatalogManager {
229
229
  const index = idsToRemove.indexOf(message.id);
230
230
  if (index !== -1) idsToRemove.splice(index, 1);
231
231
  }
232
- const relativeFilePath = path.relative(this.projectRoot, absoluteFilePath);
233
232
 
234
233
  // Clean up removed messages from `messagesById`
235
234
  idsToRemove.forEach(id => {
@@ -255,13 +254,11 @@ class CatalogManager {
255
254
  const changed = this.haveMessagesChangedForFile(prevFileMessages, fileMessages);
256
255
  return changed;
257
256
  }
258
- mergeReferences(existing, current) {
259
- const dedup = new Map();
260
- for (const ref of existing) {
261
- dedup.set(ref.path, ref);
262
- }
263
- dedup.set(current.path, current);
264
- return Array.from(dedup.values()).sort((a, b) => localeCompare(a.path, b.path));
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);
265
262
  }
266
263
  haveMessagesChangedForFile(beforeMessages, afterMessages) {
267
264
  // If one exists and the other doesn't, there's a change
@@ -13,20 +13,26 @@ function setNestedProperty(obj, keyPath, value) {
13
13
  }
14
14
  function getSortedMessages(messages) {
15
15
  return messages.toSorted((messageA, messageB) => {
16
- const pathA = messageA.references?.[0]?.path ?? '';
17
- const pathB = messageB.references?.[0]?.path ?? '';
18
- if (pathA === pathB) {
19
- return localeCompare(messageA.id, messageB.id);
20
- } else {
21
- return localeCompare(pathA, pathB);
22
- }
16
+ const refA = messageA.references?.[0];
17
+ const refB = messageB.references?.[0];
18
+
19
+ // No references: preserve original (extraction) order
20
+ if (!refA || !refB) return 0;
21
+
22
+ // Sort by path, then line. Same path+line: preserve original order
23
+ return compareReferences(refA, refB);
23
24
  });
24
25
  }
25
26
  function localeCompare(a, b) {
26
27
  return a.localeCompare(b, 'en');
27
28
  }
29
+ function compareReferences(refA, refB) {
30
+ const pathCompare = localeCompare(refA.path, refB.path);
31
+ if (pathCompare !== 0) return pathCompare;
32
+ return (refA.line ?? 0) - (refB.line ?? 0);
33
+ }
28
34
  function getDefaultProjectRoot() {
29
35
  return process.cwd();
30
36
  }
31
37
 
32
- export { getDefaultProjectRoot, getSortedMessages, localeCompare, setNestedProperty };
38
+ export { compareReferences, getDefaultProjectRoot, getSortedMessages, localeCompare, setNestedProperty };
@@ -9,9 +9,25 @@ function initExtractionCompiler(pluginConfig) {
9
9
  if (!experimental?.extract) {
10
10
  return;
11
11
  }
12
+
13
+ // Avoid rollup's `replace` plugin to compile this away
14
+ const isDevelopment = process.env['NODE_ENV'.trim()] === 'development';
15
+
16
+ // Avoid running for:
17
+ // - info
18
+ // - start
19
+ // - typegen
20
+ //
21
+ // Doesn't consult Next.js config anyway:
22
+ // - telemetry
23
+ // - lint
24
+ //
25
+ // What remains are:
26
+ // - dev (NODE_ENV=development)
27
+ // - build (NODE_ENV=production)
28
+ const shouldRun = isDevelopment || process.argv.includes('build');
29
+ if (!shouldRun) return;
12
30
  runOnce(() => {
13
- // Avoid rollup's `replace` plugin to compile this away
14
- const isDevelopment = process.env['NODE_ENV'.trim()] === 'development';
15
31
  const extractorConfig = {
16
32
  srcPath: experimental.srcPath,
17
33
  sourceLocale: experimental.extract.sourceLocale,
@@ -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,localeCompare as c}from"../utils.js";import l from"./CatalogLocales.js";import n from"./CatalogPersister.js";import h from"./SaveScheduler.js";class g{messagesByFile=(()=>new Map)();messagesById=(()=>new Map)();translationsByTargetLocale=(()=>new Map)();lastWriteByLocale=(()=>new Map)();constructor(e,s){this.config=e,this.saveScheduler=new h(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 n({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 l({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=Array.from(o?.keys()??[]),r=new Map;for(let e of a){const a=this.messagesById.get(e.id);if(a){const o=a.references??[];e={...e,references:this.mergeReferences(o,{path:s.relative(this.projectRoot,t)})};for(const s of Object.keys(a))null==e[s]&&(e[s]=a[s])}this.messagesById.set(e.id,e),r.set(e.id,e);const o=i.indexOf(e.id);-1!==o&&i.splice(o,1)}const c=s.relative(this.projectRoot,t);i.forEach((e=>{const s=this.messagesById.get(e);if(!s)return;const t=s.references?.some((e=>e.path!==c));t?s.references=s.references?.filter((e=>e.path!==c)):this.messagesById.delete(e)})),a.length>0?this.messagesByFile.set(t,r):this.messagesByFile.delete(t);return this.haveMessagesChangedForFile(o,r)}mergeReferences(e,s){const t=new Map;for(const s of e)t.set(s.path,s);return t.set(s.path,s),Array.from(t.values()).sort(((e,s)=>c(e.path,s.path)))}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{g 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 i from"../source/SourceFileWatcher.js";import{getDefaultProjectRoot as r,compareReferences as c}from"../utils.js";import l from"./CatalogLocales.js";import n from"./CatalogPersister.js";import h from"./SaveScheduler.js";class g{messagesByFile=(()=>new Map)();messagesById=(()=>new Map)();translationsByTargetLocale=(()=>new Map)();lastWriteByLocale=(()=>new Map)();constructor(e,s){this.config=e,this.saveScheduler=new h(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 n({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 l({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=s.relative(this.projectRoot,t),r=Array.from(o?.keys()??[]),c=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),c.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,c):this.messagesByFile.delete(t);return this.haveMessagesChangedForFile(o,c)}mergeReferences(e,s,t){return[...e.filter((e=>e.path!==s)),...t].sort(c)}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{g as default};
@@ -1 +1 @@
1
- function e(e,t,n){const r=t.split(".");let o=e;for(let e=0;e<r.length-1;e++){const t=r[e];t in o&&"object"==typeof o[t]&&null!==o[t]||(o[t]={}),o=o[t]}o[r[r.length-1]]=n}function t(e){return e.toSorted(((e,t)=>{const r=e.references?.[0]?.path??"",o=t.references?.[0]?.path??"";return r===o?n(e.id,t.id):n(r,o)}))}function n(e,t){return e.localeCompare(t,"en")}function r(){return process.cwd()}export{r as getDefaultProjectRoot,t as getSortedMessages,n as localeCompare,e as setNestedProperty};
1
+ function n(n,e,t){const r=e.split(".");let o=n;for(let n=0;n<r.length-1;n++){const e=r[n];e in o&&"object"==typeof o[e]&&null!==o[e]||(o[e]={}),o=o[e]}o[r[r.length-1]]=t}function e(n){return n.toSorted(((n,e)=>{const t=n.references?.[0],o=e.references?.[0];return t&&o?r(t,o):0}))}function t(n,e){return n.localeCompare(e,"en")}function r(n,e){const r=t(n.path,e.path);return 0!==r?r:(n.line??0)-(e.line??0)}function o(){return process.cwd()}export{r as compareReferences,o as getDefaultProjectRoot,e as getSortedMessages,t as localeCompare,n as setNestedProperty};
@@ -1 +1 @@
1
- import e from"../../extractor/ExtractionCompiler.js";import{once as o}from"../utils.js";let s;const t=o("_NEXT_INTL_EXTRACT");function r(o){const r=o.experimental;r?.extract&&t((()=>{const o="development"===process.env["NODE_ENV".trim()],t={srcPath:r.srcPath,sourceLocale:r.extract.sourceLocale,messages:r.messages};function c(){s&&(s[Symbol.dispose](),s=void 0)}s=new e(t,{isDevelopment:o,projectRoot:process.cwd()}),s.extractAll(),process.on("exit",c),process.on("SIGINT",c),process.on("SIGTERM",c)}))}export{r as default};
1
+ import e from"../../extractor/ExtractionCompiler.js";import{once as o}from"../utils.js";let s;const t=o("_NEXT_INTL_EXTRACT");function r(o){const r=o.experimental;if(!r?.extract)return;const c="development"===process.env["NODE_ENV".trim()];(c||process.argv.includes("build"))&&t((()=>{const o={srcPath:r.srcPath,sourceLocale:r.extract.sourceLocale,messages:r.messages};function t(){s&&(s[Symbol.dispose](),s=void 0)}s=new e(o,{isDevelopment:c,projectRoot:process.cwd()}),s.extractAll(),process.on("exit",t),process.on("SIGINT",t),process.on("SIGTERM",t)}))}export{r as default};
@@ -1,12 +1,14 @@
1
1
  import type { MessagesFormat } from './format/types.js';
2
2
  export type Locale = string;
3
+ export type ExtractorMessageReference = {
4
+ path: string;
5
+ line?: number;
6
+ };
3
7
  export type ExtractorMessage = {
4
8
  id: string;
5
9
  message: string;
6
10
  description?: string;
7
- references?: Array<{
8
- path: string;
9
- }>;
11
+ references?: Array<ExtractorMessageReference>;
10
12
  /** Allows for additional properties like .po flags to be read and later written. */
11
13
  [key: string]: unknown;
12
14
  };
@@ -1,5 +1,6 @@
1
- import type { ExtractorMessage } from './types.js';
1
+ import type { ExtractorMessage, ExtractorMessageReference } from './types.js';
2
2
  export declare function setNestedProperty(obj: Record<string, any>, keyPath: string, value: any): void;
3
3
  export declare function getSortedMessages(messages: Array<ExtractorMessage>): Array<ExtractorMessage>;
4
4
  export declare function localeCompare(a: string, b: string): number;
5
+ export declare function compareReferences(refA: ExtractorMessageReference, refB: ExtractorMessageReference): number;
5
6
  export declare function getDefaultProjectRoot(): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-intl",
3
- "version": "4.6.1",
3
+ "version": "4.7.0",
4
4
  "sideEffects": false,
5
5
  "author": "Jan Amann <jan@amann.work>",
6
6
  "funding": [
@@ -128,9 +128,9 @@
128
128
  "@parcel/watcher": "^2.4.1",
129
129
  "@swc/core": "^1.15.2",
130
130
  "negotiator": "^1.0.0",
131
- "next-intl-swc-plugin-extractor": "^4.6.1",
132
- "po-parser": "^2.0.0",
133
- "use-intl": "^4.6.1"
131
+ "next-intl-swc-plugin-extractor": "^4.7.0",
132
+ "po-parser": "^2.1.1",
133
+ "use-intl": "^4.7.0"
134
134
  },
135
135
  "peerDependencies": {
136
136
  "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
@@ -142,5 +142,5 @@
142
142
  "optional": true
143
143
  }
144
144
  },
145
- "gitHead": "dd3a0472fd142439527f7c8b80e9af0135af211b"
145
+ "gitHead": "f7da9b11eb59dcc83525500ff9af14110ea9bea9"
146
146
  }