next-intl 4.5.8 → 4.6.1

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 (88) hide show
  1. package/dist/cjs/development/ExtractorCodec-D9Tw618d.cjs +7 -0
  2. package/dist/cjs/development/JSONCodec-L1_VeQBi.cjs +48 -0
  3. package/dist/cjs/development/POCodec-Be_UL6jy.cjs +105 -0
  4. package/dist/cjs/development/plugin-DDtWCyPI.cjs +1373 -0
  5. package/dist/cjs/development/plugin.cjs +8 -379
  6. package/dist/esm/development/extractor/ExtractionCompiler.js +23 -26
  7. package/dist/esm/development/extractor/catalog/CatalogLocales.js +0 -33
  8. package/dist/esm/development/extractor/catalog/CatalogManager.js +171 -110
  9. package/dist/esm/development/extractor/catalog/CatalogPersister.js +31 -13
  10. package/dist/esm/development/extractor/catalog/SaveScheduler.js +1 -1
  11. package/dist/esm/development/extractor/catalogLoader.js +10 -10
  12. package/dist/esm/development/extractor/extractMessages.js +9 -2
  13. package/dist/esm/development/extractor/extractionLoader.js +15 -12
  14. package/dist/esm/development/extractor/extractor/MessageExtractor.js +5 -4
  15. package/dist/esm/development/extractor/format/ExtractorCodec.js +5 -0
  16. package/dist/esm/development/extractor/format/codecs/JSONCodec.js +40 -0
  17. package/dist/esm/development/extractor/format/codecs/POCodec.js +93 -0
  18. package/dist/esm/development/extractor/format/index.js +44 -0
  19. package/dist/esm/development/extractor/source/SourceFileScanner.js +2 -1
  20. package/dist/esm/development/extractor/source/SourceFileWatcher.js +132 -0
  21. package/dist/esm/development/extractor/utils.js +16 -1
  22. package/dist/esm/development/extractor.js +1 -0
  23. package/dist/esm/development/plugin/createNextIntlPlugin.js +3 -1
  24. package/dist/esm/development/plugin/declaration/createMessagesDeclaration.js +2 -11
  25. package/dist/esm/development/plugin/extractor/initExtractionCompiler.js +45 -0
  26. package/dist/esm/development/plugin/getNextConfig.js +7 -4
  27. package/dist/esm/development/plugin/utils.js +16 -1
  28. package/dist/esm/production/extractor/ExtractionCompiler.js +1 -1
  29. package/dist/esm/production/extractor/catalog/CatalogLocales.js +1 -1
  30. package/dist/esm/production/extractor/catalog/CatalogManager.js +1 -1
  31. package/dist/esm/production/extractor/catalog/CatalogPersister.js +1 -1
  32. package/dist/esm/production/extractor/catalog/SaveScheduler.js +1 -1
  33. package/dist/esm/production/extractor/catalogLoader.js +1 -1
  34. package/dist/esm/production/extractor/extractMessages.js +1 -1
  35. package/dist/esm/production/extractor/extractionLoader.js +1 -1
  36. package/dist/esm/production/extractor/extractor/MessageExtractor.js +1 -1
  37. package/dist/esm/production/extractor/format/ExtractorCodec.js +1 -0
  38. package/dist/esm/production/extractor/format/codecs/JSONCodec.js +1 -0
  39. package/dist/esm/production/extractor/format/codecs/POCodec.js +1 -0
  40. package/dist/esm/production/extractor/format/index.js +1 -0
  41. package/dist/esm/production/extractor/source/SourceFileScanner.js +1 -1
  42. package/dist/esm/production/extractor/source/SourceFileWatcher.js +1 -0
  43. package/dist/esm/production/extractor/utils.js +1 -1
  44. package/dist/esm/production/extractor.js +1 -1
  45. package/dist/esm/production/plugin/createNextIntlPlugin.js +1 -1
  46. package/dist/esm/production/plugin/declaration/createMessagesDeclaration.js +1 -1
  47. package/dist/esm/production/plugin/extractor/initExtractionCompiler.js +1 -0
  48. package/dist/esm/production/plugin/getNextConfig.js +1 -1
  49. package/dist/esm/production/plugin/utils.js +1 -1
  50. package/dist/types/extractor/ExtractionCompiler.d.ts +5 -10
  51. package/dist/types/extractor/catalog/CatalogLocales.d.ts +0 -2
  52. package/dist/types/extractor/catalog/CatalogManager.d.ts +26 -15
  53. package/dist/types/extractor/catalog/CatalogPersister.d.ts +15 -6
  54. package/dist/types/extractor/catalog/SaveScheduler.d.ts +2 -2
  55. package/dist/types/extractor/extractor/MessageExtractor.d.ts +6 -6
  56. package/dist/types/extractor/format/ExtractorCodec.d.ts +33 -0
  57. package/dist/types/extractor/format/codecs/JSONCodec.d.ts +2 -0
  58. package/dist/types/extractor/format/codecs/POCodec.d.ts +2 -0
  59. package/dist/types/extractor/format/codecs/fixtures/JSONCodecStructured.d.ts +2 -0
  60. package/dist/types/extractor/format/codecs/fixtures/POCodecSourceMessageKey.d.ts +2 -0
  61. package/dist/types/extractor/format/index.d.ts +15 -0
  62. package/dist/types/extractor/format/types.d.ts +8 -0
  63. package/dist/types/extractor/index.d.ts +1 -0
  64. package/dist/types/extractor/source/SourceFileFilter.d.ts +2 -2
  65. package/dist/types/extractor/source/SourceFileScanner.d.ts +1 -1
  66. package/dist/types/extractor/source/SourceFileWatcher.d.ts +15 -0
  67. package/dist/types/extractor/types.d.ts +2 -2
  68. package/dist/types/extractor/utils.d.ts +3 -0
  69. package/dist/types/plugin/extractor/initExtractionCompiler.d.ts +2 -0
  70. package/dist/types/plugin/types.d.ts +1 -1
  71. package/dist/types/plugin/utils.d.ts +6 -0
  72. package/package.json +6 -5
  73. package/dist/esm/development/extractor/formatters/Formatter.js +0 -3
  74. package/dist/esm/development/extractor/formatters/JSONFormatter.js +0 -42
  75. package/dist/esm/development/extractor/formatters/POFormatter.js +0 -51
  76. package/dist/esm/development/extractor/formatters/index.js +0 -6
  77. package/dist/esm/development/extractor/formatters/utils.js +0 -15
  78. package/dist/esm/production/extractor/formatters/Formatter.js +0 -1
  79. package/dist/esm/production/extractor/formatters/JSONFormatter.js +0 -1
  80. package/dist/esm/production/extractor/formatters/POFormatter.js +0 -1
  81. package/dist/esm/production/extractor/formatters/index.js +0 -1
  82. package/dist/esm/production/extractor/formatters/utils.js +0 -1
  83. package/dist/types/extractor/extractor/ASTScope.d.ts +0 -12
  84. package/dist/types/extractor/formatters/Formatter.d.ts +0 -10
  85. package/dist/types/extractor/formatters/JSONFormatter.d.ts +0 -10
  86. package/dist/types/extractor/formatters/POFormatter.d.ts +0 -10
  87. package/dist/types/extractor/formatters/index.d.ts +0 -5
  88. package/dist/types/extractor/formatters/utils.d.ts +0 -2
