next-intl 4.13.7 → 4.14.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 (39) hide show
  1. package/dist/cjs/development/BuiltInPoCodec-B6w-kmTx.cjs +34 -0
  2. package/dist/cjs/development/plugin.cjs +1607 -8
  3. package/dist/esm/development/extractor/catalog/CatalogManager.js +3 -2
  4. package/dist/esm/development/extractor/catalog/CatalogPersister.js +21 -2
  5. package/dist/esm/development/extractor/catalogLoader.js +38 -16
  6. package/dist/esm/development/extractor/extractor/MessageExtractor.js +8 -1
  7. package/dist/esm/development/extractor/format/ExtractorCodec.js +3 -0
  8. package/dist/esm/development/extractor/format/codecs/BuiltInPoCodec.js +22 -0
  9. package/dist/esm/development/extractor/format/index.js +19 -4
  10. package/dist/esm/development/extractor/utils.js +8 -27
  11. package/dist/esm/development/plugin/getNextConfig.js +6 -2
  12. package/dist/esm/production/extractor/catalog/CatalogManager.js +1 -1
  13. package/dist/esm/production/extractor/catalog/CatalogPersister.js +1 -1
  14. package/dist/esm/production/extractor/catalogLoader.js +1 -1
  15. package/dist/esm/production/extractor/extractor/MessageExtractor.js +1 -1
  16. package/dist/esm/production/extractor/format/codecs/BuiltInPoCodec.js +1 -0
  17. package/dist/esm/production/extractor/format/index.js +1 -1
  18. package/dist/esm/production/extractor/utils.js +1 -1
  19. package/dist/esm/production/plugin/getNextConfig.js +1 -1
  20. package/dist/types/extractor/catalog/CatalogManager.d.ts +1 -1
  21. package/dist/types/extractor/catalog/CatalogPersister.d.ts +2 -0
  22. package/dist/types/extractor/extractor/MessageExtractor.d.ts +2 -2
  23. package/dist/types/extractor/format/ExtractorCodec.d.ts +11 -27
  24. package/dist/types/extractor/format/codecs/fixtures/POCodecLegacy.d.ts +50 -0
  25. package/dist/types/extractor/format/index.d.ts +2 -2
  26. package/dist/types/extractor/types.d.ts +9 -21
  27. package/dist/types/extractor/utils.d.ts +1 -1
  28. package/package.json +9 -12
  29. package/dist/cjs/development/ExtractorCodec-D9Tw618d.cjs +0 -7
  30. package/dist/cjs/development/JSONCodec-CzA8ubPy.cjs +0 -55
  31. package/dist/cjs/development/POCodec-CWGHK-Gp.cjs +0 -111
  32. package/dist/cjs/development/plugin-DlFYUFWh.cjs +0 -1613
  33. package/dist/esm/development/extractor/format/codecs/JSONCodec.js +0 -47
  34. package/dist/esm/development/extractor/format/codecs/POCodec.js +0 -99
  35. package/dist/esm/production/extractor/format/codecs/JSONCodec.js +0 -1
  36. package/dist/esm/production/extractor/format/codecs/POCodec.js +0 -1
  37. package/dist/types/extractor/format/codecs/POCodec.d.ts +0 -2
  38. package/dist/types/extractor/format/codecs/fixtures/POCodecSourceMessageKey.d.ts +0 -2
  39. /package/dist/types/extractor/format/codecs/{JSONCodec.d.ts → BuiltInPoCodec.d.ts} +0 -0
