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 @@
1
+ import e from"../../extractor/ExtractionCompiler.js";import{once as o}from"../utils.js";let s;const t=o("_NEXT_INTL_EXTRACT");function r(o){const r=o.experimental;r?.extract&&t((()=>{const o="development"===process.env["NODE_ENV".trim()],t={srcPath:r.srcPath,sourceLocale:r.extract.sourceLocale,messages:r.messages};function c(){s&&(s[Symbol.dispose](),s=void 0)}s=new e(t,{isDevelopment:o,projectRoot:process.cwd()}),s.extractAll(),process.on("exit",c),process.on("SIGINT",c),process.on("SIGTERM",c)}))}export{r as default};
@@ -1 +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 +1 @@
1
- function n(n){return`\n[next-intl] ${n}\n`}function o(o){throw new Error(n(o))}function r(o){console.warn(n(o))}export{o as throwError,r as warn};
1
+ function n(n){return`\n[next-intl] ${n}\n`}function o(o){throw new Error(n(o))}function r(o){console.warn(n(o))}function t(n){return function(o){"1"!==process.env[n]&&(process.env[n]="1",o())}}export{t as once,o as throwError,r as warn};
@@ -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';
2
- export default class CatalogManager {
1
+ import type MessageExtractor from '../extractor/MessageExtractor.js';
2
+ import type { ExtractorConfig } from '../types.js';
3
+ export default class CatalogManager implements Disposable {
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,36 +23,37 @@ 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;
19
- loadCatalogsPromise?: Promise<unknown>;
20
- constructor(config: ExtractorConfig, opts?: {
28
+ private extractor;
29
+ private sourceWatcher?;
30
+ private loadCatalogsPromise?;
31
+ private scanCompletePromise?;
32
+ constructor(config: ExtractorConfig, opts: {
21
33
  projectRoot?: string;
22
34
  isDevelopment?: boolean;
23
35
  sourceMap?: boolean;
36
+ extractor: MessageExtractor;
24
37
  });
25
- private getFormatter;
38
+ private getCodec;
26
39
  private getPersister;
27
40
  private getCatalogLocales;
28
41
  private getTargetLocales;
29
- getSrcPaths(): Array<string>;
42
+ private getSrcPaths;
30
43
  loadMessages(): Promise<void>;
31
44
  private loadSourceMessages;
32
45
  private loadLocaleMessages;
33
46
  private loadTargetMessages;
34
47
  private reloadLocaleCatalog;
35
- extractFileMessages(absoluteFilePath: string, source: string): Promise<{
36
- messages: Array<ExtractedMessage>;
37
- code: string;
38
- changed: boolean;
39
- map?: string;
40
- }>;
48
+ private mergeSourceDiskMetadata;
49
+ private processFile;
50
+ private mergeReferences;
41
51
  private haveMessagesChangedForFile;
42
52
  private areMessagesEqual;
43
53
  save(): Promise<void>;
44
54
  private saveImpl;
45
55
  private saveLocale;
46
56
  private onLocalesChange;
47
- destroy(): void;
57
+ private handleFileEvents;
58
+ [Symbol.dispose](): void;
48
59
  }
@@ -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
  }
@@ -3,7 +3,7 @@ type SaveTask<T> = () => Promise<T>;
3
3
  * De-duplicates excessive save invocations,
4
4
  * while keeping a single one instant.
5
5
  */
6
- export default class SaveScheduler<Value> {
6
+ export default class SaveScheduler<Value> implements Disposable {
7
7
  private saveTimeout?;
8
8
  private isSaving;
9
9
  private delayMs;
@@ -13,6 +13,6 @@ export default class SaveScheduler<Value> {
13
13
  schedule(saveTask: SaveTask<Value>): Promise<Value>;
14
14
  private scheduleSave;
15
15
  private executeSave;
16
- destroy(): void;
16
+ [Symbol.dispose](): void;
17
17
  }
18
18
  export {};
@@ -1,6 +1,6 @@
1
- import type { ExtractedMessage } from '../types.js';
2
- type StrictExtractedMessage = ExtractedMessage & {
3
- references: NonNullable<ExtractedMessage['references']>;
1
+ import type { ExtractorMessage } from '../types.js';
2
+ type StrictExtractedMessage = ExtractorMessage & {
3
+ references: NonNullable<ExtractorMessage['references']>;
4
4
  };
5
5
  export default class MessageExtractor {
6
6
  private isDevelopment;
@@ -8,11 +8,11 @@ export default class MessageExtractor {
8
8
  private sourceMap;
9
9
  private compileCache;
10
10
  constructor(opts: {
11
- isDevelopment: boolean;
12
- projectRoot: string;
11
+ isDevelopment?: boolean;
12
+ projectRoot?: string;
13
13
  sourceMap?: boolean;
14
14
  });
15
- processFileContent(absoluteFilePath: string, source: string): Promise<{
15
+ extract(absoluteFilePath: string, source: string): Promise<{
16
16
  messages: Array<StrictExtractedMessage>;
17
17
  code: string;
18
18
  map?: string;
@@ -0,0 +1,33 @@
1
+ import type { ExtractorMessage, Locale } from '../types.js';
2
+ type ExtractorCodecContext = {
3
+ locale: Locale;
4
+ };
5
+ export default interface ExtractorCodec {
6
+ /**
7
+ * Decode the content of a file into a list of extracted messages. This is used
8
+ * to load existing messages from disk.
9
+ */
10
+ decode(content: string, context: ExtractorCodecContext): Array<ExtractorMessage>;
11
+ /**
12
+ * Encode a list of extracted messages into a string that can be written as
13
+ * file content to the disk.
14
+ */
15
+ encode(messages: Array<ExtractorMessage>, context: ExtractorCodecContext & {
16
+ sourceMessagesById: Map</* ID */ string, ExtractorMessage>;
17
+ }): string;
18
+ /**
19
+ * Turns the content of a file into a JSON string that represents extracted
20
+ * messages. The returned value will be passed to `JSON.parse`.
21
+ *
22
+ * @return E.g. `[{"id":"+YJVTi","message":"Hey!"}]`
23
+ *
24
+ * This is used when loading messages into your application, typically via a
25
+ * dynamic import (e.g. `import(`../messages/${locale}.json`)`).
26
+ *
27
+ * If your file content is JSON and should be used as-is, you can set this to
28
+ * an identity function.
29
+ */
30
+ toJSONString(content: string, context: ExtractorCodecContext): string;
31
+ }
32
+ export declare function defineCodec(factory: () => ExtractorCodec): () => ExtractorCodec;
33
+ export {};
@@ -0,0 +1,2 @@
1
+ declare const _default: () => import("../ExtractorCodec.js").default;
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: () => import("../ExtractorCodec.js").default;
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: () => import("../../ExtractorCodec.js").default;
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: () => import("../../ExtractorCodec.js").default;
2
+ export default _default;
@@ -0,0 +1,15 @@
1
+ import type ExtractorCodec from './ExtractorCodec.js';
2
+ import type { MessagesFormat } from './types.js';
3
+ declare const formats: {
4
+ json: {
5
+ codec: () => Promise<typeof import("./codecs/JSONCodec.js")>;
6
+ extension: ".json";
7
+ };
8
+ po: {
9
+ codec: () => Promise<typeof import("./codecs/POCodec.js")>;
10
+ extension: ".po";
11
+ };
12
+ };
13
+ export default formats;
14
+ export declare function getFormatExtension(format: MessagesFormat): string;
15
+ export declare function resolveCodec(format: MessagesFormat, projectRoot: string): Promise<ExtractorCodec>;
@@ -0,0 +1,8 @@
1
+ import type formats from './index.js';
2
+ export type BuiltInMessagesFormat = keyof typeof formats;
3
+ type CustomMessagesFormat = {
4
+ codec: string;
5
+ extension: `.${string}`;
6
+ };
7
+ export type MessagesFormat = BuiltInMessagesFormat | CustomMessagesFormat;
8
+ export {};
@@ -1 +1,2 @@
1
1
  export { default as unstable_extractMessages } from './extractMessages.js';
2
+ export { defineCodec } from './format/ExtractorCodec.js';
@@ -1,8 +1,8 @@
1
1
  export default class SourceFileFilter {
2
2
  static readonly EXTENSIONS: string[];
3
- private static readonly IGNORED_DIRECTORIES;
3
+ static readonly IGNORED_DIRECTORIES: string[];
4
4
  static isSourceFile(filePath: string): boolean;
5
5
  static shouldEnterDirectory(dirPath: string, srcPaths: Array<string>): boolean;
6
6
  private static isIgnoredDirectoryExplicitlyIncluded;
7
- private static isWithinPath;
7
+ static isWithinPath(targetPath: string, basePath: string): boolean;
8
8
  }
@@ -1,4 +1,4 @@
1
1
  export default class SourceFileScanner {
2
2
  private static walkSourceFiles;
3
- static getSourceFiles(srcPaths: Array<string>): Promise<Array<string>>;
3
+ static getSourceFiles(srcPaths: Array<string>): Promise<Set<string>>;
4
4
  }
@@ -0,0 +1,15 @@
1
+ import { type Event } from '@parcel/watcher';
2
+ type OnChange = (events: Array<Event>) => Promise<void>;
3
+ export type SourceFileWatcherEvent = Event;
4
+ export default class SourceFileWatcher implements Disposable {
5
+ private subscriptions;
6
+ private roots;
7
+ private onChange;
8
+ constructor(roots: Array<string>, onChange: OnChange);
9
+ start(): Promise<void>;
10
+ private normalizeEvents;
11
+ expandDirectoryDeleteEvents(events: Array<Event>, prevKnownFiles: Array<string>): Promise<Array<Event>>;
12
+ stop(): Promise<void>;
13
+ [Symbol.dispose](): void;
14
+ }
15
+ export {};
@@ -1,6 +1,6 @@
1
- export type MessagesFormat = 'json' | 'po';
1
+ import type { MessagesFormat } from './format/types.js';
2
2
  export type Locale = string;
3
- export type ExtractedMessage = {
3
+ export type ExtractorMessage = {
4
4
  id: string;
5
5
  message: string;
6
6
  description?: string;
@@ -1,2 +1,5 @@
1
+ import type { ExtractorMessage } from './types.js';
1
2
  export declare function setNestedProperty(obj: Record<string, any>, keyPath: string, value: any): void;
3
+ export declare function getSortedMessages(messages: Array<ExtractorMessage>): Array<ExtractorMessage>;
2
4
  export declare function localeCompare(a: string, b: string): number;
5
+ export declare function getDefaultProjectRoot(): string;
@@ -0,0 +1,2 @@
1
+ import type { PluginConfig } from '../types.js';
2
+ export default function initExtractionCompiler(pluginConfig: PluginConfig): void;
@@ -1,5 +1,5 @@
1
1
  import type { LoaderContext } from 'webpack';
2
- import type { MessagesFormat } from '../extractor/types.js';
2
+ import type { MessagesFormat } from '../extractor/format/types.js';
3
3
  export type PluginConfig = {
4
4
  requestConfig?: string;
5
5
  experimental?: {
@@ -1,2 +1,8 @@
1
1
  export declare function throwError(message: string): never;
2
2
  export declare function warn(message: string): void;
3
+ /**
4
+ * Returns a function that runs the provided callback only once per process.
5
+ * Next.js can call the config multiple times - this ensures we only run once.
6
+ * Uses an environment variable to track execution across config loads.
7
+ */
8
+ export declare function once(namespace: string): (fn: () => void) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-intl",
3
- "version": "4.5.8",
3
+ "version": "4.6.1",
4
4
  "sideEffects": false,
5
5
  "author": "Jan Amann <jan@amann.work>",
6
6
  "funding": [
@@ -125,11 +125,12 @@
125
125
  ],
126
126
  "dependencies": {
127
127
  "@formatjs/intl-localematcher": "^0.5.4",
128
+ "@parcel/watcher": "^2.4.1",
128
129
  "@swc/core": "^1.15.2",
129
130
  "negotiator": "^1.0.0",
130
- "next-intl-swc-plugin-extractor": "^4.5.8",
131
- "po-parser": "^1.0.2",
132
- "use-intl": "^4.5.8"
131
+ "next-intl-swc-plugin-extractor": "^4.6.1",
132
+ "po-parser": "^2.0.0",
133
+ "use-intl": "^4.6.1"
133
134
  },
134
135
  "peerDependencies": {
135
136
  "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
@@ -141,5 +142,5 @@
141
142
  "optional": true
142
143
  }
143
144
  },
144
- "gitHead": "a0e97c1c84f7bb787ec3397fcd79a6dfe9a9bbd1"
145
+ "gitHead": "dd3a0472fd142439527f7c8b80e9af0135af211b"
145
146
  }
@@ -1,3 +0,0 @@
1
- class Formatter {}
2
-
3
- export { Formatter as default };
@@ -1,42 +0,0 @@
1
- import { setNestedProperty } from '../utils.js';
2
- import Formatter from './Formatter.js';
3
- import { getSortedMessages } from './utils.js';
4
-
5
- class JSONFormatter extends Formatter {
6
- static NAMESPACE_SEPARATOR = '.';
7
- EXTENSION = '.json';
8
- parse(source) {
9
- const json = JSON.parse(source);
10
- const messages = [];
11
- this.traverseMessages(json, (message, id) => {
12
- messages.push({
13
- id,
14
- message
15
- });
16
- });
17
- return messages;
18
- }
19
- serialize(messages) {
20
- const root = {};
21
- for (const message of getSortedMessages(messages)) {
22
- setNestedProperty(root, message.id, message.message);
23
- }
24
- return JSON.stringify(root, null, 2) + '\n';
25
- }
26
- toJSONString(source) {
27
- return source;
28
- }
29
- traverseMessages(obj, callback, path = '') {
30
- for (const key of Object.keys(obj)) {
31
- const newPath = path ? path + JSONFormatter.NAMESPACE_SEPARATOR + key : key;
32
- const value = obj[key];
33
- if (typeof value === 'string') {
34
- callback(value, newPath);
35
- } else if (typeof value === 'object') {
36
- this.traverseMessages(value, callback, newPath);
37
- }
38
- }
39
- }
40
- }
41
-
42
- export { JSONFormatter as default };
@@ -1,51 +0,0 @@
1
- import POParser from 'po-parser';
2
- import { setNestedProperty } from '../utils.js';
3
- import Formatter from './Formatter.js';
4
- import { getSortedMessages } from './utils.js';
5
-
6
- class POFormatter extends Formatter {
7
- // See also https://www.gnu.org/software/gettext/manual/html_node/Header-Entry.html
8
- static DEFAULT_METADATA = {
9
- // Recommended by spec
10
- 'Content-Type': 'text/plain; charset=utf-8',
11
- 'Content-Transfer-Encoding': '8bit',
12
- // Otherwise other tools might set this
13
- 'X-Generator': 'next-intl',
14
- // Crowdin defaults to using msgid as source key
15
- 'X-Crowdin-SourceKey': 'msgstr'
16
- };
17
- EXTENSION = '.po';
18
-
19
- // Metadata is stored so it can be retained when writing
20
- metadataByLocale = (() => new Map())();
21
- parse(content, context) {
22
- const catalog = POParser.parse(content);
23
-
24
- // Store metadata for this locale
25
- if (catalog.meta) {
26
- this.metadataByLocale.set(context.locale, catalog.meta);
27
- }
28
- return catalog.messages || [];
29
- }
30
- serialize(messages, context) {
31
- const meta = {
32
- Language: context.locale,
33
- ...POFormatter.DEFAULT_METADATA,
34
- ...this.metadataByLocale.get(context.locale)
35
- };
36
- return POParser.serialize({
37
- meta,
38
- messages: getSortedMessages(messages)
39
- });
40
- }
41
- toJSONString(source, context) {
42
- const parsed = this.parse(source, context);
43
- const messagesObject = {};
44
- for (const message of parsed) {
45
- setNestedProperty(messagesObject, message.id, message.message);
46
- }
47
- return JSON.stringify(messagesObject, null, 2);
48
- }
49
- }
50
-
51
- export { POFormatter as default };
@@ -1,6 +0,0 @@
1
- const formatters = {
2
- json: () => import('./JSONFormatter.js'),
3
- po: () => import('./POFormatter.js')
4
- };
5
-
6
- export { formatters as default };
@@ -1,15 +0,0 @@
1
- import { localeCompare } from '../utils.js';
2
-
3
- function getSortedMessages(messages) {
4
- return messages.toSorted((messageA, messageB) => {
5
- const pathA = messageA.references?.[0]?.path ?? '';
6
- const pathB = messageB.references?.[0]?.path ?? '';
7
- if (pathA === pathB) {
8
- return localeCompare(messageA.id, messageB.id);
9
- } else {
10
- return localeCompare(pathA, pathB);
11
- }
12
- });
13
- }
14
-
15
- export { getSortedMessages };
@@ -1 +0,0 @@
1
- class a{}export{a as default};
@@ -1 +0,0 @@
1
- import{setNestedProperty as s}from"../utils.js";import e from"./Formatter.js";import{getSortedMessages as t}from"./utils.js";class r extends e{static NAMESPACE_SEPARATOR=".";EXTENSION=".json";parse(s){const e=JSON.parse(s),t=[];return this.traverseMessages(e,((s,e)=>{t.push({id:e,message:s})})),t}serialize(e){const r={};for(const o of t(e))s(r,o.id,o.message);return JSON.stringify(r,null,2)+"\n"}toJSONString(s){return s}traverseMessages(s,e,t=""){for(const o of Object.keys(s)){const a=t?t+r.NAMESPACE_SEPARATOR+o:o,i=s[o];"string"==typeof i?e(i,a):"object"==typeof i&&this.traverseMessages(i,e,a)}}}export{r as default};
@@ -1 +0,0 @@
1
- import t from"po-parser";import{setNestedProperty as e}from"../utils.js";import a from"./Formatter.js";import{getSortedMessages as s}from"./utils.js";class r extends a{static DEFAULT_METADATA={"Content-Type":"text/plain; charset=utf-8","Content-Transfer-Encoding":"8bit","X-Generator":"next-intl","X-Crowdin-SourceKey":"msgstr"};EXTENSION=".po";metadataByLocale=(()=>new Map)();parse(e,a){const s=t.parse(e);return s.meta&&this.metadataByLocale.set(a.locale,s.meta),s.messages||[]}serialize(e,a){const o={Language:a.locale,...r.DEFAULT_METADATA,...this.metadataByLocale.get(a.locale)};return t.serialize({meta:o,messages:s(e)})}toJSONString(t,a){const s=this.parse(t,a),r={};for(const t of s)e(r,t.id,t.message);return JSON.stringify(r,null,2)}}export{r as default};
@@ -1 +0,0 @@
1
- const t={json:()=>import("./JSONFormatter.js"),po:()=>import("./POFormatter.js")};export{t as default};
@@ -1 +0,0 @@
1
- import{localeCompare as e}from"../utils.js";function r(r){return r.toSorted(((r,t)=>{const n=r.references?.[0]?.path??"",o=t.references?.[0]?.path??"";return n===o?e(r.id,t.id):e(n,o)}))}export{r as getSortedMessages};
@@ -1,12 +0,0 @@
1
- type VarInfo = {
2
- kind: string;
3
- namespace?: string;
4
- };
5
- export default class ASTScope {
6
- parent?: ASTScope;
7
- vars: Map<string, VarInfo>;
8
- constructor(parent?: ASTScope);
9
- define(name: string, kind: string, namespace?: string): void;
10
- lookup(name: string): VarInfo | undefined;
11
- }
12
- export {};
@@ -1,10 +0,0 @@
1
- import type { ExtractedMessage, Locale } from '../types.js';
2
- export type FormatterContext = {
3
- locale: Locale;
4
- };
5
- export default abstract class Formatter {
6
- abstract readonly EXTENSION: `.${string}`;
7
- abstract parse(content: string, context: FormatterContext): Array<ExtractedMessage>;
8
- abstract serialize(messages: Array<ExtractedMessage>, context: FormatterContext): string;
9
- abstract toJSONString(source: string, context: FormatterContext): string;
10
- }
@@ -1,10 +0,0 @@
1
- import type { ExtractedMessage } from '../types.js';
2
- import Formatter from './Formatter.js';
3
- export default class JSONFormatter extends Formatter {
4
- static readonly NAMESPACE_SEPARATOR = ".";
5
- readonly EXTENSION = ".json";
6
- parse(source: string): Array<ExtractedMessage>;
7
- serialize(messages: Array<ExtractedMessage>): string;
8
- toJSONString(source: string): string;
9
- private traverseMessages;
10
- }
@@ -1,10 +0,0 @@
1
- import type { ExtractedMessage } from '../types.js';
2
- import Formatter, { type FormatterContext } from './Formatter.js';
3
- export default class POFormatter extends Formatter {
4
- private static readonly DEFAULT_METADATA;
5
- readonly EXTENSION = ".po";
6
- private metadataByLocale;
7
- parse(content: string, context: FormatterContext): Array<ExtractedMessage>;
8
- serialize(messages: Array<ExtractedMessage>, context: FormatterContext): string;
9
- toJSONString(source: string, context: FormatterContext): string;
10
- }
@@ -1,5 +0,0 @@
1
- declare const formatters: {
2
- json: () => Promise<typeof import("./JSONFormatter.js")>;
3
- po: () => Promise<typeof import("./POFormatter.js")>;
4
- };
5
- export default formatters;
@@ -1,2 +0,0 @@
1
- import type { ExtractedMessage } from '../types.js';
2
- export declare function getSortedMessages(messages: Array<ExtractedMessage>): Array<ExtractedMessage>;