@@ -0,0 +1,93 @@
1
+ import POParser from 'po-parser';
2
+ import { setNestedProperty, getSortedMessages } from '../../utils.js';
3
+ import { defineCodec } from '../ExtractorCodec.js';
4
+
5
+ var POCodec = defineCodec(() => {
6
+ // See also https://www.gnu.org/software/gettext/manual/html_node/Header-Entry.html
7
+ const DEFAULT_METADATA = {
8
+ // Recommended by spec
9
+ 'Content-Type': 'text/plain; charset=utf-8',
10
+ 'Content-Transfer-Encoding': '8bit',
11
+ // Otherwise other tools might set this
12
+ 'X-Generator': 'next-intl',
13
+ // Crowdin defaults to using msgid as source key
14
+ 'X-Crowdin-SourceKey': 'msgstr'
15
+ };
16
+
17
+ // Move all parts before the last dot to msgctxt
18
+ const NAMESPACE_SEPARATOR = '.';
19
+
20
+ // Metadata is stored so it can be retained when writing
21
+ const metadataByLocale = new Map();
22
+ return {
23
+ decode(content, context) {
24
+ const catalog = POParser.parse(content);
25
+ if (catalog.meta) {
26
+ metadataByLocale.set(context.locale, catalog.meta);
27
+ }
28
+ const messages = catalog.messages || [];
29
+ return messages.map(msg => {
30
+ const {
31
+ extractedComments,
32
+ msgctxt,
33
+ msgid,
34
+ msgstr,
35
+ ...rest
36
+ } = msg;
37
+ if (extractedComments && extractedComments.length > 1) {
38
+ throw new Error(`Multiple extracted comments are not supported. Found ${extractedComments.length} comments for msgid "${msgid}".`);
39
+ }
40
+ return {
41
+ ...rest,
42
+ id: msgctxt ? [msgctxt, msgid].join(NAMESPACE_SEPARATOR) : msgid,
43
+ message: msgstr,
44
+ ...(extractedComments && extractedComments.length > 0 && {
45
+ description: extractedComments[0]
46
+ })
47
+ };
48
+ });
49
+ },
50
+ encode(messages, context) {
51
+ const encodedMessages = getSortedMessages(messages).map(msg => {
52
+ const {
53
+ description,
54
+ id,
55
+ message,
56
+ ...rest
57
+ } = msg;
58
+ const lastDotIndex = id.lastIndexOf(NAMESPACE_SEPARATOR);
59
+ const hasNamespace = id.includes(NAMESPACE_SEPARATOR);
60
+ const msgid = hasNamespace ? id.slice(lastDotIndex + NAMESPACE_SEPARATOR.length) : id;
61
+ return {
62
+ msgid,
63
+ msgstr: message,
64
+ ...(description && {
65
+ extractedComments: [description]
66
+ }),
67
+ ...(hasNamespace && {
68
+ msgctxt: id.slice(0, lastDotIndex)
69
+ }),
70
+ ...rest
71
+ };
72
+ });
73
+ return POParser.serialize({
74
+ meta: {
75
+ Language: context.locale,
76
+ ...DEFAULT_METADATA,
77
+ ...metadataByLocale.get(context.locale)
78
+ },
79
+ messages: encodedMessages
80
+ });
81
+ },
82
+ toJSONString(source, context) {
83
+ const parsed = this.decode(source, context);
84
+ const messagesObject = {};
85
+ for (const message of parsed) {
86
+ setNestedProperty(messagesObject, message.id, message.message);
87
+ }
88
+ return JSON.stringify(messagesObject);
89
+ }
90
+ };
91
+ });
92
+
93
+ export { POCodec as default };
@@ -0,0 +1,44 @@
1
+ import path from 'path';
2
+ import { throwError } from '../../plugin/utils.js';
3
+
4
+ const formats = {
5
+ json: {
6
+ codec: () => import('./codecs/JSONCodec.js'),
7
+ extension: '.json'
8
+ },
9
+ po: {
10
+ codec: () => import('./codecs/POCodec.js'),
11
+ extension: '.po'
12
+ }
13
+ };
14
+ function isBuiltInFormat(format) {
15
+ return typeof format === 'string' && format in formats;
16
+ }
17
+ function getFormatExtension(format) {
18
+ if (isBuiltInFormat(format)) {
19
+ return formats[format].extension;
20
+ } else {
21
+ return format.extension;
22
+ }
23
+ }
24
+ async function resolveCodec(format, projectRoot) {
25
+ if (isBuiltInFormat(format)) {
26
+ const factory = (await formats[format].codec()).default;
27
+ return factory();
28
+ } else {
29
+ const resolvedPath = path.isAbsolute(format.codec) ? format.codec : path.resolve(projectRoot, format.codec);
30
+ let module;
31
+ try {
32
+ module = await import(resolvedPath);
33
+ } catch (error) {
34
+ throwError(`Could not load codec from "${resolvedPath}".\n${error}`);
35
+ }
36
+ const factory = module.default;
37
+ if (!factory || typeof factory !== 'function') {
38
+ throwError(`Codec at "${resolvedPath}" must have a default export returned from \`defineCodec\`.`);
39
+ }
40
+ return factory();
41
+ }
42
+ }
43
+
44
+ export { formats as default, getFormatExtension, resolveCodec };
@@ -23,7 +23,8 @@ class SourceFileScanner {
23
23
  return acc;
24
24
  }
