next-intl 4.5.7 → 4.6.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 (78) hide show
  1. package/dist/cjs/development/ExtractorCodec-DZKNn0Zq.cjs +37 -0
  2. package/dist/cjs/development/JSONCodec-Dlcx71xz.cjs +41 -0
  3. package/dist/cjs/development/POCodec-BW-UDNcq.cjs +94 -0
  4. package/dist/cjs/development/plugin.cjs +28 -5
  5. package/dist/esm/development/extractor/ExtractionCompiler.js +22 -25
  6. package/dist/esm/development/extractor/catalog/CatalogLocales.js +0 -33
  7. package/dist/esm/development/extractor/catalog/CatalogManager.js +134 -102
  8. package/dist/esm/development/extractor/catalog/CatalogPersister.js +31 -13
  9. package/dist/esm/development/extractor/catalog/SaveScheduler.js +33 -14
  10. package/dist/esm/development/extractor/catalogLoader.js +10 -10
  11. package/dist/esm/development/extractor/extractMessages.js +9 -2
  12. package/dist/esm/development/extractor/extractionLoader.js +27 -4
  13. package/dist/esm/development/extractor/extractor/MessageExtractor.js +5 -4
  14. package/dist/esm/development/extractor/format/ExtractorCodec.js +5 -0
  15. package/dist/esm/development/extractor/format/codecs/JSONCodec.js +40 -0
  16. package/dist/esm/development/extractor/format/codecs/POCodec.js +93 -0
  17. package/dist/esm/development/extractor/format/index.js +44 -0
  18. package/dist/esm/development/extractor/source/SourceFileScanner.js +2 -1
  19. package/dist/esm/development/extractor/source/SourceFileWatcher.js +38 -0
  20. package/dist/esm/development/extractor/utils.js +16 -1
  21. package/dist/esm/development/extractor.js +1 -0
  22. package/dist/esm/development/plugin/createNextIntlPlugin.js +1 -1
  23. package/dist/esm/development/plugin/getNextConfig.js +7 -4
  24. package/dist/esm/production/extractor/ExtractionCompiler.js +1 -1
  25. package/dist/esm/production/extractor/catalog/CatalogLocales.js +1 -1
  26. package/dist/esm/production/extractor/catalog/CatalogManager.js +1 -1
  27. package/dist/esm/production/extractor/catalog/CatalogPersister.js +1 -1
  28. package/dist/esm/production/extractor/catalog/SaveScheduler.js +1 -1
  29. package/dist/esm/production/extractor/catalogLoader.js +1 -1
  30. package/dist/esm/production/extractor/extractMessages.js +1 -1
  31. package/dist/esm/production/extractor/extractionLoader.js +1 -1
  32. package/dist/esm/production/extractor/extractor/MessageExtractor.js +1 -1
  33. package/dist/esm/production/extractor/format/ExtractorCodec.js +1 -0
  34. package/dist/esm/production/extractor/format/codecs/JSONCodec.js +1 -0
  35. package/dist/esm/production/extractor/format/codecs/POCodec.js +1 -0
  36. package/dist/esm/production/extractor/format/index.js +1 -0
  37. package/dist/esm/production/extractor/source/SourceFileScanner.js +1 -1
  38. package/dist/esm/production/extractor/source/SourceFileWatcher.js +1 -0
  39. package/dist/esm/production/extractor/utils.js +1 -1
  40. package/dist/esm/production/extractor.js +1 -1
  41. package/dist/esm/production/plugin/createNextIntlPlugin.js +1 -1
  42. package/dist/esm/production/plugin/getNextConfig.js +1 -1
  43. package/dist/types/extractor/ExtractionCompiler.d.ts +5 -10
  44. package/dist/types/extractor/catalog/CatalogLocales.d.ts +0 -2
  45. package/dist/types/extractor/catalog/CatalogManager.d.ts +21 -11
  46. package/dist/types/extractor/catalog/CatalogPersister.d.ts +15 -6
  47. package/dist/types/extractor/catalog/SaveScheduler.d.ts +1 -0
  48. package/dist/types/extractor/extractor/MessageExtractor.d.ts +6 -6
  49. package/dist/types/extractor/format/ExtractorCodec.d.ts +33 -0
  50. package/dist/types/extractor/format/codecs/JSONCodec.d.ts +2 -0
  51. package/dist/types/extractor/format/codecs/POCodec.d.ts +2 -0
  52. package/dist/types/extractor/format/codecs/fixtures/JSONCodecStructured.d.ts +2 -0
  53. package/dist/types/extractor/format/codecs/fixtures/POCodecSourceMessageKey.d.ts +2 -0
  54. package/dist/types/extractor/format/index.d.ts +15 -0
  55. package/dist/types/extractor/format/types.d.ts +8 -0
  56. package/dist/types/extractor/index.d.ts +1 -0
  57. package/dist/types/extractor/source/SourceFileFilter.d.ts +1 -1
  58. package/dist/types/extractor/source/SourceFileScanner.d.ts +1 -1
  59. package/dist/types/extractor/source/SourceFileWatcher.d.ts +12 -0
  60. package/dist/types/extractor/types.d.ts +2 -2
  61. package/dist/types/extractor/utils.d.ts +3 -0
  62. package/dist/types/plugin/types.d.ts +1 -1
  63. package/package.json +6 -5
  64. package/dist/esm/development/extractor/formatters/Formatter.js +0 -3
  65. package/dist/esm/development/extractor/formatters/JSONFormatter.js +0 -42
  66. package/dist/esm/development/extractor/formatters/POFormatter.js +0 -51
  67. package/dist/esm/development/extractor/formatters/index.js +0 -6
  68. package/dist/esm/development/extractor/formatters/utils.js +0 -15
  69. package/dist/esm/production/extractor/formatters/Formatter.js +0 -1
  70. package/dist/esm/production/extractor/formatters/JSONFormatter.js +0 -1
  71. package/dist/esm/production/extractor/formatters/POFormatter.js +0 -1
  72. package/dist/esm/production/extractor/formatters/index.js +0 -1
  73. package/dist/esm/production/extractor/formatters/utils.js +0 -1
  74. package/dist/types/extractor/formatters/Formatter.d.ts +0 -10
  75. package/dist/types/extractor/formatters/JSONFormatter.d.ts +0 -10
  76. package/dist/types/extractor/formatters/POFormatter.d.ts +0 -10
  77. package/dist/types/extractor/formatters/index.d.ts +0 -5
  78. package/dist/types/extractor/formatters/utils.d.ts +0 -2