@@ -23,7 +23,7 @@ class CatalogManager {
23
23
 
24
24
  /**
25
25
  * Reverse index for rebuilding aggregated messages without scanning all files.
26
- * Contains the same `SourceMessage` arrays as `sourceMessagesByFile` and is
26
+ * Contains the same `SourceExtractedMessage` arrays as `sourceMessagesByFile` and is
27
27
  * kept in sync with it.
28
28
  */
29
29
  sourceMessagesById = new Map();
@@ -74,7 +74,8 @@ class CatalogManager {
74
74
  this.persister = new CatalogPersister({
75
75
  messagesPath: this.config.extract.path,
76
76
  codec: await this.getCodec(),
77
- extension: getFormatExtension(this.config.messages.format)
77
+ extension: getFormatExtension(this.config.messages.format),
78
+ sourceLocale: this.config.extract.sourceLocale
78
79
  });
79
80
  return this.persister;
80
81
  }
@@ -1,11 +1,13 @@
1
1
  import fs from 'fs/promises';
2
2
  import path from 'path';
3
+ import { warnAboutMissingReferences } from '../utils.js';
3
4
 
4
5
  class CatalogPersister {
5
6
  constructor(params) {
6
7
  this.messagesPath = params.messagesPath;
7
8
  this.codec = params.codec;
8
9
  this.extension = params.extension;
10
+ this.sourceLocale = params.sourceLocale;
9
11
  }
10
12
  getFileName(locale) {
11
13
  return locale + this.extension;
@@ -27,9 +29,12 @@ class CatalogPersister {
27
29
  });
28
30
  }
29
31
  try {
30
- return this.codec.decode(content, {
31
- locale
32
+ const messages = this.codec.decode(content, {
33
+ locale,
34
+ sourceLocale: this.sourceLocale
32
35
  });
36
+ assertNoArrayMessages(messages);
37
+ return messages;
33
38
  } catch (error) {
34
39
  throw new Error(`Error while decoding ${this.getFileName(locale)}:\n> ${error}`, {
35
40
  cause: error
@@ -38,6 +43,7 @@ class CatalogPersister {
38
43
  }
39
44
  async write(messages, context) {
40
45
  const filePath = this.getFilePath(context.locale);
46
+ warnAboutMissingReferences(messages);
41
47
  const content = this.codec.encode(messages, context);
42
48
  try {
43
49
  const outputDir = path.dirname(filePath);
@@ -60,4 +66,17 @@ class CatalogPersister {
60
66
  }
61
67
  }
62
68
 
69
+ // The codecs read arrays fine (numeric path segments), but the runtime only
70
+ // supports strings — enforced here where the runtime's rules apply.
71
+ function assertNoArrayMessages(messages) {
72
+ for (const message of messages) {
73
+ const segments = message.segments;
74
+ if (!Array.isArray(segments)) continue;
75
+ const firstIndex = segments.findIndex(segment => typeof segment === 'number');
76
+ if (firstIndex === -1) continue;
77
+ const arrayPath = segments.slice(0, firstIndex).join('.');
78
+ throw new Error(`Message at \`${arrayPath}\` resolved to an array, but only strings are supported. See https://next-intl.dev/docs/usage/translations#arrays-of-messages`);
79
+ }
80
+ }
81
+
63
82
  export { CatalogPersister as default };
@@ -6,6 +6,7 @@ import { setNestedProperty } from './utils.js';
6
6
  // The module scope is safe for some caching, but Next.js can
7
7
  // create multiple loader instances so don't expect a singleton.
8
8
  let cachedCodec = null;
9
+ let hasWarnedAboutToJSONString = false;
9
10
  const messageCacheByCatalog = new Map();
10
11
  function getMessageCache(catalogId) {
11
12
  let cache = messageCacheByCatalog.get(catalogId);
@@ -34,26 +35,52 @@ function catalogLoader(source) {
34
35
  const callback = this.async();
35
36
  const extension = getFormatExtension(options.messages.format);
36
37
  getCodec(options, this.rootContext).then(codec => {
38
+ warnIfToJSONStringProvided(codec);
37
39
  const locale = path.basename(this.resourcePath, extension);
38
- let outputString;
40
+ const decoded = codec.decode(source, {
41
+ locale,
42
+ sourceLocale: options.messages.sourceLocale
43
+ });
44
+ let messages;
39
45
  if (options.messages.precompile) {
40
- const decoded = codec.decode(source, {
41
- locale
42
- });
43
46
  const cache = getMessageCache(this.resourcePath);
44
- const precompiled = precompileMessages(decoded, cache);
45
- outputString = JSON.stringify(precompiled);
47
+ messages = precompileMessages(decoded, cache);
46
48
  } else {
47
- outputString = codec.toJSONString(source, {
48
- locale
49
- });
49
+ messages = buildMessages(decoded);
50
50
  }
51
51
 
52
52
  // https://v8.dev/blog/cost-of-javascript-2019#json
53
- const result = `export default JSON.parse(${JSON.stringify(outputString)});`;
53
+ const result = `export default JSON.parse(${JSON.stringify(JSON.stringify(messages))});`;
54
54
  callback(null, result);
55
55
  }).catch(callback);
56
56
  }
57
+ function warnIfToJSONStringProvided(codec) {
58
+ if (hasWarnedAboutToJSONString || !codec.toJSONString) return;
59
+ hasWarnedAboutToJSONString = true;
60
+ console.warn('`toJSONString` is deprecated and no longer needed, please remove it.');
61
+ }
62
+
63
+ /**
64
+ * Builds a nested messages object from decoded messages, mirroring the shape
65
+ * produced by `precompileMessages` but keeping the raw message strings.
66
+ */
67
+ function buildMessages(messages) {
68
+ const result = Object.create(null);
69
+ for (const message of messages) {
70
+ assertStringMessage(message);
71
+ setNestedProperty(result, message.id, message.message);
72
+ }
73
+ return result;
74
+ }
75
+ function assertStringMessage(message) {
76
+ const messageValue = message.message;
77
+ if (Array.isArray(messageValue)) {
78
+ throw new Error(`Message at \`${message.id}\` resolved to an array, but only strings are supported. See https://next-intl.dev/docs/usage/translations#arrays-of-messages`);
79
+ }
80
+ if (typeof messageValue === 'object') {
81
+ throw new Error(`Message at \`${message.id}\` resolved to \`${typeof messageValue}\`, but only strings are supported. Use a \`.\` to retrieve nested messages. See https://next-intl.dev/docs/usage/translations#structuring-messages`);
82
+ }
83
+ }
57
84
 
58
85
  /**
59
86
  * Recursively precompiles all ICU message strings in a messages object
@@ -64,13 +91,8 @@ function precompileMessages(messages, cache) {
64
91
  const cacheKeysToEvict = new Set(cache.keys());
65
92
  for (const message of messages) {
66
93
  cacheKeysToEvict.delete(message.id);
94
+ assertStringMessage(message);
67
95
  const messageValue = message.message;
68
- if (Array.isArray(messageValue)) {
69
- throw new Error(`Message at \`${message.id}\` resolved to an array, but only strings are supported. See https://next-intl.dev/docs/usage/translations#arrays-of-messages`);
70
- }
71
- if (typeof messageValue === 'object') {
72
- throw new Error(`Message at \`${message.id}\` resolved to \`${typeof messageValue}\`, but only strings are supported. Use a \`.\` to retrieve nested messages. See https://next-intl.dev/docs/usage/translations#structuring-messages`);
73
- }
74
96
  const cachedEntry = cache.get(message.id);
75
97
  const hasCacheMatch = cachedEntry?.messageValue === messageValue;
76
98
  let compiledMessage;
@@ -52,7 +52,14 @@ class MessageExtractor {
52
52
 
53
53
  // TODO: Improve the typing of @swc/core
54
54
  const output = result.output;
55
- const messages = JSON.parse(JSON.parse(output).results);
55
+ // The plugin emits a tagged union of extracted messages and
56
+ // `useTranslations` usages; this extractor only consumes the former.
57
+ const messages = JSON.parse(JSON.parse(output).results).filter(item => item.type === 'extracted').map(item => ({
58
+ id: item.id,
59
+ message: item.message,
60
+ description: item.description,
61
+ reference: item.reference
62
+ }));
56
63
  const extractionResult = {
57
64
  code: result.code,
58
65
  map: result.map,
@@ -1,3 +1,6 @@
1
+ // Derived from `@eloqnt/config`, so next-intl and eloqnt pass identical
2
+ // options to codecs.
3
+
1
4
  function defineCodec(factory) {
2
5
  return factory;
3
6
  }
@@ -0,0 +1,22 @@
1
+ import createPoCodec from '@eloqnt/format-po';
2
+ import { defineCodec } from '../ExtractorCodec.js';
3
+
4
+ // `@eloqnt/format-po`, guarded so catalogs in the previous key-based layout
5
+ // fail loudly. Custom codecs referencing the package directly opt out.
6
+ var BuiltInPoCodec = defineCodec(() => {
7
+ const codec = createPoCodec();
8
+ function assertMigrated(content) {
9
+ if (content.includes('X-Crowdin-SourceKey: msgstr')) {
10
+ throw new Error('This catalog uses the previous PO layout of next-intl, which stored message keys in `msgid`. The PO format now follows the gettext convention: the source text in `msgid` and the message key in `msgctxt`. Please migrate your catalogs, or use a custom codec to keep the previous layout: https://github.com/amannn/next-intl/pull/2393');
11
+ }
12
+ }
13
+ return {
14
+ ...codec,
15
+ decode(content, context) {
16
+ assertMigrated(content);
17
+ return codec.decode(content, context);
18
+ }
19
+ };
20
+ });
21
+
22
+ export { BuiltInPoCodec as default };
@@ -1,13 +1,15 @@
1
+ import { createRequire } from 'module';
1
2
  import path from 'path';
3
+ import { pathToFileURL } from 'url';
2
4
  import { throwError } from '../../plugin/utils.js';
3
5
 
4
6
  const formats = {
5
7
  json: {
6
- codec: () => import('./codecs/JSONCodec.js'),
8
+ codec: () => import('@eloqnt/format-json'),
7
9
  extension: '.json'
8
10
  },
9
11
  po: {
10
- codec: () => import('./codecs/POCodec.js'),
12
+ codec: () => import('./codecs/BuiltInPoCodec.js'),
11
13
  extension: '.po'
12
14
  }
13
15
  };
@@ -26,10 +28,23 @@ async function resolveCodec(format, projectRoot) {
26
28
  const factory = (await formats[format].codec()).default;
27
29
  return factory();
28
30
  } else {
29
- const resolvedPath = path.isAbsolute(format.codec) ? format.codec : path.resolve(projectRoot, format.codec);
31
+ // A codec that starts with `.` or a filesystem root is a file path;
32
+ // everything else reads as a package specifier, mirroring how imports
33
+ // distinguish the two. Packages resolve from the project's dependencies.
34
+ const isPath = format.codec.startsWith('.') || path.isAbsolute(format.codec);
35
+ let resolvedPath;
36
+ if (isPath) {
37
+ resolvedPath = path.resolve(projectRoot, format.codec);
38
+ } else {
39
+ try {
40
+ resolvedPath = createRequire(path.join(projectRoot, 'noop.js')).resolve(format.codec);
41
+ } catch (error) {
42
+ throwError(`Could not resolve the codec package "${format.codec}". Is it installed?\n${error}`);
43
+ }
44
+ }
30
45
  let module;
31
46
  try {
32
- module = await import(resolvedPath);
47
+ module = await import(pathToFileURL(resolvedPath).href);
33
48
  } catch (error) {
34
49
  throwError(`Could not load codec from "${resolvedPath}".\n${error}`);
35
50
  }
@@ -35,34 +35,15 @@ function setNestedProperty(obj, keyPath, value) {
35
35
  }
36
36
  current[keys[keys.length - 1]] = value;
37
37
  }
38
- function getSortedMessages(messages) {
39
- const warnedMissingReferenceIds = new Set();
40
- return messages.toSorted((messageA, messageB) => {
41
- const refA = messageA.references[0];
42
- const refB = messageB.references[0];
43
-
44
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
45
- if (refA == null) {
46
- warnAboutMissingReference(messageA.id, warnedMissingReferenceIds);
47
- }
38
+ function warnAboutMissingReferences(messages) {
39
+ const warnedIds = new Set();
40
+ for (const message of messages) {
48
41
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
49
- if (refB == null) {
50
- warnAboutMissingReference(messageB.id, warnedMissingReferenceIds);
42
+ if (message.references[0] == null && !warnedIds.has(message.id)) {
43
+ warnedIds.add(message.id);
44
+ warn(`Missing file reference for extracted message: ${message.id}`);
51
45
  }
52
-
53
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
54
- if (refA == null || refB == null) {
55
- return 0;
56
- }
57
-
58
- // Sort by path, then line. Same path+line: preserve original order
59
- return compareReferences(refA, refB);
60
- });
61
- }
62
- function warnAboutMissingReference(id, warnedMissingReferenceIds) {
63
- if (warnedMissingReferenceIds.has(id)) return;
64
- warnedMissingReferenceIds.add(id);
65
- warn(`Missing file reference for extracted message: ${id}`);
46
+ }
66
47
  }
67
48
  function localeCompare(a, b) {
68
49
  return a.localeCompare(b, 'en');
@@ -76,4 +57,4 @@ function getDefaultProjectRoot() {
76
57
  return process.cwd();
77
58
  }
78
59
 
79
- export { compareReferences, getDefaultProjectRoot, getSortedMessages, hasLocalesToExtract, isForbiddenObjectKey, localeCompare, normalizePathToPosix, setNestedProperty };
60
+ export { compareReferences, getDefaultProjectRoot, hasLocalesToExtract, isForbiddenObjectKey, localeCompare, normalizePathToPosix, setNestedProperty, warnAboutMissingReferences };
@@ -72,7 +72,10 @@ function getNextConfig(pluginConfig, nextConfig, extractorConfig) {
72
72
  };
73
73
  }
74
74
  function getCatalogLoaderConfig() {
75
- const messages = pluginConfig.experimental.messages;
75
+ const experimental = pluginConfig.experimental;
76
+ const messages = experimental.messages;
77
+ // Normalization enforces a source locale before extraction runs.
78
+ const sourceLocale = messages.sourceLocale ?? (typeof experimental.extract === 'object' ? experimental.extract.sourceLocale : undefined);
76
79
  return {
77
80
  loader: 'next-intl/extractor/catalogLoader',
78
81
  options: {
@@ -80,7 +83,8 @@ function getNextConfig(pluginConfig, nextConfig, extractorConfig) {
80
83
  format: messages.format,
81
84
  ...(messages.precompile !== undefined && {
82
85
  precompile: messages.precompile
83
- })
86
+ }),
87
+ sourceLocale
84
88
  }
85
89
  }
86
90
  };
@@ -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 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
+ 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),sourceLocale:this.config.extract.sourceLocale})),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 e from"fs/promises";import t from"path";class r{constructor(e){this.messagesPath=e.messagesPath,this.codec=e.codec,this.extension=e.extension}getFileName(e){return e+this.extension}getFilePath(e){return t.join(this.messagesPath,this.getFileName(e))}async read(t){const r=this.getFilePath(t);let i;try{i=await e.readFile(r,"utf8")}catch(e){if(e&&"object"==typeof e&&"code"in e&&"ENOENT"===e.code)return[];throw new Error(`Error while reading ${this.getFileName(t)}:\n> ${e}`,{cause:e})}try{return this.codec.decode(i,{locale:t})}catch(e){throw new Error(`Error while decoding ${this.getFileName(t)}:\n> ${e}`,{cause:e})}}async write(r,i){const a=this.getFilePath(i.locale),s=this.codec.encode(r,i);try{const r=t.dirname(a);await e.mkdir(r,{recursive:!0}),await e.writeFile(a,s)}catch(e){console.error(`❌ Failed to write catalog: ${e}`)}}async getLastModified(t){const r=this.getFilePath(t);try{return(await e.stat(r)).mtime}catch{return}}}export{r as default};
1
+ import e from"fs/promises";import t from"path";import{warnAboutMissingReferences as s}from"../utils.js";class r{constructor(e){this.messagesPath=e.messagesPath,this.codec=e.codec,this.extension=e.extension,this.sourceLocale=e.sourceLocale}getFileName(e){return e+this.extension}getFilePath(e){return t.join(this.messagesPath,this.getFileName(e))}async read(t){const s=this.getFilePath(t);let r;try{r=await e.readFile(s,"utf8")}catch(e){if(e&&"object"==typeof e&&"code"in e&&"ENOENT"===e.code)return[];throw new Error(`Error while reading ${this.getFileName(t)}:\n> ${e}`,{cause:e})}try{const e=this.codec.decode(r,{locale:t,sourceLocale:this.sourceLocale});return function(e){for(const t of e){const e=t.segments;if(!Array.isArray(e))continue;const s=e.findIndex((e=>"number"==typeof e));if(-1===s)continue;const r=e.slice(0,s).join(".");throw new Error(`Message at \`${r}\` resolved to an array, but only strings are supported. See https://next-intl.dev/docs/usage/translations#arrays-of-messages`)}}(e),e}catch(e){throw new Error(`Error while decoding ${this.getFileName(t)}:\n> ${e}`,{cause:e})}}async write(r,o){const i=this.getFilePath(o.locale);s(r);const a=this.codec.encode(r,o);try{const s=t.dirname(i);await e.mkdir(s,{recursive:!0}),await e.writeFile(i,a)}catch(e){console.error(`❌ Failed to write catalog: ${e}`)}}async getLastModified(t){const s=this.getFilePath(t);try{return(await e.stat(s)).mtime}catch{return}}}export{r as default};
@@ -1 +1 @@
1
- import e from"path";import t from"icu-minify/compile";import{getFormatExtension as s,resolveCodec as o}from"./format/index.js";import{setNestedProperty as r}from"./utils.js";let a=null;const n=new Map;function i(i){const c=this.getOptions(),l=this.async(),f=s(c.messages.format);(async function(e,t){return a||(a=await o(e.messages.format,t)),a})(c,this.rootContext).then((s=>{const o=e.basename(this.resourcePath,f);let a;if(c.messages.precompile){const e=function(e,s){const o=Object.create(null),a=new Set(s.keys());for(const n of e){a.delete(n.id);const e=n.message;if(Array.isArray(e))throw new Error(`Message at \`${n.id}\` resolved to an array, but only strings are supported. See https://next-intl.dev/docs/usage/translations#arrays-of-messages`);if("object"==typeof e)throw new Error(`Message at \`${n.id}\` resolved to \`${typeof e}\`, but only strings are supported. Use a \`.\` to retrieve nested messages. See https://next-intl.dev/docs/usage/translations#structuring-messages`);const i=s.get(n.id);let c;i?.messageValue===e?c=i.compiledMessage:(c=t(e),s.set(n.id,{compiledMessage:c,messageValue:e})),r(o,n.id,c)}for(const e of a)s.delete(e);return o}(s.decode(i,{locale:o}),function(e){let t=n.get(e);return t||(t=new Map,n.set(e,t)),t}(this.resourcePath));a=JSON.stringify(e)}else a=s.toJSONString(i,{locale:o});const m=`export default JSON.parse(${JSON.stringify(a)});`;l(null,m)})).catch(l)}export{i as default};
1
+ import e from"path";import t from"icu-minify/compile";import{getFormatExtension as s,resolveCodec as o}from"./format/index.js";import{setNestedProperty as r}from"./utils.js";let n=null,a=!1;const i=new Map;function c(c){const u=this.getOptions(),f=this.async(),d=s(u.messages.format);(async function(e,t){return n||(n=await o(e.messages.format,t)),n})(u,this.rootContext).then((s=>{!function(e){if(a||!e.toJSONString)return;a=!0,console.warn("`toJSONString` is deprecated and no longer needed, please remove it.")}(s);const o=e.basename(this.resourcePath,d),n=s.decode(c,{locale:o,sourceLocale:u.messages.sourceLocale});let m;if(u.messages.precompile){m=function(e,s){const o=Object.create(null),n=new Set(s.keys());for(const a of e){n.delete(a.id),l(a);const e=a.message,i=s.get(a.id);let c;i?.messageValue===e?c=i.compiledMessage:(c=t(e),s.set(a.id,{compiledMessage:c,messageValue:e})),r(o,a.id,c)}for(const e of n)s.delete(e);return o}(n,function(e){let t=i.get(e);return t||(t=new Map,i.set(e,t)),t}(this.resourcePath))}else m=function(e){const t=Object.create(null);for(const s of e)l(s),r(t,s.id,s.message);return t}(n);const g=`export default JSON.parse(${JSON.stringify(JSON.stringify(m))});`;f(null,g)})).catch(f)}function l(e){const t=e.message;if(Array.isArray(t))throw new Error(`Message at \`${e.id}\` resolved to an array, but only strings are supported. See https://next-intl.dev/docs/usage/translations#arrays-of-messages`);if("object"==typeof t)throw new Error(`Message at \`${e.id}\` resolved to \`${typeof t}\`, but only strings are supported. Use a \`.\` to retrieve nested messages. See https://next-intl.dev/docs/usage/translations#structuring-messages`)}export{c as default};
@@ -1 +1 @@
1
- import{createRequire as e}from"module";import t from"path";import{transform as s}from"@swc/core";import{getDefaultProjectRoot as o,normalizePathToPosix as r}from"../utils.js";import i from"./LRUCache.js";const c=e(import.meta.url);class a{compileCache=new i(750);constructor(e){this.isDevelopment=e.isDevelopment??!1,this.projectRoot=e.projectRoot??o(),this.sourceMap=e.sourceMap??!1}async extract(e,o){const i=[o,e].join("!"),a=this.compileCache.get(i);if(a)return a;if(!o.includes("useExtracted")&&!o.includes("getExtracted"))return{messages:[],code:o};const p=r(t.relative(this.projectRoot,e)),l=await s(o,{jsc:{target:"esnext",parser:{syntax:"typescript",tsx:!0,decorators:!0},experimental:{cacheRoot:"node_modules/.cache/swc",disableBuiltinTransformsForInternalTesting:!0,disableAllLints:!0,plugins:[[c.resolve("next-intl-swc-plugin-extractor"),{isDevelopment:this.isDevelopment,filePath:p}]]}},sourceMaps:this.sourceMap,sourceFileName:p,filename:p}),n=l.output,m=JSON.parse(JSON.parse(n).results),u={code:l.code,map:l.map,messages:m};return this.compileCache.set(i,u),u}}export{a as default};
1
+ import{createRequire as e}from"module";import t from"path";import{transform as s}from"@swc/core";import{getDefaultProjectRoot as r,normalizePathToPosix as o}from"../utils.js";import i from"./LRUCache.js";const c=e(import.meta.url);class a{compileCache=new i(750);constructor(e){this.isDevelopment=e.isDevelopment??!1,this.projectRoot=e.projectRoot??r(),this.sourceMap=e.sourceMap??!1}async extract(e,r){const i=[r,e].join("!"),a=this.compileCache.get(i);if(a)return a;if(!r.includes("useExtracted")&&!r.includes("getExtracted"))return{messages:[],code:r};const p=o(t.relative(this.projectRoot,e)),n=await s(r,{jsc:{target:"esnext",parser:{syntax:"typescript",tsx:!0,decorators:!0},experimental:{cacheRoot:"node_modules/.cache/swc",disableBuiltinTransformsForInternalTesting:!0,disableAllLints:!0,plugins:[[c.resolve("next-intl-swc-plugin-extractor"),{isDevelopment:this.isDevelopment,filePath:p}]]}},sourceMaps:this.sourceMap,sourceFileName:p,filename:p}),l=n.output,m=JSON.parse(JSON.parse(l).results).filter((e=>"extracted"===e.type)).map((e=>({id:e.id,message:e.message,description:e.description,reference:e.reference}))),u={code:n.code,map:n.map,messages:m};return this.compileCache.set(i,u),u}}export{a as default};
@@ -0,0 +1 @@
1
+ import e from"@eloqnt/format-po";import{defineCodec as t}from"../ExtractorCodec.js";var o=t((()=>{const t=e();return{...t,decode:(e,o)=>(function(e){if(e.includes("X-Crowdin-SourceKey: msgstr"))throw new Error("This catalog uses the previous PO layout of next-intl, which stored message keys in `msgid`. The PO format now follows the gettext convention: the source text in `msgid` and the message key in `msgctxt`. Please migrate your catalogs, or use a custom codec to keep the previous layout: https://github.com/amannn/next-intl/pull/2393")}(e),t.decode(e,o))}}));export{o as default};
@@ -1 +1 @@
1
- import o from"path";import{throwError as e}from"../../plugin/utils.js";const t={json:{codec:()=>import("./codecs/JSONCodec.js"),extension:".json"},po:{codec:()=>import("./codecs/POCodec.js"),extension:".po"}};function n(o){return"string"==typeof o&&o in t}function c(o){return n(o)?t[o].extension:o.extension}async function r(c,r){if(n(c)){return(0,(await t[c].codec()).default)()}{const t=o.isAbsolute(c.codec)?c.codec:o.resolve(r,c.codec);let n;try{n=await import(t)}catch(o){e(`Could not load codec from "${t}".\n${o}`)}const i=n.default;return i&&"function"==typeof i||e(`Codec at "${t}" must have a default export returned from \`defineCodec\`.`),i()}}export{t as default,c as getFormatExtension,r as resolveCodec};
1
+ import{createRequire as o}from"module";import e from"path";import{pathToFileURL as t}from"url";import{throwError as n}from"../../plugin/utils.js";const c={json:{codec:()=>import("@eloqnt/format-json"),extension:".json"},po:{codec:()=>import("./codecs/BuiltInPoCodec.js"),extension:".po"}};function r(o){return"string"==typeof o&&o in c}function i(o){return r(o)?c[o].extension:o.extension}async function s(i,s){if(r(i)){return(0,(await c[i].codec()).default)()}{let c,r;if(i.codec.startsWith(".")||e.isAbsolute(i.codec))c=e.resolve(s,i.codec);else try{c=o(e.join(s,"noop.js")).resolve(i.codec)}catch(o){n(`Could not resolve the codec package "${i.codec}". Is it installed?\n${o}`)}try{r=await import(t(c).href)}catch(o){n(`Could not load codec from "${c}".\n${o}`)}const d=r.default;return d&&"function"==typeof d||n(`Codec at "${c}" must have a default export returned from \`defineCodec\`.`),d()}}export{c as default,i as getFormatExtension,s as resolveCodec};
@@ -1 +1 @@
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
+ import t from"path";import{warn as e}from"../plugin/utils.js";function n(e){return t.posix.normalize(e.split(t.win32.sep).join(t.posix.sep))}const o=new Set(["__proto__","constructor","prototype"]);function r(t){return o.has(t)}function c(t){const{locales:e}=t.extract;return"infer"===e||e.length>0}function i(t,e,n){const o=e.split(".");for(const t of o)if(r(t))throw new Error(`Invalid message id segment: ${t}`);let c=t;for(let t=0;t<o.length-1;t++){const e=o[t];Object.prototype.hasOwnProperty.call(c,e)&&"object"==typeof c[e]&&null!==c[e]||(c[e]=Object.create(null)),c=c[e]}c[o[o.length-1]]=n}function s(t){const n=new Set;for(const o of t)null!=o.references[0]||n.has(o.id)||(n.add(o.id),e(`Missing file reference for extracted message: ${o.id}`))}function l(t,e){return t.localeCompare(e,"en")}function f(t,e){const n=l(t.path,e.path);return 0!==n?n:(t.line??0)-(e.line??0)}function p(){return process.cwd()}export{f as compareReferences,p as getDefaultProjectRoot,c as hasLocalesToExtract,r as isForbiddenObjectKey,l as localeCompare,n as normalizePathToPosix,i as setNestedProperty,s as warnAboutMissingReferences};
@@ -1 +1 @@
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
+ import e from"fs";import{createRequire as t}from"module";import r from"path";import{getFormatExtension as o}from"../extractor/format/index.js";import{normalizeMessagesCatalogPaths as s}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,o){function s(t){return e.existsSync(function(e){const t=[];return o&&t.push(o),t.push(e),r.resolve(...t)}(t))}if(t)return i&&!s(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(s(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);"),s("./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,r=t.messages,o=r.sourceLocale??("object"==typeof t.extract?t.extract.sourceLocale:void 0);return{loader:"next-intl/extractor/catalogLoader",options:{messages:{format:r.format,...void 0!==r.precompile&&{precompile:r.precompile},sourceLocale:o}}}}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=s(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 s={"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}`),s["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,`*${o(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,...s}}:x.experimental={...t?.experimental,turbo:{...t?.experimental?.turbo,...m&&{rules:m},resolveAlias:{...t?.experimental?.turbo?.resolveAlias,...s}}}}return m||(x.webpack=function(s,a){if(s.resolve||(s.resolve={}),s.resolve.alias||(s.resolve.alias={}),s.resolve.alias["next-intl/config"]=r.resolve(s.context,p(e.requestConfig,s.context)),e.experimental?.messages?.precompile&&(s.resolve.alias["use-intl/format-message"]=c.resolve("use-intl/format-message/format-only")),e.experimental?.extract&&(s.module||(s.module={}),s.module.rules||(s.module.rules=[]),s.module.rules.push({test:new RegExp(`\\.(${n.EXTENSIONS.join("|")})$`),use:[d(i)]})),e.experimental?.messages){s.module||(s.module={}),s.module.rules||(s.module.rules=[]);const t=o(e.experimental.messages.format);s.module.rules.push({test:new RegExp(`${t.replace(/\./g,"\\.")}$`),include:g.map((e=>r.resolve(s.context,e))),use:[v()],type:"javascript/auto"})}return"function"==typeof t?.webpack?t.webpack(s,a):s}),t?.trailingSlash&&(x.env={...t.env,_next_intl_trailing_slash:"true"}),Object.assign({},t,x)}export{f as default};
@@ -15,7 +15,7 @@ export default class CatalogManager implements Disposable {
15
15
  private sourceMessagesByFile;
16
16
  /**
17
17
  * Reverse index for rebuilding aggregated messages without scanning all files.
18
- * Contains the same `SourceMessage` arrays as `sourceMessagesByFile` and is
18
+ * Contains the same `SourceExtractedMessage` arrays as `sourceMessagesByFile` and is
19
19
  * kept in sync with it.
20
20
  */
21
21
  private sourceMessagesById;
@@ -4,10 +4,12 @@ export default class CatalogPersister {
4
4
  private messagesPath;
5
5
  private codec;
6
6
  private extension;
7
+ private sourceLocale;
7
8
  constructor(params: {
8
9
  messagesPath: string;
9
10
  codec: ExtractorCodec;
10
11
  extension: string;
12
+ sourceLocale: Locale;
11
13
  });
12
14
  private getFileName;
13
15
  private getFilePath;
@@ -1,4 +1,4 @@
1
- import type { SourceMessage } from '../types.js';
1
+ import type { SourceExtractedMessage } from '../types.js';
2
2
  export default class MessageExtractor {
3
3
  private isDevelopment;
4
4
  private projectRoot;
@@ -10,7 +10,7 @@ export default class MessageExtractor {
10
10
  sourceMap?: boolean;
11
11
  });
12
12
  extract(absoluteFilePath: string, source: string): Promise<{
13
- messages: Array<SourceMessage>;
13
+ messages: Array<SourceExtractedMessage>;
14
14
  code: string;
15
15
  map?: string;
16
16
  }>;
@@ -1,33 +1,17 @@
1
- import type { ExtractorMessage, Locale } from '../types.js';
2
- type ExtractorCodecContext = {
3
- locale: Locale;
4
- };
5
- export default interface ExtractorCodec {
1
+ import type { defineCodec as sharedDefineCodec } from '@eloqnt/config';
2
+ import type { Locale } from '../types.js';
3
+ type SeparateFileCodec = Extract<ReturnType<ReturnType<typeof sharedDefineCodec>>, {
4
+ decode: unknown;
5
+ }>;
6
+ export default interface ExtractorCodec extends SeparateFileCodec {
6
7
  /**
7
- * Decode the content of a file into a list of extracted messages. This is used
8
- * to load existing messages from disk.
8
+ * @deprecated No longer used. Catalogs are loaded into your application via
9
+ * `decode`, so you can remove `toJSONString` from your codec. Providing it
10
+ * logs a deprecation warning and has no effect.
9
11
  */
10
- decode(content: string, context: ExtractorCodecContext): Array<ExtractorMessage>;
11
- /**
12
- * Encode a list of extracted messages into a string that can be written as
13
- * file content to the disk.
14
- */
15
- encode(messages: Array<ExtractorMessage>, context: ExtractorCodecContext & {
16
- sourceMessagesById: Map</* ID */ string, ExtractorMessage>;
12
+ toJSONString?(content: string, context: {
13
+ locale: Locale;
17
14
  }): string;
18
- /**
19
- * Turns the content of a file into a JSON string that represents extracted
20
- * messages. The returned value will be passed to `JSON.parse`.
21
- *
22
- * @return E.g. `[{"id":"-YJVTi","message":"Hey!"}]`
23
- *
24
- * This is used when loading messages into your application, typically via a
25
- * dynamic import (e.g. `import(`../messages/${locale}.json`)`).
26
- *
27
- * If your file content is JSON and should be used as-is, you can set this to
28
- * an identity function.
29
- */
30
- toJSONString(content: string, context: ExtractorCodecContext): string;
31
15
  }
32
16
  export declare function defineCodec(factory: () => ExtractorCodec): () => ExtractorCodec;
33
17
  export {};
@@ -0,0 +1,50 @@
1
+ declare const _default: () => {
2
+ encode(messages: {
3
+ [key: string]: unknown;
4
+ id: string;
5
+ message: string;
6
+ description: Array<string>;
7
+ references: Array<{
8
+ path: string;
9
+ line?: number;
10
+ }>;
11
+ segments?: Array<string | number>;
12
+ verbatim?: {
13
+ value: unknown;
14
+ };
15
+ orphan?: boolean;
16
+ }[], context: {
17
+ locale: string;
18
+ sourceMessagesById: Map<string, {
19
+ [key: string]: unknown;
20
+ id: string;
21
+ message: string;
22
+ description: Array<string>;
23
+ references: Array<{
24
+ path: string;
25
+ line?: number;
26
+ }>;
27
+ segments?: Array<string | number>;
28
+ verbatim?: {
29
+ value: unknown;
30
+ };
31
+ orphan?: boolean;
32
+ }>;
33
+ }): string;
34
+ decode(content: string, context: {
35
+ locale: string;
36
+ sourceLocale: string;
37
+ }): {
38
+ formatPoEmptySource?: string | undefined;
39
+ formatPoContext?: string | undefined;
40
+ id: string;
41
+ message: string;
42
+ description: string[];
43
+ references: {
44
+ path: string;
45
+ line?: number;
46
+ }[];
47
+ flags?: Array<string>;
48
+ }[];
49
+ };
50
+ export default _default;
@@ -2,11 +2,11 @@ import type ExtractorCodec from './ExtractorCodec.js';
2
2
  import type { MessagesFormat } from './types.js';
3
3
  declare const formats: {
4
4
  json: {
5
- codec: () => Promise<typeof import("./codecs/JSONCodec.js")>;
5
+ codec: () => Promise<typeof import("@eloqnt/format-json")>;
6
6
  extension: ".json";
7
7
  };
8
8
  po: {
9
- codec: () => Promise<typeof import("./codecs/POCodec.js")>;
9
+ codec: () => Promise<typeof import("./codecs/BuiltInPoCodec.js")>;
10
10
  extension: ".po";
11
11
  };
12
12
  };