25
25
  static async getSourceFiles(srcPaths) {
26
- return (await Promise.all(srcPaths.map(srcPath => SourceFileScanner.walkSourceFiles(srcPath, srcPaths)))).flat();
26
+ const files = (await Promise.all(srcPaths.map(srcPath => SourceFileScanner.walkSourceFiles(srcPath, srcPaths)))).flat();
27
+ return new Set(files);
27
28
  }
28
29
  }
29
30
 
@@ -0,0 +1,132 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { subscribe } from '@parcel/watcher';
4
+ import SourceFileFilter from './SourceFileFilter.js';
5
+ import SourceFileScanner from './SourceFileScanner.js';
6
+
7
+ class SourceFileWatcher {
8
+ subscriptions = [];
9
+ constructor(roots, onChange) {
10
+ this.roots = roots;
11
+ this.onChange = onChange;
12
+ }
13
+ async start() {
14
+ if (this.subscriptions.length > 0) {
15
+ return;
16
+ }
17
+ const ignore = SourceFileFilter.IGNORED_DIRECTORIES.map(dir => `**/${dir}/**`);
18
+ for (const root of this.roots) {
19
+ const sub = await subscribe(root, async (err, events) => {
20
+ if (err) {
21
+ console.error(err);
22
+ return;
23
+ }
24
+ const filtered = await this.normalizeEvents(events);
25
+ if (filtered.length > 0) {
26
+ void this.onChange(filtered);
27
+ }
28
+ }, {
29
+ ignore
30
+ });
31
+ this.subscriptions.push(sub);
32
+ }
33
+ }
34
+ async normalizeEvents(events) {
35
+ const directoryCreatePaths = [];
36
+ const otherEvents = [];
37
+
38
+ // We need to expand directory creates because during rename operations,
39
+ // @parcel/watcher emits a directory create event but may not emit individual
40
+ // file events for the moved files
41
+ await Promise.all(events.map(async event => {
42
+ if (event.type === 'create') {
43
+ try {
44
+ const stats = await fs.stat(event.path);
45
+ if (stats.isDirectory()) {
46
+ directoryCreatePaths.push(event.path);
47
+ return;
48
+ }
49
+ } catch {
50
+ // Path doesn't exist or is inaccessible, treat as file
51
+ }
52
+ }
53
+ otherEvents.push(event);
54
+ }));
55
+
56
+ // Expand directory create events to find source files inside
57
+ let expandedCreateEvents = [];
58
+ if (directoryCreatePaths.length > 0) {
59
+ try {
60
+ const sourceFiles = await SourceFileScanner.getSourceFiles(directoryCreatePaths);
61
+ expandedCreateEvents = Array.from(sourceFiles).map(filePath => ({
62
+ type: 'create',
63
+ path: filePath
64
+ }));
65
+ } catch {
66
+ // Directories might have been deleted or are inaccessible
67
+ }
68
+ }
69
+
70
+ // Combine original events with expanded directory creates.
71
+ // Deduplicate by path to avoid processing the same file twice
72
+ // in case @parcel/watcher also emitted individual file events.
73
+ const allEvents = [...otherEvents, ...expandedCreateEvents];
74
+ const seenPaths = new Set();
75
+ const deduplicated = [];
76
+ for (const event of allEvents) {
77
+ const key = `${event.type}:${event.path}`;
78
+ if (!seenPaths.has(key)) {
79
+ seenPaths.add(key);
80
+ deduplicated.push(event);
81
+ }
82
+ }
83
+ return deduplicated.filter(event => {
84
+ // Keep all delete events (might be deleted directories that no longer exist)
85
+ if (event.type === 'delete') {
86
+ return true;
87
+ }
88
+ // Keep source files
89
+ return SourceFileFilter.isSourceFile(event.path);
90
+ });
91
+ }
92
+ async expandDirectoryDeleteEvents(events, prevKnownFiles) {
93
+ const expanded = [];
94
+ for (const event of events) {
95
+ if (event.type === 'delete' && !SourceFileFilter.isSourceFile(event.path)) {
96
+ const dirPath = path.resolve(event.path);
97
+ const filesInDirectory = [];
98
+ for (const filePath of prevKnownFiles) {
99
+ if (SourceFileFilter.isWithinPath(filePath, dirPath)) {
100
+ filesInDirectory.push(filePath);
101
+ }
102
+ }
103
+
104
+ // If we found files within this path, it was a directory
105
+ if (filesInDirectory.length > 0) {
106
+ for (const filePath of filesInDirectory) {
107
+ expanded.push({
108
+ type: 'delete',
109
+ path: filePath
110
+ });
111
+ }
112
+ } else {
113
+ // Not a directory or no files in it, pass through as-is
114
+ expanded.push(event);
115
+ }
116
+ } else {
117
+ // Pass through as-is
118
+ expanded.push(event);
119
+ }
120
+ }
121
+ return expanded;
122
+ }
123
+ async stop() {
124
+ await Promise.all(this.subscriptions.map(sub => sub.unsubscribe()));
125
+ this.subscriptions = [];
126
+ }
127
+ [Symbol.dispose]() {
128
+ void this.stop();
129
+ }
130
+ }
131
+
132
+ export { SourceFileWatcher as default };
@@ -1,3 +1,4 @@
1
+ // Essentialls lodash/set, but we avoid this dependency
1
2
  function setNestedProperty(obj, keyPath, value) {
2
3
  const keys = keyPath.split('.');
3
4
  let current = obj;
@@ -10,8 +11,22 @@ function setNestedProperty(obj, keyPath, value) {
10
11
  }
11
12
  current[keys[keys.length - 1]] = value;
12
13
  }
14
+ function getSortedMessages(messages) {
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
+ }
23
+ });
24
+ }
13
25
  function localeCompare(a, b) {
14
26
  return a.localeCompare(b, 'en');
15
27
  }
28
+ function getDefaultProjectRoot() {
29
+ return process.cwd();
30
+ }
16
31
 