@@ -0,0 +1,40 @@
1
+ import { getSortedMessages, setNestedProperty } from '../../utils.js';
2
+ import { defineCodec } from '../ExtractorCodec.js';
3
+
4
+ var JSONCodec = defineCodec(() => ({
5
+ decode(source) {
6
+ const json = JSON.parse(source);
7
+ const messages = [];
8
+ traverseMessages(json, (message, id) => {
9
+ messages.push({
10
+ id,
11
+ message
12
+ });
13
+ });
14
+ return messages;
15
+ },
16
+ encode(messages) {
17
+ const root = {};
18
+ for (const message of getSortedMessages(messages)) {
19
+ setNestedProperty(root, message.id, message.message);
20
+ }
21
+ return JSON.stringify(root, null, 2) + '\n';
22
+ },
23
+ toJSONString(source) {
24
+ return source;
25
+ }
26
+ }));
27
+ function traverseMessages(obj, callback, path = '') {
28
+ const NAMESPACE_SEPARATOR = '.';
29
+ for (const key of Object.keys(obj)) {
30
+ const newPath = path ? path + NAMESPACE_SEPARATOR + key : key;
31
+ const value = obj[key];
32
+ if (typeof value === 'string') {
33
+ callback(value, newPath);
34
+ } else if (typeof value === 'object') {
35
+ traverseMessages(value, callback, newPath);
36
+ }
37
+ }
38
+ }
39
+
40
+ export { JSONCodec as default };
@@ -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,38 @@
1
+ import { subscribe } from '@parcel/watcher';
2
+ import SourceFileFilter from './SourceFileFilter.js';
3
+
4
+ class SourceFileWatcher {
5
+ subscriptions = [];
6
+ constructor(roots, onChange) {
7
+ this.roots = roots;
8
+ this.onChange = onChange;
9
+ }
10
+ async start() {
11
+ if (this.subscriptions.length > 0) return;
12
+ const ignore = SourceFileFilter.IGNORED_DIRECTORIES.map(dir => `**/${dir}/**`);
13
+ for (const root of this.roots) {
14
+ const sub = await subscribe(root, (err, events) => {
15
+ if (err) {
16
+ console.error(err);
17
+ return;
18
+ }
19
+ const filteredEvents = events.filter(event => SourceFileFilter.isSourceFile(event.path));
20
+ if (filteredEvents.length > 0) {
21
+ void this.onChange(filteredEvents);
22
+ }
23
+ }, {
24
+ ignore
25
+ });
26
+ this.subscriptions.push(sub);
27
+ }
28
+ }
29
+ async stop() {
30
+ await Promise.all(this.subscriptions.map(sub => sub.unsubscribe()));
31
+ this.subscriptions = [];
32
+ }
33
+ [Symbol.dispose]() {
34
+ void this.stop();
35
+ }
36
+ }
37
+
38
+ 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';
@@ -4,7 +4,7 @@ import createMessagesDeclaration from './declaration/createMessagesDeclaration.j
4
4
 