17
- export { localeCompare, setNestedProperty };
32
+ export { getDefaultProjectRoot, getSortedMessages, localeCompare, setNestedProperty };
@@ -1 +1,2 @@
1
1
  export { default as unstable_extractMessages } from './extractor/extractMessages.js';
2
+ export { defineCodec } from './extractor/format/ExtractorCodec.js';
@@ -1,15 +1,17 @@
1
+ import initExtractionCompiler from './extractor/initExtractionCompiler.js';
1
2
  import getNextConfig from './getNextConfig.js';
2
3
  import { warn } from './utils.js';
3
4
  import createMessagesDeclaration from './declaration/createMessagesDeclaration.js';
4
5
 
5
6
  function initPlugin(pluginConfig, nextConfig) {
6
7
  if (nextConfig?.i18n != null) {
7
- warn("\n[next-intl] 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");
8
+ warn("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");
8
9
  }
9
10
  const messagesPathOrPaths = pluginConfig.experimental?.createMessagesDeclaration;
10
11
  if (messagesPathOrPaths) {
11
12
  createMessagesDeclaration(typeof messagesPathOrPaths === 'string' ? [messagesPathOrPaths] : messagesPathOrPaths);
12
13
  }
14
+ initExtractionCompiler(pluginConfig);
13
15
  return getNextConfig(pluginConfig, nextConfig);
14
16
  }