5
5
  function initPlugin(pluginConfig, nextConfig) {
6
6
  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");
7
+ 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
8
  }
9
9
  const messagesPathOrPaths = pluginConfig.experimental?.createMessagesDeclaration;
10
10
  if (messagesPathOrPaths) {
@@ -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'
@@ -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.destroy()}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();this.loadCatalogsPromise=this.loadTargetMessages(),await this.loadCatalogsPromise;const s=await o.getSourceFiles(this.getSrcPaths());if(await Promise.all(Array.from(s).map((async e=>this.processFile(e)))),this.mergeSourceDiskMetadata(e),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=new Map;for(const e of s)t.set(e.id,e);this.translationsByTargetLocale.set(e,t)}}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");a=(await this.extractor.extract(t,s)).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;let s=!1;for(const t of e){const e=await this.processFile(t.path);s||=e}s&&await this.save()}destroy(){this.sourceWatcher?.stop(),this.sourceWatcher=void 0,this.saveScheduler.destroy(),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}),1!==this.pendingResolvers.length||this.isSaving?this.pendingResolvers.length>1&&this.scheduleSave(e):this.executeSave(e)}))}scheduleSave(e){this.saveTimeout&&clearTimeout(this.saveTimeout),this.saveTimeout=setTimeout((()=>{this.executeSave(e)}),this.delayMs)}async executeSave(e){if(!this.isSaving){this.isSaving=!0;try{const s=await e();this.pendingResolvers.forEach((({resolve:e})=>e(s)))}catch(e){this.pendingResolvers.forEach((({reject:s})=>s(e)))}finally{this.pendingResolvers=[],this.isSaving=!1}}}destroy(){this.saveTimeout&&(clearTimeout(this.saveTimeout),this.saveTimeout=void 0),this.pendingResolvers=[],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()}}destroy(){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"./ExtractionCompiler.js";import e from"./extractor/MessageExtractor.js";let o,r,s;function c(c){const a=this.getOptions(),i=this.async(),n=this.rootContext,p="development"===process.env["NODE_ENV".trim()];r||(r=new e({isDevelopment:p,projectRoot:n,sourceMap:this.sourceMap})),o||(o=new t(a,{isDevelopment:p,projectRoot:n,sourceMap:this.sourceMap,extractor:r})),s||(s=o.extractAll()),r.extract(this.resourcePath,c).then((async t=>{p||await s,i(null,t.code,t.map)})).catch(i)}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 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{subscribe as s}from"@parcel/watcher";import t from"./SourceFileFilter.js";class o{subscriptions=[];constructor(s,t){this.roots=s,this.onChange=t}async start(){if(this.subscriptions.length>0)return;const o=t.IGNORED_DIRECTORIES.map((s=>`**/${s}/**`));for(const i of this.roots){const r=await s(i,((s,o)=>{if(s)return void console.error(s);const i=o.filter((s=>t.isSourceFile(s.path)));i.length>0&&this.onChange(i)}),{ignore:o});this.subscriptions.push(r)}}async stop(){await Promise.all(this.subscriptions.map((s=>s.unsubscribe()))),this.subscriptions=[]}[Symbol.dispose](){this.stop()}}export{o 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"./getNextConfig.js";import{warn as t}from"./utils.js";import r from"./declaration/createMessagesDeclaration.js";function o(o={}){const n="string"==typeof o?{requestConfig:o}:o;return function(o){return function(o,n){null!=n?.i18n&&t("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=o.experimental?.createMessagesDeclaration;return i&&r("string"==typeof i?[i]:i),e(o,n)}(n,o)}}export{o as default};
@@ -1 +1 @@
1
- import e from"fs";import t from"path";import r from"../extractor/source/SourceFileFilter.js";import{isNextJs16OrHigher as s,hasStableTurboConfig as n}from"./nextFlags.js";import{throwError as o}from"./utils.js";function a(e){return[`${e}.ts`,`${e}.tsx`,`${e}.js`,`${e}.jsx`]}function i(r,s){function n(r){return e.existsSync(function(e){const r=[];return s&&r.push(s),r.push(e),t.resolve(...r)}(r))}if(r)return n(r)||o(`Could not find i18n config at ${r}, please provide a valid path.`),r;for(const e of[...a("./i18n/request"),...a("./src/i18n/request")])if(n(e))return e;o("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\nAlternatively, you can specify a custom location in your Next.js config:\n\nconst withNextIntl = createNextIntlPlugin(\n './path/to/i18n/request.tsx'\n);")}function l(e,a){const l={};function u(){const t=e.experimental;return t.srcPath&&t.messages||o("`srcPath` and `messages` are required when using `extractor`."),{loader:"next-intl/extractor/extractionLoader",options:{srcPath:t.srcPath,sourceLocale:t.extract.sourceLocale,messages:t.messages}}}function c(){return{loader:"next-intl/extractor/catalogLoader",options:{messages:e.experimental.messages}}}function p(){return a?.turbopack?.rules||a?.experimental?.turbo?.rules||{}}function x(e,t,r){e[t]?Array.isArray(e[t])?e[t].push(r):e[t]=[e[t],r]:e[t]=r}if(null!=process.env.TURBOPACK){e.requestConfig&&t.isAbsolute(e.requestConfig)&&o("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 m={"next-intl/config":i(e.requestConfig)};let f;if(e.experimental?.extract){s()||o("Message extraction requires Next.js 16 or higher."),f??=p();const t=(Array.isArray(e.experimental.srcPath)?e.experimental.srcPath:[e.experimental.srcPath]).map((e=>e.endsWith("/")?e.slice(0,-1):e));x(f,`*.{${r.EXTENSIONS.join(",")}}`,{loaders:[u()],condition:{path:`{${t.join(",")}}/**/*`,content:/(useExtracted|getExtracted)/}})}e.experimental?.messages&&(s()||o("Message catalog loading requires Next.js 16 or higher."),f??=p(),x(f,`*.${e.experimental.messages.format}`,{loaders:[c()],condition:{path:`${e.experimental.messages.path}/**/*`},as:"*.js"})),n()&&!a?.experimental?.turbo?l.turbopack={...a?.turbopack,...f&&{rules:f},resolveAlias:{...a?.turbopack?.resolveAlias,...m}}:l.experimental={...a?.experimental,turbo:{...a?.experimental?.turbo,...f&&{rules:f},resolveAlias:{...a?.experimental?.turbo?.resolveAlias,...m}}}}else l.webpack=function(s,n){if(s.resolve||(s.resolve={}),s.resolve.alias||(s.resolve.alias={}),s.resolve.alias["next-intl/config"]=t.resolve(s.context,i(e.requestConfig,s.context)),e.experimental?.extract){s.module||(s.module={}),s.module.rules||(s.module.rules=[]);const n=e.experimental.srcPath;s.module.rules.push({test:new RegExp(`\\.(${r.EXTENSIONS.join("|")})$`),include:Array.isArray(n)?n.map((e=>t.resolve(s.context,e))):t.resolve(s.context,n||""),use:[u()]})}return e.experimental?.messages&&(s.module||(s.module={}),s.module.rules||(s.module.rules=[]),s.module.rules.push({test:new RegExp(`\\.${e.experimental.messages.format}$`),include:t.resolve(s.context,e.experimental.messages.path),use:[c()],type:"javascript/auto"})),"function"==typeof a?.webpack?a.webpack(s,n):s};return a?.trailingSlash&&(l.env={...a.env,_next_intl_trailing_slash:"true"}),Object.assign({},a,l)}export{l as default};
1
+ import e from"fs";import t from"path";import{getFormatExtension as r}from"../extractor/format/index.js";import s from"../extractor/source/SourceFileFilter.js";import{isNextJs16OrHigher as n,hasStableTurboConfig as o}from"./nextFlags.js";import{throwError as a}from"./utils.js";function i(e){return[`${e}.ts`,`${e}.tsx`,`${e}.js`,`${e}.jsx`]}function l(r,s){function n(r){return e.existsSync(function(e){const r=[];return s&&r.push(s),r.push(e),t.resolve(...r)}(r))}if(r)return n(r)||a(`Could not find i18n config at ${r}, please provide a valid path.`),r;for(const e of[...i("./i18n/request"),...i("./src/i18n/request")])if(n(e))return e;a("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\nAlternatively, you can specify a custom location in your Next.js config:\n\nconst withNextIntl = createNextIntlPlugin(\n './path/to/i18n/request.tsx'\n);")}function u(e,i){const u={};function c(){const t=e.experimental;return t.srcPath&&e.experimental?.messages||a("`srcPath` and `messages` are required when using `extractor`."),{loader:"next-intl/extractor/extractionLoader",options:{srcPath:t.srcPath,sourceLocale:t.extract.sourceLocale,messages:e.experimental.messages}}}function p(){return{loader:"next-intl/extractor/catalogLoader",options:{messages:e.experimental.messages}}}function m(){return i?.turbopack?.rules||i?.experimental?.turbo?.rules||{}}function x(e,t,r){e[t]?Array.isArray(e[t])?e[t].push(r):e[t]=[e[t],r]:e[t]=r}if(null!=process.env.TURBOPACK){e.requestConfig&&t.isAbsolute(e.requestConfig)&&a("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 f={"next-intl/config":l(e.requestConfig)};let g;if(e.experimental?.extract){n()||a("Message extraction requires Next.js 16 or higher."),g??=m();const t=(Array.isArray(e.experimental.srcPath)?e.experimental.srcPath:[e.experimental.srcPath]).map((e=>e.endsWith("/")?e.slice(0,-1):e));x(g,`*.{${s.EXTENSIONS.join(",")}}`,{loaders:[c()],condition:{path:`{${t.join(",")}}/**/*`,content:/(useExtracted|getExtracted)/}})}if(e.experimental?.messages){n()||a("Message catalog loading requires Next.js 16 or higher."),g??=m();x(g,`*${r(e.experimental.messages.format)}`,{loaders:[p()],condition:{path:`${e.experimental.messages.path}/**/*`},as:"*.js"})}o()&&!i?.experimental?.turbo?u.turbopack={...i?.turbopack,...g&&{rules:g},resolveAlias:{...i?.turbopack?.resolveAlias,...f}}:u.experimental={...i?.experimental,turbo:{...i?.experimental?.turbo,...g&&{rules:g},resolveAlias:{...i?.experimental?.turbo?.resolveAlias,...f}}}}else u.webpack=function(n,o){if(n.resolve||(n.resolve={}),n.resolve.alias||(n.resolve.alias={}),n.resolve.alias["next-intl/config"]=t.resolve(n.context,l(e.requestConfig,n.context)),e.experimental?.extract){n.module||(n.module={}),n.module.rules||(n.module.rules=[]);const r=e.experimental.srcPath;n.module.rules.push({test:new RegExp(`\\.(${s.EXTENSIONS.join("|")})$`),include:Array.isArray(r)?r.map((e=>t.resolve(n.context,e))):t.resolve(n.context,r||""),use:[c()]})}if(e.experimental?.messages){n.module||(n.module={}),n.module.rules||(n.module.rules=[]);const s=r(e.experimental.messages.format);n.module.rules.push({test:new RegExp(`${s.replace(/\./g,"\\.")}$`),include:t.resolve(n.context,e.experimental.messages.path),use:[p()],type:"javascript/auto"})}return"function"==typeof i?.webpack?i.webpack(n,o):n};return i?.trailingSlash&&(u.env={...i.env,_next_intl_trailing_slash:"true"}),Object.assign({},i,u)}export{u as default};
@@ -1,20 +1,15 @@
1
+ import MessageExtractor from './extractor/MessageExtractor.js';
1
2
  import type { ExtractorConfig } from './types.js';
2
3
  export default class ExtractionCompiler implements Disposable {
3
4
  private manager;
4
- private isDevelopment;
5
- private initialScanPromise;
6
5
  constructor(config: ExtractorConfig, opts?: {
7
6
  isDevelopment?: boolean;
8
7
  projectRoot?: string;
9
8
  sourceMap?: boolean;
9
+ extractor?: MessageExtractor;
10
10
  });
11
- compile(resourcePath: string, source: string): Promise<{
12
- messages: Array<import("./types.js").ExtractedMessage>;
13
- code: string;
14
- changed: boolean;
15
- map?: string;
16
- }>;
17
- private performInitialScan;
18
- extract(): Promise<void>;
11
+ extractAll(): Promise<void>;
19
12
  [Symbol.dispose](): void;
13
+ private installExitHandlers;
14
+ private uninstallExitHandlers;
20
15
  }
@@ -15,7 +15,6 @@ export default class CatalogLocales {
15
15
  private sourceLocale;
16
16
  private locales;
17
17
  private watcher?;
18
- private cleanupHandlers;
19
18
  private targetLocales?;
20
19
  private onChangeCallbacks;
21
20
  constructor(params: CatalogLocalesParams);
@@ -26,6 +25,5 @@ export default class CatalogLocales {
26
25
  private startWatcher;
27
26
  private stopWatcher;
28
27
  private onChange;
29
- private setupCleanupHandlers;
30
28
  }
31
29
  export {};
@@ -1,7 +1,17 @@
1
- import type { ExtractedMessage, ExtractorConfig } from '../types.js';
1
+ import type MessageExtractor from '../extractor/MessageExtractor.js';
2
+ import type { ExtractorConfig } from '../types.js';
2
3
  export default class CatalogManager {
3
4
  private config;
5
+ /**
6
+ * The source of truth for which messages are used.
7
+ * NOTE: Should be mutated in place to keep `messagesById` and `messagesByFile` in sync.
8
+ */
4
9
  private messagesByFile;
10
+ /**
11
+ * Fast lookup for messages by ID across all files,
12
+ * contains the same messages as `messagesByFile`.
13
+ * NOTE: Should be mutated in place to keep `messagesById` and `messagesByFile` in sync.
14
+ */
5
15
  private messagesById;
6
16
  /**
7
17
  * This potentially also includes outdated ones that were initially available,
@@ -13,16 +23,18 @@ export default class CatalogManager {
13
23
  private projectRoot;
14
24
  private isDevelopment;
15
25
  private persister?;
16
- private formatter?;
26
+ private codec?;
17
27
  private catalogLocales?;
18
- private messageExtractor;
28
+ private extractor;
29
+ private sourceWatcher?;
19
30
  loadCatalogsPromise?: Promise<unknown>;
20
- constructor(config: ExtractorConfig, opts?: {
31
+ constructor(config: ExtractorConfig, opts: {
21
32
  projectRoot?: string;
22
33
  isDevelopment?: boolean;
23
34
  sourceMap?: boolean;
35
+ extractor: MessageExtractor;
24
36
  });
25
- private getFormatter;
37
+ private getCodec;
26
38
  private getPersister;
27
39
  private getCatalogLocales;
28
40
  private getTargetLocales;
@@ -32,17 +44,15 @@ export default class CatalogManager {
32
44
  private loadLocaleMessages;
33
45
  private loadTargetMessages;
34
46
  private reloadLocaleCatalog;
35
- extractFileMessages(absoluteFilePath: string, source: string): Promise<{
36
- messages: Array<ExtractedMessage>;
37
- code: string;
38
- changed: boolean;
39
- map?: string;
40
- }>;
47
+ private mergeSourceDiskMetadata;
48
+ processFile(absoluteFilePath: string): Promise<boolean>;
49
+ private mergeReferences;
41
50
  private haveMessagesChangedForFile;
42
51
  private areMessagesEqual;
43
52
  save(): Promise<void>;
44
53
  private saveImpl;
45
54
  private saveLocale;
46
55
  private onLocalesChange;
56
+ private handleFileEvents;
47
57
  destroy(): void;
48
58
  }
@@ -1,11 +1,20 @@
1
- import type Formatter from '../formatters/Formatter.js';
2
- import type { ExtractedMessage, Locale } from '../types.js';
1
+ import type ExtractorCodec from '../format/ExtractorCodec.js';
2
+ import type { ExtractorMessage, Locale } from '../types.js';
3
3
  export default class CatalogPersister {
4
4
  private messagesPath;
5
- private formatter;
6
- constructor(messagesPath: string, formatter: Formatter);
5
+ private codec;
6
+ private extension;
7
+ constructor(params: {
8
+ messagesPath: string;
9
+ codec: ExtractorCodec;
10
+ extension: string;
11
+ });
12
+ private getFileName;
7
13
  private getFilePath;
8
- read(locale: Locale): Promise<Array<ExtractedMessage>>;
9
- write(locale: Locale, messages: Array<ExtractedMessage>): Promise<void>;
14
+ read(locale: Locale): Promise<Array<ExtractorMessage>>;
15
+ write(messages: Array<ExtractorMessage>, context: {
16
+ locale: Locale;
17
+ sourceMessagesById: Map<string, ExtractorMessage>;
18
+ }): Promise<void>;
10
19
  getLastModified(locale: Locale): Promise<Date | undefined>;
11
20
  }