15
17
  function createNextIntlPlugin(i18nPathOrConfig = {}) {
@@ -1,15 +1,9 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { throwError } from '../utils.js';
3
+ import { once, throwError } from '../utils.js';
4
4
  import watchFile from '../watchFile.js';
5
5
 
6
- function runOnce(fn) {
7
- if (process.env._NEXT_INTL_COMPILE_MESSAGES === '1') {
8
- return;
9
- }
10
- process.env._NEXT_INTL_COMPILE_MESSAGES = '1';
11
- fn();
12
- }
6
+ const runOnce = once('_NEXT_INTL_COMPILE_MESSAGES');
13
7
  function createMessagesDeclaration(messagesPaths) {
14
8
  // Instead of running _only_ in certain cases, it's
15
9
  // safer to _avoid_ running for certain known cases.
@@ -29,9 +23,6 @@ function createMessagesDeclaration(messagesPaths) {
29
23
  if (shouldBailOut) {
30
24
  return;
31
25
  }
32
-
33
- // Next.js can call the Next.js config multiple
34
- // times - ensure we only run once.
35
26
  runOnce(() => {
36
27
  for (const messagesPath of messagesPaths) {
37
28
  const fullPath = path.resolve(messagesPath);
@@ -0,0 +1,45 @@
1
+ import ExtractionCompiler from '../../extractor/ExtractionCompiler.js';
2
+ import { once } from '../utils.js';
3
+
4
+ // Single compiler instance, initialized once per process
5
+ let compiler;
6
+ const runOnce = once('_NEXT_INTL_EXTRACT');
7
+ function initExtractionCompiler(pluginConfig) {
8
+ const experimental = pluginConfig.experimental;
9
+ if (!experimental?.extract) {
10
+ return;
11
+ }
12
+ runOnce(() => {
13
+ // Avoid rollup's `replace` plugin to compile this away
14
+ const isDevelopment = process.env['NODE_ENV'.trim()] === 'development';
15
+ const extractorConfig = {
16
+ srcPath: experimental.srcPath,
17
+ sourceLocale: experimental.extract.sourceLocale,
18
+ messages: experimental.messages
19
+ };
20
+ compiler = new ExtractionCompiler(extractorConfig, {
21
+ isDevelopment,
22
+ projectRoot: process.cwd()
23
+ });
24
+
25
+ // Fire-and-forget: Start extraction, don't block config return.
26
+ // In dev mode, this also starts the file watcher.
27
+ // In prod, ideally we would wait until the extraction is complete,
28
+ // but we can't `await` anywhere (at least for Turbopack).
29
+ // The result is ok though, as if we encounter untranslated messages,
30
+ // we'll simply add empty messages to the catalog. So for actually
31
+ // running the app, there is no difference.
32
+ compiler.extractAll();
33
+ function cleanup() {
34
+ if (compiler) {
35
+ compiler[Symbol.dispose]();
36
+ compiler = undefined;
37
+ }
38
+ }
39
+ process.on('exit', cleanup);
40
+ process.on('SIGINT', cleanup);
41
+ process.on('SIGTERM', cleanup);
42
+ });
43
+ }
44
+
45
+ export { initExtractionCompiler as default };
@@ -1,5 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
+ import { getFormatExtension } from '../extractor/format/index.js';
3
4
  import SourceFileFilter from '../extractor/source/SourceFileFilter.js';
4
5
  import { isNextJs16OrHigher, hasStableTurboConfig } from './nextFlags.js';
5
6
  import { throwError } from './utils.js';
@@ -42,7 +43,7 @@ function getNextConfig(pluginConfig, nextConfig) {
42
43
  const nextIntlConfig = {};
43
44
  function getExtractMessagesLoaderConfig() {
44
45
  const experimental = pluginConfig.experimental;
45
- if (!experimental.srcPath || !experimental.messages) {
46
+ if (!experimental.srcPath || !pluginConfig.experimental?.messages) {
46
47
  throwError('`srcPath` and `messages` are required when using `extractor`.');
47
48
  }
48
49
  return {
@@ -50,7 +51,7 @@ function getNextConfig(pluginConfig, nextConfig) {
50
51
  options: {
51
52
  srcPath: experimental.srcPath,
52
53
  sourceLocale: experimental.extract.sourceLocale,
53
- messages: experimental.messages
54
+ messages: pluginConfig.experimental.messages
54
55
  }
55
56
  };
56
57
  }
@@ -118,7 +119,8 @@ function getNextConfig(pluginConfig, nextConfig) {
118
119
  throwError('Message catalog loading requires Next.js 16 or higher.');
119
120
  }
120
121
  rules ??= getTurboRules();
121
- addTurboRule(rules, `*.${pluginConfig.experimental.messages.format}`, {
122
+ const extension = getFormatExtension(pluginConfig.experimental.messages.format);
123
+ addTurboRule(rules, `*${extension}`, {
122
124
  loaders: [getCatalogLoaderConfig()],
123
125
  condition: {
124
126
  path: `${pluginConfig.experimental.messages.path}/**/*`
@@ -182,8 +184,9 @@ function getNextConfig(pluginConfig, nextConfig) {
182
184
  if (pluginConfig.experimental?.messages) {
183
185
  if (!config.module) config.module = {};
184
186
  if (!config.module.rules) config.module.rules = [];
187
+ const extension = getFormatExtension(pluginConfig.experimental.messages.format);
185
188
  config.module.rules.push({
186
- test: new RegExp(`\\.${pluginConfig.experimental.messages.format}$`),
189
+ test: new RegExp(`${extension.replace(/\./g, '\\.')}$`),
187
190
  include: path.resolve(config.context, pluginConfig.experimental.messages.path),
188
191
  use: [getCatalogLoaderConfig()],
189
192
  type: 'javascript/auto'
@@ -8,4 +8,19 @@ function warn(message) {
8
8
  console.warn(formatMessage(message));
9
9
  }
10
10
 
11
- export { throwError, warn };
11
+ /**
12
+ * Returns a function that runs the provided callback only once per process.
13
+ * Next.js can call the config multiple times - this ensures we only run once.
14
+ * Uses an environment variable to track execution across config loads.
15
+ */
16
+ function once(namespace) {
17
+ return function runOnce(fn) {
18
+ if (process.env[namespace] === '1') {
19
+ return;
20
+ }
21
+ process.env[namespace] = '1';
22
+ fn();
23
+ };
24
+ }
25
+
26
+ export { once, throwError, warn };
@@ -1 +1 @@
1
- import a from"./catalog/CatalogManager.js";class i{isDevelopment=!1;constructor(i,t={}){this.manager=new a(i,t),this.isDevelopment=t.isDevelopment??!1,this.initialScanPromise=this.performInitialScan()}async compile(a,i){this.initialScanPromise&&(await this.initialScanPromise,this.initialScanPromise=void 0);const t=await this.manager.extractFileMessages(a,i);return this.isDevelopment&&t.changed&&this.manager.save(),t}async performInitialScan(){await this.manager.loadMessages(),await this.manager.save()}async extract(){await this.initialScanPromise}[Symbol.dispose](){this.manager.destroy()}}export{i as default};
1
+ import s from"./catalog/CatalogManager.js";import t from"./extractor/MessageExtractor.js";class o{constructor(o,a={}){const e=a.extractor??new t(a);this.manager=new s(o,{...a,extractor:e}),this[Symbol.dispose]=this[Symbol.dispose].bind(this),this.installExitHandlers()}async extractAll(){await this.manager.loadMessages(),await this.manager.save()}[Symbol.dispose](){this.uninstallExitHandlers(),this.manager[Symbol.dispose]()}installExitHandlers(){const s=this[Symbol.dispose];process.on("exit",s),process.on("SIGINT",s),process.on("SIGTERM",s)}uninstallExitHandlers(){const s=this[Symbol.dispose];process.off("exit",s),process.off("SIGINT",s),process.off("SIGTERM",s)}}export{o as default};
@@ -1 +1 @@
1
- import e from"fs";import s from"fs/promises";import t from"path";class a{cleanupHandlers=[];onChangeCallbacks=(()=>new Set)();constructor(e){this.messagesDir=e.messagesDir,this.sourceLocale=e.sourceLocale,this.extension=e.extension,this.locales=e.locales}async getTargetLocales(){return this.targetLocales||("infer"===this.locales?this.targetLocales=await this.readTargetLocales():this.targetLocales=this.locales.filter((e=>e!==this.sourceLocale))),this.targetLocales}async readTargetLocales(){try{return(await s.readdir(this.messagesDir)).filter((e=>e.endsWith(this.extension))).map((e=>t.basename(e,this.extension))).filter((e=>e!==this.sourceLocale))}catch{return[]}}subscribeLocalesChange(e){this.onChangeCallbacks.add(e),"infer"!==this.locales||this.watcher||this.startWatcher()}unsubscribeLocalesChange(e){this.onChangeCallbacks.delete(e),0===this.onChangeCallbacks.size&&this.stopWatcher()}async startWatcher(){this.watcher||(await s.mkdir(this.messagesDir,{recursive:!0}),this.watcher=e.watch(this.messagesDir,{persistent:!1,recursive:!1},((e,s)=>{null!=s&&s.endsWith(this.extension)&&!s.includes(t.sep)&&this.onChange()})),this.setupCleanupHandlers())}stopWatcher(){this.watcher&&(this.watcher.close(),this.watcher=void 0);for(const e of this.cleanupHandlers)e();this.cleanupHandlers=[]}async onChange(){const e=new Set(this.targetLocales||[]);this.targetLocales=await this.readTargetLocales();const s=new Set(this.targetLocales),t=this.targetLocales.filter((s=>!e.has(s))),a=Array.from(e).filter((e=>!s.has(e)));if(t.length>0||a.length>0)for(const e of this.onChangeCallbacks)e({added:t,removed:a})}setupCleanupHandlers(){const e=()=>{this.watcher&&(this.watcher.close(),this.watcher=void 0)};function s(){e()}function t(){e(),process.exit(0)}function a(){e(),process.exit(0)}process.once("exit",s),process.once("SIGINT",t),process.once("SIGTERM",a),this.cleanupHandlers.push((()=>{process.removeListener("exit",s),process.removeListener("SIGINT",t),process.removeListener("SIGTERM",a)}))}}export{a as default};
1
+ import e from"fs";import s from"fs/promises";import t from"path";class a{onChangeCallbacks=(()=>new Set)();constructor(e){this.messagesDir=e.messagesDir,this.sourceLocale=e.sourceLocale,this.extension=e.extension,this.locales=e.locales}async getTargetLocales(){return this.targetLocales||("infer"===this.locales?this.targetLocales=await this.readTargetLocales():this.targetLocales=this.locales.filter((e=>e!==this.sourceLocale))),this.targetLocales}async readTargetLocales(){try{return(await s.readdir(this.messagesDir)).filter((e=>e.endsWith(this.extension))).map((e=>t.basename(e,this.extension))).filter((e=>e!==this.sourceLocale))}catch{return[]}}subscribeLocalesChange(e){this.onChangeCallbacks.add(e),"infer"!==this.locales||this.watcher||this.startWatcher()}unsubscribeLocalesChange(e){this.onChangeCallbacks.delete(e),0===this.onChangeCallbacks.size&&this.stopWatcher()}async startWatcher(){this.watcher||(await s.mkdir(this.messagesDir,{recursive:!0}),this.watcher=e.watch(this.messagesDir,{persistent:!1,recursive:!1},((e,s)=>{null!=s&&s.endsWith(this.extension)&&!s.includes(t.sep)&&this.onChange()})))}stopWatcher(){this.watcher&&(this.watcher.close(),this.watcher=void 0)}async onChange(){const e=new Set(this.targetLocales||[]);this.targetLocales=await this.readTargetLocales();const s=new Set(this.targetLocales),t=this.targetLocales.filter((s=>!e.has(s))),a=Array.from(e).filter((e=>!s.has(e)));if(t.length>0||a.length>0)for(const e of this.onChangeCallbacks)e({added:t,removed:a})}}export{a as default};
@@ -1 +1 @@
1
- import e from"fs/promises";import s from"path";import t from"../extractor/MessageExtractor.js";import a from"../formatters/index.js";import o from"../source/SourceFileScanner.js";import{localeCompare as i}from"../utils.js";import r from"./CatalogLocales.js";import c from"./CatalogPersister.js";import l from"./SaveScheduler.js";class n{messagesByFile=(()=>new Map)();messagesById=(()=>new Map)();translationsByTargetLocale=(()=>new Map)();lastWriteByLocale=(()=>new Map)();constructor(e,s={}){this.config=e,this.saveScheduler=new l(50),this.projectRoot=s.projectRoot||process.cwd(),this.isDevelopment=s.isDevelopment??!1,this.messageExtractor=new t({isDevelopment:this.isDevelopment,projectRoot:this.projectRoot,sourceMap:s.sourceMap})}async getFormatter(){if(this.formatter)return this.formatter;{const e=(await a[this.config.messages.format]()).default;return this.formatter=new e,this.formatter}}async getPersister(){return this.persister||(this.persister=new c(this.config.messages.path,await this.getFormatter())),this.persister}async getCatalogLocales(){if(this.catalogLocales)return this.catalogLocales;{const e=s.join(this.projectRoot,this.config.messages.path),t=await this.getFormatter();return this.catalogLocales=new r({messagesDir:e,sourceLocale:this.config.sourceLocale,extension:t.EXTENSION,locales:this.config.messages.locales}),this.catalogLocales}}async getTargetLocales(){return(await 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(){if(this.loadCatalogsPromise=Promise.all([this.loadSourceMessages(),this.loadTargetMessages()]),await this.loadCatalogsPromise,this.isDevelopment){(await this.getCatalogLocales()).subscribeLocalesChange(this.onLocalesChange)}const s=await o.getSourceFiles(this.getSrcPaths());await Promise.all(s.map((async s=>this.extractFileMessages(s,await e.readFile(s,"utf8")))))}async loadSourceMessages(){const e=await this.loadLocaleMessages(this.config.sourceLocale),t=new Map,a=new Map;for(const o of e)if(t.set(o.id,o),o.references)for(const e of o.references){const t=s.join(this.projectRoot,e.path);let i=a.get(t);i||(i=new Map,a.set(t,i)),i.set(o.id,o)}this.messagesById=t,this.messagesByFile=a}async loadLocaleMessages(e){const s=await this.getPersister();try{const t=await s.read(e),a=await s.getLastModified(e);return this.lastWriteByLocale.set(e,a),t}catch{return[]}}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);s&&this.messagesById.set(e.id,{...e,id:s.id,message:s.message,description:s.description,references:s.references})}else{const t=new Map;for(const e of s)t.set(e.id,e);this.translationsByTargetLocale.set(e,t)}}async extractFileMessages(e,t){const a=await this.messageExtractor.processFileContent(e,t),o=this.messagesByFile.get(e),r=Array.from(o?.keys()??[]),c=new Map;for(let e of a.messages){const s=this.messagesById.get(e.id);if(s){const t=[...s.references??[]];e.references.forEach((e=>{t.some((s=>s.path===e.path))||t.push(e)})),t.sort(((e,s)=>i(e.path,s.path))),e={...e,references:t};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)}const l=s.relative(this.projectRoot,e);r.filter((e=>{const s=this.messagesById.get(e);return!s?.references?.some((e=>e.path!==l))})).forEach((e=>{this.messagesById.delete(e)}));a.messages.length>0?this.messagesByFile.set(e,c):this.messagesByFile.delete(e);const n=this.haveMessagesChangedForFile(o,c);return{...a,changed:n}}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(e,c);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)};destroy(){this.saveScheduler.destroy(),this.catalogLocales&&this.isDevelopment&&this.catalogLocales.unsubscribeLocalesChange(this.onLocalesChange)}}export{n 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,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 +1 @@
1
- import t from"fs/promises";import e from"path";class a{constructor(t,e){this.messagesPath=t,this.formatter=e}getFilePath(t){return e.join(this.messagesPath,t+this.formatter.EXTENSION)}async read(e){const a=this.getFilePath(e),r=await t.readFile(a,"utf8");return this.formatter.parse(r,{locale:e})}async write(a,r){const i=this.getFilePath(a),s=this.formatter.serialize(r,{locale:a});try{const a=e.dirname(i);await t.mkdir(a,{recursive:!0}),await t.writeFile(i,s)}catch(t){console.error(`❌ Failed to write catalog: ${t}`)}}async getLastModified(e){const a=this.getFilePath(e);try{return(await t.stat(a)).mtime}catch{return}}}export{a as default};
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 +1 @@
1
- class e{isSaving=!1;pendingResolvers=[];constructor(e=50){this.delayMs=e}async schedule(e){return new Promise(((s,i)=>{this.pendingResolvers.push({resolve:s,reject:i}),this.nextSaveTask=e,this.isSaving||this.saveTimeout?this.saveTimeout&&this.scheduleSave():this.executeSave()}))}scheduleSave(){this.saveTimeout&&clearTimeout(this.saveTimeout),this.saveTimeout=setTimeout((()=>{this.saveTimeout=void 0,this.executeSave()}),this.delayMs)}async executeSave(){if(this.isSaving)return;const e=this.nextSaveTask;if(!e)return;const s=this.pendingResolvers;this.pendingResolvers=[],this.nextSaveTask=void 0,this.isSaving=!0;try{const i=await e();s.forEach((({resolve:e})=>e(i)))}catch(e){s.forEach((({reject:s})=>s(e)))}finally{this.isSaving=!1,this.pendingResolvers.length>0&&this.scheduleSave()}}destroy(){this.saveTimeout&&(clearTimeout(this.saveTimeout),this.saveTimeout=void 0),this.pendingResolvers=[],this.nextSaveTask=void 0,this.isSaving=!1}}export{e as default};
1
+ class e{isSaving=!1;pendingResolvers=[];constructor(e=50){this.delayMs=e}async schedule(e){return new Promise(((s,i)=>{this.pendingResolvers.push({resolve:s,reject:i}),this.nextSaveTask=e,this.isSaving||this.saveTimeout?this.saveTimeout&&this.scheduleSave():this.executeSave()}))}scheduleSave(){this.saveTimeout&&clearTimeout(this.saveTimeout),this.saveTimeout=setTimeout((()=>{this.saveTimeout=void 0,this.executeSave()}),this.delayMs)}async executeSave(){if(this.isSaving)return;const e=this.nextSaveTask;if(!e)return;const s=this.pendingResolvers;this.pendingResolvers=[],this.nextSaveTask=void 0,this.isSaving=!0;try{const i=await e();s.forEach((({resolve:e})=>e(i)))}catch(e){s.forEach((({reject:s})=>s(e)))}finally{this.isSaving=!1,this.pendingResolvers.length>0&&this.scheduleSave()}}[Symbol.dispose](){this.saveTimeout&&(clearTimeout(this.saveTimeout),this.saveTimeout=void 0),this.pendingResolvers=[],this.nextSaveTask=void 0,this.isSaving=!1}}export{e as default};
@@ -1 +1 @@
1
- import t from"path";import e from"./formatters/index.js";let n=null;function s(s){const a=this.getOptions(),o=this.async();(async function(t){if(!n){const s=(await e[t.messages.format]()).default;n=new s}return n})(a).then((e=>{const n=t.basename(this.resourcePath,e.EXTENSION),a=e.toJSONString(s,{locale:n}),r=`export default JSON.parse(${JSON.stringify(a)});`;o(null,r)})).catch(o)}export{s as default};
1
+ import t from"path";import{getFormatExtension as s,resolveCodec as e}from"./format/index.js";let o=null;function a(a){const n=this.getOptions(),r=this.async(),i=s(n.messages.format);(async function(t,s){return o||(o=await e(t.messages.format,s)),o})(n,this.rootContext).then((s=>{const e=t.basename(this.resourcePath,i),o=s.toJSONString(a,{locale:e}),n=`export default JSON.parse(${JSON.stringify(o)});`;r(null,n)})).catch(r)}export{a as default};
@@ -1 +1 @@
1
- import t from"./ExtractionCompiler.js";async function a(a){const o=new t(a);await o.extract()}export{a as default};
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 +1 @@
1
- import e from"./ExtractionCompiler.js";let t;function o(o){const s=this.getOptions(),c=this.async();t||(t=new e(s,{isDevelopment:"development"===process.env["NODE_ENV".trim()],sourceMap:this.sourceMap})),t.compile(this.resourcePath,o).then((e=>{c(null,e.code,e.map)})).catch(c)}export{o as default};
1
+ import t from"./extractor/MessageExtractor.js";let e;function o(o){const r=this.async(),s=this.rootContext,c="development"===process.env["NODE_ENV".trim()];e||(e=new t({isDevelopment:c,projectRoot:s,sourceMap:this.sourceMap})),e.extract(this.resourcePath,o).then((t=>{r(null,t.code,t.map)})).catch(r)}export{o as default};
@@ -1 +1 @@
1
- import{createRequire as e}from"module";import t from"path";import{transform as s}from"@swc/core";import o from"./LRUCache.js";const r=e(import.meta.url);class c{compileCache=(()=>new o(750))();constructor(e){this.isDevelopment=e.isDevelopment,this.projectRoot=e.projectRoot,this.sourceMap=e.sourceMap??!1}async processFileContent(e,o){const c=o,i=this.compileCache.get(c);if(i)return i;if(!o.includes("useExtracted")&&!o.includes("getExtracted"))return{messages:[],code:o};const a=t.relative(this.projectRoot,e),p=await s(o,{jsc:{target:"esnext",parser:{syntax:"typescript",tsx:!0,decorators:!0},experimental:{cacheRoot:"node_modules/.cache/swc",disableBuiltinTransformsForInternalTesting:!0,disableAllLints:!0,plugins:[[r.resolve("next-intl-swc-plugin-extractor"),{isDevelopment:this.isDevelopment,filePath:a}]]}},sourceMaps:this.sourceMap,sourceFileName:a,filename:a}),n=p.output,l=JSON.parse(JSON.parse(n).results),m={code:p.code,map:p.map,messages:l};return this.compileCache.set(c,m),m}}export{c as default};
1
+ import{createRequire as e}from"module";import t from"path";import{transform as s}from"@swc/core";import{getDefaultProjectRoot as o}from"../utils.js";import r from"./LRUCache.js";const i=e(import.meta.url);class c{compileCache=(()=>new r(750))();constructor(e){this.isDevelopment=e.isDevelopment??!1,this.projectRoot=e.projectRoot??o(),this.sourceMap=e.sourceMap??!1}async extract(e,o){const r=[o,e].join("!"),c=this.compileCache.get(r);if(c)return c;if(!o.includes("useExtracted")&&!o.includes("getExtracted"))return{messages:[],code:o};const a=t.relative(this.projectRoot,e),p=await s(o,{jsc:{target:"esnext",parser:{syntax:"typescript",tsx:!0,decorators:!0},experimental:{cacheRoot:"node_modules/.cache/swc",disableBuiltinTransformsForInternalTesting:!0,disableAllLints:!0,plugins:[[i.resolve("next-intl-swc-plugin-extractor"),{isDevelopment:this.isDevelopment,filePath:a}]]}},sourceMaps:this.sourceMap,sourceFileName:a,filename:a}),l=p.output,n=JSON.parse(JSON.parse(l).results),m={code:p.code,map:p.map,messages:n};return this.compileCache.set(r,m),m}}export{c as default};
@@ -0,0 +1 @@
1
+ function n(n){return n}export{n as defineCodec};
@@ -0,0 +1 @@
1
+ import{getSortedMessages as o,setNestedProperty as t}from"../../utils.js";import{defineCodec as e}from"../ExtractorCodec.js";var r=e((()=>({decode(o){const t=JSON.parse(o),e=[];return s(t,((o,t)=>{e.push({id:t,message:o})})),e},encode(e){const r={};for(const s of o(e))t(r,s.id,s.message);return JSON.stringify(r,null,2)+"\n"},toJSONString:o=>o})));function s(o,t,e=""){for(const r of Object.keys(o)){const n=e?e+"."+r:r,c=o[r];"string"==typeof c?t(c,n):"object"==typeof c&&s(c,t,n)}}export{r as default};
@@ -0,0 +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};
@@ -0,0 +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 +1 @@
1
- import e from"fs/promises";import i from"path";import r from"./SourceFileFilter.js";class t{static async walkSourceFiles(o,s,a=[]){const l=await e.readdir(o,{withFileTypes:!0});for(const e of l){const l=i.join(o,e.name);if(e.isDirectory()){if(!r.shouldEnterDirectory(l,s))continue;await t.walkSourceFiles(l,s,a)}else r.isSourceFile(e.name)&&a.push(l)}return a}static async getSourceFiles(e){return(await Promise.all(e.map((i=>t.walkSourceFiles(i,e))))).flat()}}export{t as default};
1
+ import e from"fs/promises";import i from"path";import r from"./SourceFileFilter.js";class t{static async walkSourceFiles(o,s,a=[]){const c=await e.readdir(o,{withFileTypes:!0});for(const e of c){const c=i.join(o,e.name);if(e.isDirectory()){if(!r.shouldEnterDirectory(c,s))continue;await t.walkSourceFiles(c,s,a)}else r.isSourceFile(e.name)&&a.push(c)}return a}static async getSourceFiles(e){const i=(await Promise.all(e.map((i=>t.walkSourceFiles(i,e))))).flat();return new Set(i)}}export{t as default};
@@ -0,0 +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 +1 @@
1
- function t(t,n,e){const o=n.split(".");let l=t;for(let t=0;t<o.length-1;t++){const n=o[t];n in l&&"object"==typeof l[n]&&null!==l[n]||(l[n]={}),l=l[n]}l[o[o.length-1]]=e}function n(t,n){return t.localeCompare(n,"en")}export{n as localeCompare,t as setNestedProperty};
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 +1 @@
1
- export{default as unstable_extractMessages}from"./extractor/extractMessages.js";
1
+ export{default as unstable_extractMessages}from"./extractor/extractMessages.js";export{defineCodec}from"./extractor/format/ExtractorCodec.js";
@@ -1 +1 @@
1
- import e from"./getNextConfig.js";import{warn as t}from"./utils.js";import n from"./declaration/createMessagesDeclaration.js";function r(r={}){const o="string"==typeof r?{requestConfig:r}:r;return function(r){return function(r,o){null!=o?.i18n&&t("\n[next-intl] 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 i=r.experimental?.createMessagesDeclaration;return i&&n("string"==typeof i?[i]:i),e(r,o)}(o,r)}}export{r as default};
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={}){const i="string"==typeof n?{requestConfig:n}:n;return function(n){return function(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=n.experimental?.createMessagesDeclaration;return s&&o("string"==typeof s?[s]:s),e(n),t(n,i)}(i,n)}}export{n as default};
@@ -1 +1 @@
1
- import e from"fs";import s from"path";import{throwError as t}from"../utils.js";import o from"../watchFile.js";function n(o){var n;["info","start"].some((e=>process.argv.includes(e)))||(n=()=>{for(const n of o){const o=s.resolve(n);e.existsSync(o)||t(`\`createMessagesDeclaration\` points to a non-existent file: ${o}`),o.endsWith(".json")||t(`\`createMessagesDeclaration\` needs to point to a JSON file. Received: ${o}`);const c=process.env["NODE_ENV".trim()];i(n),"development"===c&&r(n)}},"1"!==process.env._NEXT_INTL_COMPILE_MESSAGES&&(process.env._NEXT_INTL_COMPILE_MESSAGES="1",n()))}function r(e){const s=o(e,(()=>{i(e,!0)}));process.on("exit",(()=>{s.close()}))}function i(s,t=!1){const o=s.replace(/\.json$/,".d.json.ts");function n(e){return`// This file is auto-generated by next-intl, do not edit directly.\n// See: https://next-intl.dev/docs/workflows/typescript#messages-arguments\n\ndeclare const messages: ${e.trim()};\nexport default messages;`}if(t)return e.promises.readFile(s,"utf-8").then((s=>e.promises.writeFile(o,n(s))));const r=e.readFileSync(s,"utf-8");e.writeFileSync(o,n(r))}export{n as default};
1
+ import e from"fs";import t from"path";import{once as s,throwError as o}from"../utils.js";import n from"../watchFile.js";const i=s("_NEXT_INTL_COMPILE_MESSAGES");function r(s){["info","start"].some((e=>process.argv.includes(e)))||i((()=>{for(const n of s){const s=t.resolve(n);e.existsSync(s)||o(`\`createMessagesDeclaration\` points to a non-existent file: ${s}`),s.endsWith(".json")||o(`\`createMessagesDeclaration\` needs to point to a JSON file. Received: ${s}`);const i=process.env["NODE_ENV".trim()];a(n),"development"===i&&c(n)}}))}function c(e){const t=n(e,(()=>{a(e,!0)}));process.on("exit",(()=>{t.close()}))}function a(t,s=!1){const o=t.replace(/\.json$/,".d.json.ts");function n(e){return`// This file is auto-generated by next-intl, do not edit directly.\n// See: https://next-intl.dev/docs/workflows/typescript#messages-arguments\n\ndeclare const messages: ${e.trim()};\nexport default messages;`}if(s)return e.promises.readFile(t,"utf-8").then((t=>e.promises.writeFile(o,n(t))));const i=e.readFileSync(t,"utf-8");e.writeFileSync(o,n(i))}export{r as default};