next-intl 4.5.3 → 4.5.4

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 (25) hide show
  1. package/dist/esm/development/extractor/ExtractionCompiler.js +1 -1
  2. package/dist/esm/development/extractor/catalog/CatalogManager.js +2 -1
  3. package/dist/esm/development/extractor/extractionLoader.js +3 -2
  4. package/dist/esm/development/extractor/extractor/MessageExtractor.js +35 -373
  5. package/dist/esm/development/extractor/formatters/POFormatter.js +1 -1
  6. package/dist/esm/production/extractor/ExtractionCompiler.js +1 -1
  7. package/dist/esm/production/extractor/catalog/CatalogManager.js +1 -1
  8. package/dist/esm/production/extractor/extractionLoader.js +1 -1
  9. package/dist/esm/production/extractor/extractor/MessageExtractor.js +1 -1
  10. package/dist/esm/production/extractor/formatters/POFormatter.js +1 -1
  11. package/dist/types/extractor/ExtractionCompiler.d.ts +7 -1
  12. package/dist/types/extractor/catalog/CatalogManager.d.ts +3 -1
  13. package/dist/types/extractor/extractor/MessageExtractor.bench.d.ts +1 -0
  14. package/dist/types/extractor/extractor/MessageExtractor.d.ts +4 -3
  15. package/dist/types/navigation/react-client/createNavigation.d.ts +6 -6
  16. package/dist/types/navigation/react-server/createNavigation.d.ts +6 -6
  17. package/dist/types/navigation/shared/createSharedNavigationFns.d.ts +6 -6
  18. package/package.json +6 -4
  19. package/dist/esm/development/extractor/extractor/ASTScope.js +0 -18
  20. package/dist/esm/development/extractor/extractor/KeyGenerator.js +0 -11
  21. package/dist/esm/development/extractor/utils/POParser.js +0 -222
  22. package/dist/esm/production/extractor/extractor/ASTScope.js +0 -1
  23. package/dist/esm/production/extractor/extractor/KeyGenerator.js +0 -1
  24. package/dist/esm/production/extractor/utils/POParser.js +0 -1
  25. package/dist/types/extractor/utils/POParser.d.ts +0 -24
@@ -22,7 +22,7 @@ class ExtractionCompiler {
22
22
  // don't need to await the persistence
23
23
  void this.manager.save();
24
24
  }
25
- return result.source;
25
+ return result;
26
26
  }
27
27
  async performInitialScan() {
28
28
  // We can't rely on all files being compiled (e.g. due to persistent
@@ -31,7 +31,8 @@ class CatalogManager {
31
31
  this.isDevelopment = opts.isDevelopment ?? false;
32
32
  this.messageExtractor = new MessageExtractor({
33
33
  isDevelopment: this.isDevelopment,
34
- projectRoot: this.projectRoot
34
+ projectRoot: this.projectRoot,
35
+ sourceMap: opts.sourceMap
35
36
  });
36
37
  }
37
38
  async getFormatter() {
@@ -11,11 +11,12 @@ function extractionLoader(source) {
11
11
  if (!compiler) {
12
12
  compiler = new ExtractionCompiler(options, {
13
13
  // Avoid rollup's `replace` plugin to compile this away
14
- isDevelopment: process.env['NODE_ENV'.trim()] === 'development'
14
+ isDevelopment: process.env['NODE_ENV'.trim()] === 'development',
15
+ sourceMap: this.sourceMap
15
16
  });
16
17
  }
17
18
  compiler.compile(this.resourcePath, source).then(result => {
18
- callback(null, result);
19
+ callback(null, result.code, result.map);
19
20
  }).catch(callback);
20
21
  }
21
22
 
@@ -1,16 +1,15 @@
1
+ import { createRequire } from 'module';
1
2
  import path from 'path';
2
- import { parse, print } from '@swc/core';
3
- import { warn } from '../../plugin/utils.js';
4
- import ASTScope from './ASTScope.js';
5
- import KeyGenerator from './KeyGenerator.js';
3
+ import { transform } from '@swc/core';
6
4
  import LRUCache from './LRUCache.js';
7
5
 
6
+ const require = createRequire(import.meta.url);
8
7
  class MessageExtractor {
9
- static NAMESPACE_SEPARATOR = '.';
10
8
  compileCache = (() => new LRUCache(750))();
11
9
  constructor(opts) {
12
10
  this.isDevelopment = opts.isDevelopment;
13
11
  this.projectRoot = opts.projectRoot;
12
+ this.sourceMap = opts.sourceMap ?? false;
14
13
  }
15
14
  async processFileContent(absoluteFilePath, source) {
16
15
  const cacheKey = source;
@@ -23,379 +22,42 @@ class MessageExtractor {
23
22
  if (!source.includes('useExtracted') && !source.includes('getExtracted')) {
24
23
  return {
25
24
  messages: [],
26
- source
25
+ code: source
27
26
  };
28
27
  }
29
- const ast = await parse(source, {
30
- syntax: 'typescript',
31
- tsx: true,
32
- target: 'es2022',
33
- decorators: true
34
- });
35
- const relativeFilePath = path.relative(this.projectRoot, absoluteFilePath);
36
- const processResult = await this.processAST(ast, relativeFilePath);
37
- const finalResult = processResult.source ? processResult : {
38
- ...processResult,
39
- source
40
- };
41
- this.compileCache.set(cacheKey, finalResult);
42
- return finalResult;
43
- }
44
- async processAST(ast, filePath) {
45
- const results = [];
46
- let hookLocalName = null;
47
- let hookType = null;
48
- const isDevelopment = this.isDevelopment;
49
- const scopeStack = [new ASTScope()];
50
- function currentScope() {
51
- return scopeStack[scopeStack.length - 1];
52
- }
53
- function createUndefinedArgument() {
54
- return {
55
- expression: {
56
- type: 'Identifier',
57
- value: 'undefined',
58
- optional: false,
59
- ctxt: 1,
60
- span: {
61
- start: 0,
62
- end: 0,
63
- ctxt: 0
64
- }
65
- }
66
- };
67
- }
68
- function extractStaticString(value) {
69
- if (value.type === 'StringLiteral') {
70
- return value.value;
71
- } else if (value.type === 'TemplateLiteral') {
72
- const templateLiteral = value;
73
- // Only handle simple template literals without expressions
74
- if (templateLiteral.expressions.length === 0 && templateLiteral.quasis.length === 1) {
75
- return templateLiteral.quasis[0].cooked || templateLiteral.quasis[0].raw;
28
+ const filePath = path.relative(this.projectRoot, absoluteFilePath);
29
+ const result = await transform(source, {
30
+ jsc: {
31
+ target: 'esnext',
32
+ parser: {
33
+ syntax: 'typescript',
34
+ tsx: true,
35
+ decorators: true
36
+ },
37
+ experimental: {
38
+ disableBuiltinTransformsForInternalTesting: true,
39
+ disableAllLints: true,
40
+ plugins: [[require.resolve('next-intl-swc-plugin-extractor'), {
41
+ isDevelopment: this.isDevelopment,
42
+ filePath
43
+ }]]
76
44
  }
77
- }
78
- return null;
79
- }
80
- function visit(node) {
81
- if (typeof node !== 'object') return;
82
- switch (node.type) {
83
- case 'ImportDeclaration':
84
- {
85
- const decl = node;
86
- if (decl.source.value === 'next-intl') {
87
- for (const spec of decl.specifiers) {
88
- if (spec.type === 'ImportSpecifier') {
89
- const importedName = spec.imported?.value;
90
- const localName = spec.local.value;
91
- if (importedName === 'useExtracted' || localName === 'useExtracted') {
92
- hookLocalName = localName;
93
- hookType = 'useTranslations';
94
-
95
- // Transform import to useTranslations
96
- spec.imported = undefined;
97
- spec.local.value = 'useTranslations';
98
- }
99
- }
100
- }
101
- } else if (decl.source.value === 'next-intl/server') {
102
- for (const spec of decl.specifiers) {
103
- if (spec.type === 'ImportSpecifier') {
104
- const importedName = spec.imported?.value;
105
- const localName = spec.local.value;
106
- if (importedName === 'getExtracted' || localName === 'getExtracted') {
107
- hookLocalName = localName;
108
- hookType = 'getTranslations';
109
-
110
- // Transform import to getTranslations
111
- spec.imported = undefined;
112
- spec.local.value = 'getTranslations';
113
- }
114
- }
115
- }
116
- }
117
- break;
118
- }
119
- case 'VariableDeclarator':
120
- {
121
- const decl = node;
122
- let callExpr = null;
123
-
124
- // Handle direct CallExpression: const t = useExtracted();
125
- if (decl.init?.type === 'CallExpression' && decl.init.callee.type === 'Identifier' && decl.init.callee.value === hookLocalName) {
126
- callExpr = decl.init;
127
- }
128
- // Handle AwaitExpression: const t = await getExtracted();
129
- else if (decl.init?.type === 'AwaitExpression' && decl.init.argument.type === 'CallExpression' && decl.init.argument.callee.type === 'Identifier' && decl.init.argument.callee.value === hookLocalName) {
130
- callExpr = decl.init.argument;
131
- }
132
- if (callExpr && decl.id.type === 'Identifier') {
133
- // Extract namespace from first argument if present
134
- let namespace;
135
- if (callExpr.arguments.length > 0) {
136
- const firstArg = callExpr.arguments[0].expression;
137
- if (firstArg.type === 'StringLiteral') {
138
- namespace = firstArg.value;
139
- } else if (firstArg.type === 'ObjectExpression') {
140
- const objectExpression = firstArg;
141
- for (const prop of objectExpression.properties) {
142
- if (prop.type === 'KeyValueProperty') {
143
- const key = prop.key;
144
- if (key.type === 'Identifier' && key.value === 'namespace') {
145
- const staticNamespace = extractStaticString(prop.value);
146
- if (staticNamespace != null) {
147
- namespace = staticNamespace;
148
- }
149
- break;
150
- }
151
- }
152
- }
153
- }
154
- }
155
- currentScope().define(decl.id.value, 'translator', namespace);
156
-
157
- // Transform the call based on the hook type
158
- if (hookType) {
159
- callExpr.callee.value = hookType;
160
- }
161
- }
162
- break;
163
- }
164
- case 'CallExpression':
165
- {
166
- const call = node;
167
- let isTranslatorCall = false;
168
- let namespace;
169
-
170
- // Handle Identifier case: t("message")
171
- if (call.callee.type === 'Identifier') {
172
- const name = call.callee.value;
173
- const resolved = currentScope().lookup(name);
174
- isTranslatorCall = resolved?.kind === 'translator';
175
- namespace = resolved?.namespace;
176
- }
177
-
178
- // Handle MemberExpression case: t.rich, t.markup, or t.has
179
- else if (call.callee.type === 'MemberExpression') {
180
- const memberExpr = call.callee;
181
- if (memberExpr.object.type === 'Identifier' && memberExpr.property.type === 'Identifier') {
182
- const objectName = memberExpr.object.value;
183
- const propertyName = memberExpr.property.value;
184
- const resolved = currentScope().lookup(objectName);
185
- isTranslatorCall = resolved?.kind === 'translator' && (propertyName === 'rich' || propertyName === 'markup' || propertyName === 'has');
186
- namespace = resolved?.namespace;
187
- }
188
- }
189
- if (isTranslatorCall) {
190
- const arg0 = call.arguments[0]?.expression;
191
- let messageText = null;
192
- let explicitId = null;
193
- let description = null;
194
- let valuesNode = null;
195
- let formatsNode = null;
196
- function warnDynamicExpression(expressionNode) {
197
- const hasSpan = 'span' in expressionNode && expressionNode.span && typeof expressionNode.span === 'object' && 'start' in expressionNode.span;
198
- const location = hasSpan ? path.basename(filePath) : undefined;
199
- warn((location ? `${location}: ` : '') + 'Cannot extract message from dynamic expression, messages need to be statically analyzable. If you need to provide runtime values, pass them as a separate argument.');
200
- }
201
-
202
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
203
- if (arg0) {
204
- // Handle object syntax: t({id: 'key', message: 'text'})
205
- if (arg0.type === 'ObjectExpression') {
206
- const objectExpression = arg0;
207
-
208
- // Look for id, message, values, and formats properties
209
- for (const prop of objectExpression.properties) {
210
- if (prop.type === 'KeyValueProperty') {
211
- const key = prop.key;
212
- if (key.type === 'Identifier' && key.value === 'id') {
213
- const staticId = extractStaticString(prop.value);
214
- if (staticId !== null) {
215
- explicitId = staticId;
216
- }
217
- } else if (key.type === 'Identifier' && key.value === 'message') {
218
- const staticMessage = extractStaticString(prop.value);
219
- if (staticMessage != null) {
220
- messageText = staticMessage;
221
- } else {
222
- warnDynamicExpression(prop.value);
223
- }
224
- } else if (key.type === 'Identifier' && key.value === 'description') {
225
- const staticDescription = extractStaticString(prop.value);
226
- if (staticDescription != null) {
227
- description = staticDescription;
228
- } else {
229
- warnDynamicExpression(prop.value);
230
- }
231
- } else if (key.type === 'Identifier' && key.value === 'values') {
232
- valuesNode = prop.value;
233
- } else if (key.type === 'Identifier' && key.value === 'formats') {
234
- formatsNode = prop.value;
235
- }
236
- }
237
- }
238
- }
239
-
240
- // Handle string syntax: t('text') or t(`text`)
241
- else {
242
- const staticString = extractStaticString(arg0);
243
- if (staticString != null) {
244
- messageText = staticString;
245
- } else {
246
- // Dynamic expression (Identifier, CallExpression, BinaryExpression, etc.)
247
- warnDynamicExpression(arg0);
248
- }
249
- }
250
- }
251
- if (messageText) {
252
- const callKey = explicitId || KeyGenerator.generate(messageText);
253
- const fullKey = namespace ? [namespace, callKey].join(MessageExtractor.NAMESPACE_SEPARATOR) : callKey;
254
- const message = {
255
- id: fullKey,
256
- message: messageText,
257
- references: [{
258
- path: filePath
259
- }]
260
- };
261
- if (description) {
262
- message.description = description;
263
- }
264
- results.push(message);
265
-
266
- // Transform the argument based on type
267
- if (arg0.type === 'StringLiteral') {
268
- arg0.value = callKey;
269
- arg0.raw = undefined;
270
- } else if (arg0.type === 'TemplateLiteral') {
271
- // Replace template literal with string literal
272
- Object.assign(arg0, {
273
- type: 'StringLiteral',
274
- value: callKey,
275
- raw: undefined
276
- });
277
- } else if (arg0.type === 'ObjectExpression') {
278
- // Transform object expression to individual parameters
279
- // Replace the object with the key as first argument
280
- Object.assign(arg0, {
281
- type: 'StringLiteral',
282
- value: callKey,
283
- raw: undefined
284
- });
285
-
286
- // Add values as second argument if present
287
- if (valuesNode) {
288
- if (call.arguments.length < 2) {
289
- call.arguments.push({
290
- // @ts-expect-error -- Node type compatible with Expression
291
- expression: valuesNode
292
- });
293
- } else {
294
- // @ts-expect-error -- Node type compatible with Expression
295
- call.arguments[1].expression = valuesNode;
296
- }
297
- }
298
-
299
- // Add formats as third argument if present
300
- if (formatsNode) {
301
- // Ensure we have a second argument (values or undefined)
302
- while (call.arguments.length < 2) {
303
- call.arguments.push(createUndefinedArgument());
304
- }
305
- if (call.arguments.length < 3) {
306
- // Append argument
307
- call.arguments.push({
308
- // @ts-expect-error -- Node type compatible with Expression
309
- expression: formatsNode
310
- });
311
- } else {
312
- // Replace argument
313
- // @ts-expect-error -- Node type compatible with Expression
314
- call.arguments[2].expression = formatsNode;
315
- }
316
- }
317
- }
318
-
319
- // Check if this is a t.has call (which doesn't need fallback)
320
- const isHasCall = call.callee.type === 'MemberExpression' && call.callee.property.type === 'Identifier' && call.callee.property.value === 'has';
321
-
322
- // Add fallback message as fourth parameter in development mode (except for t.has)
323
- if (isDevelopment && !isHasCall) {
324
- // Ensure we have at least 3 arguments
325
- while (call.arguments.length < 3) {
326
- call.arguments.push(createUndefinedArgument());
327
- }
328
-
329
- // Add fallback message
330
- call.arguments.push({
331
- expression: {
332
- type: 'StringLiteral',
333
- value: messageText,
334
- raw: JSON.stringify(messageText),
335
- // @ts-expect-error -- Seems required
336
- ctxt: 1,
337
- span: {
338
- start: 0,
339
- end: 0,
340
- ctxt: 0
341
- }
342
- }
343
- });
344
- }
345
- }
346
- }
347
- break;
348
- }
349
- case 'FunctionDeclaration':
350
- case 'FunctionExpression':
351
- case 'ArrowFunctionExpression':
352
- case 'BlockStatement':
353
- {
354
- scopeStack.push(new ASTScope(currentScope()));
355
- for (const key of Object.keys(node)) {
356
- const child = node[key];
357
- if (Array.isArray(child)) {
358
- child.forEach(item => {
359
- if (item && typeof item === 'object') {
360
- if ('expression' in item && typeof item.expression === 'object' && 'type' in item.expression) {
361
- visit(item.expression);
362
- } else if ('type' in item) {
363
- visit(item);
364
- }
365
- }
366
- });
367
- } else if (child && typeof child === 'object' && 'type' in child) {
368
- visit(child);
369
- }
370
- }
371
- scopeStack.pop();
372
- return;
373
- }
374
- }
45
+ },
46
+ sourceMaps: this.sourceMap,
47
+ sourceFileName: filePath,
48
+ filename: filePath
49
+ });
375
50
 
376
- // Generic recursion
377
- for (const key of Object.keys(node)) {
378
- const child = node[key];
379
- if (Array.isArray(child)) {
380
- child.forEach(item => {
381
- if (item && typeof item === 'object') {
382
- if ('expression' in item && item.expression && typeof item.expression === 'object' && 'type' in item.expression) {
383
- visit(item.expression);
384
- } else if ('type' in item) {
385
- visit(item);
386
- }
387
- }
388
- });
389
- } else if (child && typeof child === 'object' && 'type' in child) {
390
- visit(child);
391
- }
392
- }
393
- }
394
- visit(ast);
395
- return {
396
- messages: results,
397
- source: (await print(ast)).code
51
+ // TODO: Improve the typing of @swc/core
52
+ const output = result.output;
53
+ const messages = JSON.parse(JSON.parse(output).results);
54
+ const extractionResult = {
55
+ code: result.code,
56
+ map: result.map,
57
+ messages
398
58
  };
59
+ this.compileCache.set(cacheKey, extractionResult);
60
+ return extractionResult;
399
61
  }
400
62
  }
401
63
 
@@ -1,5 +1,5 @@
1
+ import POParser from 'po-parser';
1
2
  import { setNestedProperty } from '../utils/ObjectUtils.js';
2
- import POParser from '../utils/POParser.js';
3
3
  import Formatter from './Formatter.js';
4
4
  import { getSortedMessages } from './utils.js';
5
5
 
@@ -1 +1 @@
1
- import a from"./catalog/CatalogManager.js";class i{isDevelopment=!1;constructor(i,e={}){this.manager=new a(i,e),this.isDevelopment=e.isDevelopment??!1,this.initialScanPromise=this.performInitialScan()}async compile(a,i){this.initialScanPromise&&(await this.initialScanPromise,this.initialScanPromise=void 0);const e=await this.manager.extractFileMessages(a,i);return this.isDevelopment&&e.changed&&this.manager.save(),e.source}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 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 +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 r from"./CatalogLocales.js";import i from"./CatalogPersister.js";import c 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 c(50),this.projectRoot=s.projectRoot||process.cwd(),this.isDevelopment=s.isDevelopment??!1,this.messageExtractor=new t({isDevelopment:this.isDevelopment,projectRoot:this.projectRoot})}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 i(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)))}getFileMessages(e){return this.messagesByFile.get(e)}async loadMessages(){if(await this.loadSourceMessages(),await this.loadTargetMessages(),this.isDevelopment){(await this.getCatalogLocales()).subscribeLocalesChange(this.onLocalesChange)}}async loadSourceMessages(){await this.loadLocaleMessages(this.config.sourceLocale);const s=await o.getSourceFiles(this.getSrcPaths());await Promise.all(s.map((async s=>this.extractFileMessages(s,await e.readFile(s,"utf8")))))}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((async e=>{this.translationsByTargetLocale.set(e,new Map);const s=await this.loadLocaleMessages(e);for(const t of s){this.translationsByTargetLocale.get(e).set(t.id,t.message)}})))}async extractFileMessages(e,t){const a=await this.messageExtractor.processFileContent(e,t),o=this.messagesByFile.get(e),r=Array.from(o?.keys()??[]),i=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)=>e.path.localeCompare(s.path))),e={...e,references:t},s.description&&!e.description&&(e={...e,description:s.description})}this.messagesById.set(e.id,e),i.set(e.id,e);const t=r.indexOf(e.id);-1!==t&&r.splice(t,1)}const c=s.relative(this.projectRoot,e);r.filter((e=>{const s=this.messagesById.get(e);return!s?.references?.some((e=>e.path!==c))})).forEach((e=>{this.messagesById.delete(e)}));a.messages.length>0?this.messagesByFile.set(e,i):this.messagesByFile.delete(e);const n=this.haveMessagesChanged(o,i);return{...a,changed:n}}haveMessagesChanged(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&&this.areReferencesEqual(e.references,s.references)}areReferencesEqual(e,s){if(!e&&!s)return!0;if(!e||!s)return!1;if(e.length!==s.length)return!1;for(let t=0;t<e.length;t++)if(e[t].path!==s[t].path)return!1;return!0}async save(){return this.saveScheduler.schedule((()=>this.saveImpl()))}async saveImpl(){const e=Array.from(this.messagesById.values()),s=await this.getPersister();await s.write(this.config.sourceLocale,e);for(const e of await this.getTargetLocales())await this.saveLocale(e);return e.length}async saveLocale(e){const s=Array.from(this.messagesById.values()),t=await this.getPersister(),a=this.lastWriteByLocale.get(e),o=await t.getLastModified(e);if(o&&a&&o>a){const s=await t.read(e),a=this.translationsByTargetLocale.get(e);for(const e of s)a.set(e.id,e.message)}const r=this.translationsByTargetLocale.get(e),i=s.map((e=>({...e,message:r.get(e.id)||""})));await t.write(e,i);const c=await t.getLastModified(e);this.lastWriteByLocale.set(e,c)}onLocalesChange=async e=>{for(const s of e.added){const e=new Map;this.translationsByTargetLocale.set(s,e);const t=await this.loadLocaleMessages(s);for(const s of t)e.set(s.id,s.message);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 t from"../extractor/MessageExtractor.js";import a from"../formatters/index.js";import o from"../source/SourceFileScanner.js";import r from"./CatalogLocales.js";import i from"./CatalogPersister.js";import c 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 c(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 i(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)))}getFileMessages(e){return this.messagesByFile.get(e)}async loadMessages(){if(await this.loadSourceMessages(),await this.loadTargetMessages(),this.isDevelopment){(await this.getCatalogLocales()).subscribeLocalesChange(this.onLocalesChange)}}async loadSourceMessages(){await this.loadLocaleMessages(this.config.sourceLocale);const s=await o.getSourceFiles(this.getSrcPaths());await Promise.all(s.map((async s=>this.extractFileMessages(s,await e.readFile(s,"utf8")))))}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((async e=>{this.translationsByTargetLocale.set(e,new Map);const s=await this.loadLocaleMessages(e);for(const t of s){this.translationsByTargetLocale.get(e).set(t.id,t.message)}})))}async extractFileMessages(e,t){const a=await this.messageExtractor.processFileContent(e,t),o=this.messagesByFile.get(e),r=Array.from(o?.keys()??[]),i=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)=>e.path.localeCompare(s.path))),e={...e,references:t},s.description&&!e.description&&(e={...e,description:s.description})}this.messagesById.set(e.id,e),i.set(e.id,e);const t=r.indexOf(e.id);-1!==t&&r.splice(t,1)}const c=s.relative(this.projectRoot,e);r.filter((e=>{const s=this.messagesById.get(e);return!s?.references?.some((e=>e.path!==c))})).forEach((e=>{this.messagesById.delete(e)}));a.messages.length>0?this.messagesByFile.set(e,i):this.messagesByFile.delete(e);const n=this.haveMessagesChanged(o,i);return{...a,changed:n}}haveMessagesChanged(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&&this.areReferencesEqual(e.references,s.references)}areReferencesEqual(e,s){if(!e&&!s)return!0;if(!e||!s)return!1;if(e.length!==s.length)return!1;for(let t=0;t<e.length;t++)if(e[t].path!==s[t].path)return!1;return!0}async save(){return this.saveScheduler.schedule((()=>this.saveImpl()))}async saveImpl(){const e=Array.from(this.messagesById.values()),s=await this.getPersister();await s.write(this.config.sourceLocale,e);for(const e of await this.getTargetLocales())await this.saveLocale(e);return e.length}async saveLocale(e){const s=Array.from(this.messagesById.values()),t=await this.getPersister(),a=this.lastWriteByLocale.get(e),o=await t.getLastModified(e);if(o&&a&&o>a){const s=await t.read(e),a=this.translationsByTargetLocale.get(e);for(const e of s)a.set(e.id,e.message)}const r=this.translationsByTargetLocale.get(e),i=s.map((e=>({...e,message:r.get(e.id)||""})));await t.write(e,i);const c=await t.getLastModified(e);this.lastWriteByLocale.set(e,c)}onLocalesChange=async e=>{for(const s of e.added){const e=new Map;this.translationsByTargetLocale.set(s,e);const t=await this.loadLocaleMessages(s);for(const s of t)e.set(s.id,s.message);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 +1 @@
1
- import t from"./ExtractionCompiler.js";let e;function o(o){const n=this.getOptions(),s=this.async();e||(e=new t(n,{isDevelopment:"development"===process.env["NODE_ENV".trim()]})),e.compile(this.resourcePath,o).then((t=>{s(null,t)})).catch(s)}export{o as default};
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 +1 @@
1
- import e from"path";import{parse as t,print as s}from"@swc/core";import{warn as n}from"../../plugin/utils.js";import i from"./ASTScope.js";import r from"./KeyGenerator.js";import a from"./LRUCache.js";class o{static NAMESPACE_SEPARATOR=".";compileCache=(()=>new a(750))();constructor(e){this.isDevelopment=e.isDevelopment,this.projectRoot=e.projectRoot}async processFileContent(s,n){const i=n,r=this.compileCache.get(i);if(r)return r;if(!n.includes("useExtracted")&&!n.includes("getExtracted"))return{messages:[],source:n};const a=await t(n,{syntax:"typescript",tsx:!0,target:"es2022",decorators:!0}),o=e.relative(this.projectRoot,s),l=await this.processAST(a,o),p=l.source?l:{...l,source:n};return this.compileCache.set(i,p),p}async processAST(t,a){const l=[];let p=null,c=null;const u=this.isDevelopment,f=[new i];function y(){return f[f.length-1]}function m(e){if("StringLiteral"===e.type)return e.value;if("TemplateLiteral"===e.type){const t=e;if(0===t.expressions.length&&1===t.quasis.length)return t.quasis[0].cooked||t.quasis[0].raw}return null}return function t(s){if("object"==typeof s){switch(s.type){case"ImportDeclaration":{const d=s;if("next-intl"===d.source.value){for(const v of d.specifiers)if("ImportSpecifier"===v.type){const g=v.imported?.value,x=v.local.value;"useExtracted"!==g&&"useExtracted"!==x||(p=x,c="useTranslations",v.imported=void 0,v.local.value="useTranslations")}}else if("next-intl/server"===d.source.value)for(const h of d.specifiers)if("ImportSpecifier"===h.type){const b=h.imported?.value,j=h.local.value;"getExtracted"!==b&&"getExtracted"!==j||(p=j,c="getTranslations",h.imported=void 0,h.local.value="getTranslations")}break}case"VariableDeclarator":{const E=s;let I=null;if("CallExpression"===E.init?.type&&"Identifier"===E.init.callee.type&&E.init.callee.value===p?I=E.init:"AwaitExpression"===E.init?.type&&"CallExpression"===E.init.argument.type&&"Identifier"===E.init.argument.callee.type&&E.init.argument.callee.value===p&&(I=E.init.argument),I&&"Identifier"===E.id.type){let S;if(I.arguments.length>0){const A=I.arguments[0].expression;if("StringLiteral"===A.type)S=A.value;else if("ObjectExpression"===A.type){const k=A;for(const w of k.properties)if("KeyValueProperty"===w.type){const C=w.key;if("Identifier"===C.type&&"namespace"===C.value){const T=m(w.value);null!=T&&(S=T);break}}}}y().define(E.id.value,"translator",S),c&&(I.callee.value=c)}break}case"CallExpression":{const O=s;let L,R=!1;if("Identifier"===O.callee.type){const D=O.callee.value,P=y().lookup(D);R="translator"===P?.kind,L=P?.namespace}else if("MemberExpression"===O.callee.type){const F=O.callee;if("Identifier"===F.object.type&&"Identifier"===F.property.type){const M=F.object.value,q=F.property.value,K=y().lookup(M);R="translator"===K?.kind&&("rich"===q||"markup"===q||"has"===q),L=K?.namespace}}if(R){const N=O.arguments[0]?.expression;let V=null,_=null,z=null,B=null,G=null;function J(t){const s="span"in t&&t.span&&"object"==typeof t.span&&"start"in t.span?e.basename(a):void 0;n((s?`${s}: `:"")+"Cannot extract message from dynamic expression, messages need to be statically analyzable. If you need to provide runtime values, pass them as a separate argument.")}if(N)if("ObjectExpression"===N.type){const U=N;for(const $ of U.properties)if("KeyValueProperty"===$.type){const H=$.key;if("Identifier"===H.type&&"id"===H.value){const Q=m($.value);null!==Q&&(_=Q)}else if("Identifier"===H.type&&"message"===H.value){const W=m($.value);null!=W?V=W:J($.value)}else if("Identifier"===H.type&&"description"===H.value){const X=m($.value);null!=X?z=X:J($.value)}else"Identifier"===H.type&&"values"===H.value?B=$.value:"Identifier"===H.type&&"formats"===H.value&&(G=$.value)}}else{const Y=m(N);null!=Y?V=Y:J(N)}if(V){const Z=_||r.generate(V),ee={id:L?[L,Z].join(o.NAMESPACE_SEPARATOR):Z,message:V,references:[{path:a}]};if(z&&(ee.description=z),l.push(ee),"StringLiteral"===N.type)N.value=Z,N.raw=void 0;else if("TemplateLiteral"===N.type)Object.assign(N,{type:"StringLiteral",value:Z,raw:void 0});else if("ObjectExpression"===N.type&&(Object.assign(N,{type:"StringLiteral",value:Z,raw:void 0}),B&&(O.arguments.length<2?O.arguments.push({expression:B}):O.arguments[1].expression=B),G)){for(;O.arguments.length<2;)O.arguments.push({expression:{type:"Identifier",value:"undefined",optional:!1,ctxt:1,span:{start:0,end:0,ctxt:0}}});O.arguments.length<3?O.arguments.push({expression:G}):O.arguments[2].expression=G}const te="MemberExpression"===O.callee.type&&"Identifier"===O.callee.property.type&&"has"===O.callee.property.value;if(u&&!te){for(;O.arguments.length<3;)O.arguments.push({expression:{type:"Identifier",value:"undefined",optional:!1,ctxt:1,span:{start:0,end:0,ctxt:0}}});O.arguments.push({expression:{type:"StringLiteral",value:V,raw:JSON.stringify(V),ctxt:1,span:{start:0,end:0,ctxt:0}}})}}}break}case"FunctionDeclaration":case"FunctionExpression":case"ArrowFunctionExpression":case"BlockStatement":f.push(new i(y()));for(const se of Object.keys(s)){const ne=s[se];Array.isArray(ne)?ne.forEach((e=>{e&&"object"==typeof e&&("expression"in e&&"object"==typeof e.expression&&"type"in e.expression?t(e.expression):"type"in e&&t(e))})):ne&&"object"==typeof ne&&"type"in ne&&t(ne)}return void f.pop()}for(const ie of Object.keys(s)){const re=s[ie];Array.isArray(re)?re.forEach((e=>{e&&"object"==typeof e&&("expression"in e&&e.expression&&"object"==typeof e.expression&&"type"in e.expression?t(e.expression):"type"in e&&t(e))})):re&&"object"==typeof re&&"type"in re&&t(re)}}}(t),{messages:l,source:(await s(t)).code}}}export{o as default};
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 i{compileCache=(()=>new o(750))();constructor(e){this.isDevelopment=e.isDevelopment,this.projectRoot=e.projectRoot,this.sourceMap=e.sourceMap??!1}async processFileContent(e,o){const i=o,a=this.compileCache.get(i);if(a)return a;if(!o.includes("useExtracted")&&!o.includes("getExtracted"))return{messages:[],code:o};const c=t.relative(this.projectRoot,e),p=await s(o,{jsc:{target:"esnext",parser:{syntax:"typescript",tsx:!0,decorators:!0},experimental:{disableBuiltinTransformsForInternalTesting:!0,disableAllLints:!0,plugins:[[r.resolve("next-intl-swc-plugin-extractor"),{isDevelopment:this.isDevelopment,filePath:c}]]}},sourceMaps:this.sourceMap,sourceFileName:c,filename:c}),n=p.output,l=JSON.parse(JSON.parse(n).results),m={code:p.code,map:p.map,messages:l};return this.compileCache.set(i,m),m}}export{i as default};
@@ -1 +1 @@
1
- import{setNestedProperty as t}from"../utils/ObjectUtils.js";import e from"../utils/POParser.js";import s from"./Formatter.js";import{getSortedMessages as a}from"./utils.js";class r extends s{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(t,s){const a=e.parse(t);return a.meta&&this.metadataByLocale.set(s.locale,a.meta),a.messages||[]}serialize(t,s){const o={Language:s.locale,...r.DEFAULT_METADATA,...this.metadataByLocale.get(s.locale)};return e.serialize({meta:o,messages:a(t)})}toJSONString(e,s){const a=this.parse(e,s),r={};for(const e of a)t(r,e.id,e.message);return JSON.stringify(r,null,2)}}export{r as default};
1
+ import t from"po-parser";import{setNestedProperty as e}from"../utils/ObjectUtils.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};
@@ -6,8 +6,14 @@ export default class ExtractionCompiler implements Disposable {
6
6
  constructor(config: ExtractorConfig, opts?: {
7
7
  isDevelopment?: boolean;
8
8
  projectRoot?: string;
9
+ sourceMap?: boolean;
9
10
  });
10
- compile(resourcePath: string, source: string): Promise<string>;
11
+ compile(resourcePath: string, source: string): Promise<{
12
+ messages: Array<import("./types.js").ExtractedMessage>;
13
+ code: string;
14
+ changed: boolean;
15
+ map?: string;
16
+ }>;
11
17
  private performInitialScan;
12
18
  extract(): Promise<void>;
13
19
  [Symbol.dispose](): void;
@@ -19,6 +19,7 @@ export default class CatalogManager {
19
19
  constructor(config: ExtractorConfig, opts?: {
20
20
  projectRoot?: string;
21
21
  isDevelopment?: boolean;
22
+ sourceMap?: boolean;
22
23
  });
23
24
  private getFormatter;
24
25
  private getPersister;
@@ -32,8 +33,9 @@ export default class CatalogManager {
32
33
  private loadTargetMessages;
33
34
  extractFileMessages(absoluteFilePath: string, source: string): Promise<{
34
35
  messages: Array<ExtractedMessage>;
35
- source: string;
36
+ code: string;
36
37
  changed: boolean;
38
+ map?: string;
37
39
  }>;
38
40
  private haveMessagesChanged;
39
41
  private areMessagesEqual;
@@ -3,18 +3,19 @@ type StrictExtractedMessage = ExtractedMessage & {
3
3
  references: NonNullable<ExtractedMessage['references']>;
4
4
  };
5
5
  export default class MessageExtractor {
6
- private static readonly NAMESPACE_SEPARATOR;
7
6
  private isDevelopment;
8
7
  private projectRoot;
8
+ private sourceMap;
9
9
  private compileCache;
10
10
  constructor(opts: {
11
11
  isDevelopment: boolean;
12
12
  projectRoot: string;
13
+ sourceMap?: boolean;
13
14
  });
14
15
  processFileContent(absoluteFilePath: string, source: string): Promise<{
15
16
  messages: Array<StrictExtractedMessage>;
16
- source: string;
17
+ code: string;
18
+ map?: string;
17
19
  }>;
18
- private processAST;
19
20
  }
20
21
  export {};
@@ -4,19 +4,17 @@ import type { DomainsConfig, LocalePrefixMode, Locales, Pathnames } from '../../
4
4
  export default function createNavigation<const AppLocales extends Locales, const AppLocalePrefixMode extends LocalePrefixMode = 'always', const AppPathnames extends Pathnames<AppLocales> = never, const AppDomains extends DomainsConfig<AppLocales> = never>(routing?: [AppPathnames] extends [never] ? RoutingConfigSharedNavigation<AppLocales, AppLocalePrefixMode, AppDomains> | undefined : RoutingConfigLocalizedNavigation<AppLocales, AppLocalePrefixMode, AppPathnames, AppDomains>): {
5
5
  Link: import("react").ForwardRefExoticComponent<Omit<{
6
6
  target?: import("react").HTMLAttributeAnchorTarget | undefined;
7
- type?: string | undefined | undefined;
8
- id?: string | undefined | undefined;
9
- prefix?: string | undefined | undefined;
10
- property?: string | undefined | undefined;
11
- children?: import("react").ReactNode;
12
- key?: import("react").Key | null | undefined;
13
7
  replace?: boolean | undefined;
8
+ id?: string | undefined | undefined;
14
9
  slot?: string | undefined | undefined;
15
10
  style?: import("react").CSSProperties | undefined;
16
11
  title?: string | undefined | undefined;
17
12
  locale?: Locale | undefined;
18
13
  onError?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
14
+ children?: import("react").ReactNode;
19
15
  ref?: import("react").Ref<HTMLAnchorElement> | undefined;
16
+ prefix?: string | undefined | undefined;
17
+ key?: import("react").Key | null | undefined;
20
18
  as?: (string | import("url").UrlObject) | undefined;
21
19
  scroll?: boolean | undefined;
22
20
  shallow?: boolean | undefined;
@@ -33,6 +31,7 @@ export default function createNavigation<const AppLocales extends Locales, const
33
31
  hrefLang?: string | undefined | undefined;
34
32
  media?: string | undefined | undefined;
35
33
  ping?: string | undefined | undefined;
34
+ type?: string | undefined | undefined;
36
35
  referrerPolicy?: import("react").HTMLAttributeReferrerPolicy | undefined;
37
36
  defaultChecked?: boolean | undefined | undefined;
38
37
  defaultValue?: string | number | readonly string[] | undefined;
@@ -59,6 +58,7 @@ export default function createNavigation<const AppLocales extends Locales, const
59
58
  content?: string | undefined | undefined;
60
59
  datatype?: string | undefined | undefined;
61
60
  inlist?: any;
61
+ property?: string | undefined | undefined;
62
62
  rel?: string | undefined | undefined;
63
63
  resource?: string | undefined | undefined;
64
64
  rev?: string | undefined | undefined;
@@ -5,19 +5,17 @@ export default function createNavigation<const AppLocales extends Locales, const
5
5
  useRouter: () => never;
6
6
  Link: import("react").ForwardRefExoticComponent<Omit<{
7
7
  target?: import("react").HTMLAttributeAnchorTarget | undefined;
8
- type?: string | undefined | undefined;
9
- id?: string | undefined | undefined;
10
- prefix?: string | undefined | undefined;
11
- property?: string | undefined | undefined;
12
- children?: import("react").ReactNode;
13
- key?: import("react").Key | null | undefined;
14
8
  replace?: boolean | undefined;
9
+ id?: string | undefined | undefined;
15
10
  slot?: string | undefined | undefined;
16
11
  style?: import("react").CSSProperties | undefined;
17
12
  title?: string | undefined | undefined;
18
13
  locale?: import("use-intl").Locale | undefined;
19
14
  onError?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
15
+ children?: import("react").ReactNode;
20
16
  ref?: import("react").Ref<HTMLAnchorElement> | undefined;
17
+ prefix?: string | undefined | undefined;
18
+ key?: import("react").Key | null | undefined;
21
19
  as?: (string | import("url").UrlObject) | undefined;
22
20
  scroll?: boolean | undefined;
23
21
  shallow?: boolean | undefined;
@@ -34,6 +32,7 @@ export default function createNavigation<const AppLocales extends Locales, const
34
32
  hrefLang?: string | undefined | undefined;
35
33
  media?: string | undefined | undefined;
36
34
  ping?: string | undefined | undefined;
35
+ type?: string | undefined | undefined;
37
36
  referrerPolicy?: import("react").HTMLAttributeReferrerPolicy | undefined;
38
37
  defaultChecked?: boolean | undefined | undefined;
39
38
  defaultValue?: string | number | readonly string[] | undefined;
@@ -60,6 +59,7 @@ export default function createNavigation<const AppLocales extends Locales, const
60
59
  content?: string | undefined | undefined;
61
60
  datatype?: string | undefined | undefined;
62
61
  inlist?: any;
62
+ property?: string | undefined | undefined;
63
63
  rel?: string | undefined | undefined;
64
64
  resource?: string | undefined | undefined;
65
65
  rev?: string | undefined | undefined;
@@ -23,19 +23,17 @@ export default function createSharedNavigationFns<const AppLocales extends Local
23
23
  };
24
24
  Link: import("react").ForwardRefExoticComponent<Omit<{
25
25
  target?: import("react").HTMLAttributeAnchorTarget | undefined;
26
- type?: string | undefined | undefined;
27
- id?: string | undefined | undefined;
28
- prefix?: string | undefined | undefined;
29
- property?: string | undefined | undefined;
30
- children?: import("react").ReactNode;
31
- key?: import("react").Key | null | undefined;
32
26
  replace?: boolean | undefined;
27
+ id?: string | undefined | undefined;
33
28
  slot?: string | undefined | undefined;
34
29
  style?: import("react").CSSProperties | undefined;
35
30
  title?: string | undefined | undefined;
36
31
  locale?: Locale | undefined;
37
32
  onError?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
33
+ children?: import("react").ReactNode;
38
34
  ref?: import("react").Ref<HTMLAnchorElement> | undefined;
35
+ prefix?: string | undefined | undefined;
36
+ key?: import("react").Key | null | undefined;
39
37
  as?: (string | import("url").UrlObject) | undefined;
40
38
  scroll?: boolean | undefined;
41
39
  shallow?: boolean | undefined;
@@ -52,6 +50,7 @@ export default function createSharedNavigationFns<const AppLocales extends Local
52
50
  hrefLang?: string | undefined | undefined;
53
51
  media?: string | undefined | undefined;
54
52
  ping?: string | undefined | undefined;
53
+ type?: string | undefined | undefined;
55
54
  referrerPolicy?: import("react").HTMLAttributeReferrerPolicy | undefined;
56
55
  defaultChecked?: boolean | undefined | undefined;
57
56
  defaultValue?: string | number | readonly string[] | undefined;
@@ -78,6 +77,7 @@ export default function createSharedNavigationFns<const AppLocales extends Local
78
77
  content?: string | undefined | undefined;
79
78
  datatype?: string | undefined | undefined;
80
79
  inlist?: any;
80
+ property?: string | undefined | undefined;
81
81
  rel?: string | undefined | undefined;
82
82
  resource?: string | undefined | undefined;
83
83
  rev?: string | undefined | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-intl",
3
- "version": "4.5.3",
3
+ "version": "4.5.4",
4
4
  "sideEffects": false,
5
5
  "author": "Jan Amann <jan@amann.work>",
6
6
  "funding": [
@@ -125,9 +125,11 @@
125
125
  ],
126
126
  "dependencies": {
127
127
  "@formatjs/intl-localematcher": "^0.5.4",
128
- "@swc/core": "^1.13.19",
128
+ "@swc/core": "^1.15.2",
129
129
  "negotiator": "^1.0.0",
130
- "use-intl": "^4.5.3"
130
+ "next-intl-swc-plugin-extractor": "^4.5.4",
131
+ "po-parser": "^0.1.2",
132
+ "use-intl": "^4.5.4"
131
133
  },
132
134
  "peerDependencies": {
133
135
  "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
@@ -139,5 +141,5 @@
139
141
  "optional": true
140
142
  }
141
143
  },
142
- "gitHead": "39bf801f0115dcd1700b4720dda1f46b53d3aec1"
144
+ "gitHead": "32b170cc341cc847d4a6f4ae353c53d941f626f2"
143
145
  }
@@ -1,18 +0,0 @@
1
- class ASTScope {
2
- vars = (() => new Map())();
3
- constructor(parent) {
4
- this.parent = parent;
5
- }
6
- define(name, kind, namespace) {
7
- this.vars.set(name, {
8
- kind,
9
- namespace
10
- });
11
- }
12
- lookup(name) {
13
- if (this.vars.has(name)) return this.vars.get(name);
14
- return this.parent?.lookup(name);
15
- }
16
- }
17
-
18
- export { ASTScope as default };
@@ -1,11 +0,0 @@
1
- import crypto from 'crypto';
2
-
3
- class KeyGenerator {
4
- static generate(message) {
5
- const hash = crypto.createHash('sha512').update(message).digest();
6
- const base64 = hash.toString('base64');
7
- return base64.slice(0, 6);
8
- }
9
- }
10
-
11
- export { KeyGenerator as default };
@@ -1,222 +0,0 @@
1
- class POParser {
2
- static KEYWORDS = (() => ({
3
- MSGID: 'msgid',
4
- MSGSTR: 'msgstr',
5
- MSGCTXT: 'msgctxt',
6
- MSGID_PLURAL: 'msgid_plural'
7
- }))();
8
- static COMMENTS = (() => ({
9
- REFERENCE: '#:',
10
- EXTRACTED: '#.',
11
- TRANSLATOR: '#',
12
- FLAG: '#,',
13
- PREVIOUS: '#|'
14
- }))();
15
- static NAMESPACE_SEPARATOR = '.';
16
- static QUOTE = '"';
17
- static NEWLINE = '\\n';
18
- static FILE_COLUMN_SEPARATOR = ':';
19
- static META_SEPARATOR = ':';
20
- static parse(content) {
21
- const lines = POParser.splitLines(content);
22
- const messages = [];
23
- const meta = {};
24
- let state = 'entry';
25
- let entry;
26
- for (let i = 0; i < lines.length; i++) {
27
- const line = lines[i].trim();
28
-
29
- // An empty line indicates the end of an entry
30
- if (!line) {
31
- if (state === 'entry' && entry) {
32
- messages.push(POParser.finishEntry(entry));
33
- entry = undefined;
34
- }
35
- state = 'entry';
36
- continue;
37
- }
38
- if (state === 'meta') {
39
- if (line.startsWith(POParser.QUOTE)) {
40
- const metaLine = POParser.extractQuotedString(line, state);
41
- const cleaned = metaLine.endsWith(POParser.NEWLINE) ? metaLine.slice(0, -2) : metaLine;
42
- const separatorIndex = cleaned.indexOf(POParser.META_SEPARATOR);
43
- if (separatorIndex > 0) {
44
- const key = cleaned.substring(0, separatorIndex).trim();
45
- const value = cleaned.substring(separatorIndex + 1).trim();
46
- meta[key] = value;
47
- }
48
- } else {
49
- POParser.throwWithLine('Encountered unexpected non-quoted metadata line', line);
50
- }
51
- } else {
52
- // Unsupported comment types
53
- if (POParser.lineStartsWithPrefix(line, POParser.COMMENTS.TRANSLATOR)) {
54
- POParser.throwWithLine('Translator comments (#) are not supported, use inline descriptions instead', line);
55
- }
56
- if (POParser.lineStartsWithPrefix(line, POParser.COMMENTS.FLAG)) {
57
- POParser.throwWithLine('Flag comments (#,) are not supported', line);
58
- }
59
- if (POParser.lineStartsWithPrefix(line, POParser.COMMENTS.PREVIOUS)) {
60
- POParser.throwWithLine('Previous string key comments (#|) are not supported', line);
61
- }
62
-
63
- // Reference comments
64
- if (POParser.lineStartsWithPrefix(line, POParser.COMMENTS.REFERENCE)) {
65
- entry = POParser.ensureEntry(entry);
66
- // Only use the path part, ignore line and column numbers
67
- const path = line.substring(POParser.COMMENTS.REFERENCE.length).trim().split(POParser.FILE_COLUMN_SEPARATOR).at(0);
68
- entry.references ??= [];
69
- entry.references.push({
70
- path
71
- });
72
- continue;
73
- }
74
-
75
- // Extracted comments
76
- if (POParser.lineStartsWithPrefix(line, POParser.COMMENTS.EXTRACTED)) {
77
- entry = POParser.ensureEntry(entry);
78
- entry.description = line.substring(POParser.COMMENTS.EXTRACTED.length).trim();
79
- continue;
80
- }
81
-
82
- // Check for unsupported features
83
- if (POParser.lineStartsWithPrefix(line, POParser.KEYWORDS.MSGID_PLURAL)) {
84
- POParser.throwWithLine('Plural forms (msgid_plural) are not supported, use ICU pluralization instead', line);
85
- }
86
-
87
- // msgctxt
88
- if (POParser.lineStartsWithPrefix(line, POParser.KEYWORDS.MSGCTXT)) {
89
- entry = POParser.ensureEntry(entry);
90
- entry.msgctxt = POParser.extractQuotedString(line.substring(POParser.KEYWORDS.MSGCTXT.length + 1), state);
91
- continue;
92
- }
93
-
94
- // msgid
95
- if (POParser.lineStartsWithPrefix(line, POParser.KEYWORDS.MSGID)) {
96
- entry = POParser.ensureEntry(entry);
97
- entry.msgid = POParser.extractQuotedString(line.substring(POParser.KEYWORDS.MSGID.length + 1), state);
98
- if (POParser.isMetaEntry(entry, messages)) {
99
- state = 'meta';
100
- entry = undefined;
101
- }
102
- continue;
103
- }
104
-
105
- // msgstr
106
- if (POParser.lineStartsWithPrefix(line, POParser.KEYWORDS.MSGSTR)) {
107
- entry = POParser.ensureEntry(entry);
108
- entry.msgstr = POParser.extractQuotedString(line.substring(POParser.KEYWORDS.MSGSTR.length + 1), state);
109
- if (POParser.isMetaEntry(entry, messages)) {
110
- state = 'meta';
111
- entry = undefined;
112
- }
113
- continue;
114
- }
115
-
116
- // Multi-line strings are not supported in entry mode
117
- if (line.startsWith(POParser.QUOTE)) {
118
- POParser.throwWithLine('Multi-line strings are not supported, use single-line strings instead', line);
119
- }
120
- }
121
- }
122
-
123
- // Finish any remaining entry
124
- if (state === 'entry' && entry) {
125
- messages.push(POParser.finishEntry(entry));
126
- }
127
- return {
128
- meta: Object.keys(meta).length > 0 ? meta : undefined,
129
- messages: messages.length > 0 ? messages : undefined
130
- };
131
- }
132
- static isMetaEntry(entry, messages) {
133
- return messages.length === 0 && entry.msgid === '' && entry.msgstr === '';
134
- }
135
- static serialize(catalog) {
136
- const lines = [];
137
-
138
- // Metadata
139
- if (catalog.meta) {
140
- lines.push(`${POParser.KEYWORDS.MSGID} ${POParser.QUOTE}${POParser.QUOTE}`);
141
- lines.push(`${POParser.KEYWORDS.MSGSTR} ${POParser.QUOTE}${POParser.QUOTE}`);
142
- for (const [key, value] of Object.entries(catalog.meta)) {
143
- lines.push(`${POParser.QUOTE}${key}${POParser.META_SEPARATOR} ${value}${POParser.NEWLINE}${POParser.QUOTE}`);
144
- }
145
- lines.push('');
146
- }
147
-
148
- // Messages
149
- if (catalog.messages) {
150
- for (const message of catalog.messages) {
151
- if (message.description) {
152
- lines.push(`${POParser.COMMENTS.EXTRACTED} ${message.description}`);
153
- }
154
- if (message.references && message.references.length > 0) {
155
- for (const ref of message.references) {
156
- lines.push(`${POParser.COMMENTS.REFERENCE} ${ref.path}`);
157
- }
158
- }
159
- let msgctxt;
160
- let msgid;
161
- const lastDotIndex = message.id.lastIndexOf(POParser.NAMESPACE_SEPARATOR);
162
- if (lastDotIndex > 0) {
163
- msgctxt = message.id.substring(0, lastDotIndex);
164
- msgid = message.id.substring(lastDotIndex + 1);
165
- } else {
166
- msgid = message.id;
167
- }
168
- if (msgctxt) {
169
- lines.push(`${POParser.KEYWORDS.MSGCTXT} ${POParser.QUOTE}${msgctxt}${POParser.QUOTE}`);
170
- }
171
- lines.push(`${POParser.KEYWORDS.MSGID} ${POParser.QUOTE}${msgid}${POParser.QUOTE}`);
172
- lines.push(`${POParser.KEYWORDS.MSGSTR} ${POParser.QUOTE}${message.message}${POParser.QUOTE}`);
173
- lines.push('');
174
- }
175
- }
176
- return lines.join('\n');
177
- }
178
- static lineStartsWithPrefix(line, prefix) {
179
- return line.startsWith(prefix + ' ');
180
- }
181
- static throwWithLine(message, line) {
182
- throw new Error(`${message}:\n> ${line}`);
183
- }
184
- static splitLines(content) {
185
- // Avoid overhead for Unix newlines only
186
- if (content.includes('\r')) {
187
- content = content.replace(/\r\n/g, '\n');
188
- }
189
- return content.split('\n');
190
- }
191
- static ensureEntry(entry) {
192
- return entry || {};
193
- }
194
- static finishEntry(entry) {
195
- if (entry.msgid == null || entry.msgstr == null) {
196
- throw new Error('Incomplete message entry: both msgid and msgstr are required');
197
- }
198
- let fullId = entry.msgid;
199
- if (entry.msgctxt) {
200
- fullId = entry.msgctxt + POParser.NAMESPACE_SEPARATOR + entry.msgid;
201
- }
202
- return {
203
- id: fullId,
204
- message: entry.msgstr,
205
- description: entry.description,
206
- references: entry.references
207
- };
208
- }
209
- static extractQuotedString(line, state) {
210
- const trimmed = line.trim();
211
- const endIndex = trimmed.indexOf(POParser.QUOTE, POParser.QUOTE.length);
212
- if (endIndex === -1) {
213
- if (state === 'meta') {
214
- return trimmed.substring(POParser.QUOTE.length);
215
- }
216
- POParser.throwWithLine('Incomplete quoted string', line);
217
- }
218
- return trimmed.substring(POParser.QUOTE.length, endIndex);
219
- }
220
- }
221
-
222
- export { POParser as default };
@@ -1 +0,0 @@
1
- class s{vars=(()=>new Map)();constructor(s){this.parent=s}define(s,t,a){this.vars.set(s,{kind:t,namespace:a})}lookup(s){return this.vars.has(s)?this.vars.get(s):this.parent?.lookup(s)}}export{s as default};
@@ -1 +0,0 @@
1
- import t from"crypto";class e{static generate(e){return t.createHash("sha512").update(e).digest().toString("base64").slice(0,6)}}export{e as default};
@@ -1 +0,0 @@
1
- class t{static KEYWORDS={MSGID:"msgid",MSGSTR:"msgstr",MSGCTXT:"msgctxt",MSGID_PLURAL:"msgid_plural"};static COMMENTS={REFERENCE:"#:",EXTRACTED:"#.",TRANSLATOR:"#",FLAG:"#,",PREVIOUS:"#|"};static NAMESPACE_SEPARATOR=".";static QUOTE='"';static NEWLINE="\\n";static FILE_COLUMN_SEPARATOR=":";static META_SEPARATOR=":";static parse(e){const s=t.splitLines(e),i=[],r={};let n,E="entry";for(let e=0;e<s.length;e++){const a=s[e].trim();if(a)if("meta"===E)if(a.startsWith(t.QUOTE)){const e=t.extractQuotedString(a,E),s=e.endsWith(t.NEWLINE)?e.slice(0,-2):e,i=s.indexOf(t.META_SEPARATOR);if(i>0){const t=s.substring(0,i).trim(),e=s.substring(i+1).trim();r[t]=e}}else t.throwWithLine("Encountered unexpected non-quoted metadata line",a);else{if(t.lineStartsWithPrefix(a,t.COMMENTS.TRANSLATOR)&&t.throwWithLine("Translator comments (#) are not supported, use inline descriptions instead",a),t.lineStartsWithPrefix(a,t.COMMENTS.FLAG)&&t.throwWithLine("Flag comments (#,) are not supported",a),t.lineStartsWithPrefix(a,t.COMMENTS.PREVIOUS)&&t.throwWithLine("Previous string key comments (#|) are not supported",a),t.lineStartsWithPrefix(a,t.COMMENTS.REFERENCE)){n=t.ensureEntry(n);const e=a.substring(t.COMMENTS.REFERENCE.length).trim().split(t.FILE_COLUMN_SEPARATOR).at(0);n.references??=[],n.references.push({path:e});continue}if(t.lineStartsWithPrefix(a,t.COMMENTS.EXTRACTED)){n=t.ensureEntry(n),n.description=a.substring(t.COMMENTS.EXTRACTED.length).trim();continue}if(t.lineStartsWithPrefix(a,t.KEYWORDS.MSGID_PLURAL)&&t.throwWithLine("Plural forms (msgid_plural) are not supported, use ICU pluralization instead",a),t.lineStartsWithPrefix(a,t.KEYWORDS.MSGCTXT)){n=t.ensureEntry(n),n.msgctxt=t.extractQuotedString(a.substring(t.KEYWORDS.MSGCTXT.length+1),E);continue}if(t.lineStartsWithPrefix(a,t.KEYWORDS.MSGID)){n=t.ensureEntry(n),n.msgid=t.extractQuotedString(a.substring(t.KEYWORDS.MSGID.length+1),E),t.isMetaEntry(n,i)&&(E="meta",n=void 0);continue}if(t.lineStartsWithPrefix(a,t.KEYWORDS.MSGSTR)){n=t.ensureEntry(n),n.msgstr=t.extractQuotedString(a.substring(t.KEYWORDS.MSGSTR.length+1),E),t.isMetaEntry(n,i)&&(E="meta",n=void 0);continue}a.startsWith(t.QUOTE)&&t.throwWithLine("Multi-line strings are not supported, use single-line strings instead",a)}else"entry"===E&&n&&(i.push(t.finishEntry(n)),n=void 0),E="entry"}return"entry"===E&&n&&i.push(t.finishEntry(n)),{meta:Object.keys(r).length>0?r:void 0,messages:i.length>0?i:void 0}}static isMetaEntry(t,e){return 0===e.length&&""===t.msgid&&""===t.msgstr}static serialize(e){const s=[];if(e.meta){s.push(`${t.KEYWORDS.MSGID} ${t.QUOTE}${t.QUOTE}`),s.push(`${t.KEYWORDS.MSGSTR} ${t.QUOTE}${t.QUOTE}`);for(const[i,r]of Object.entries(e.meta))s.push(`${t.QUOTE}${i}${t.META_SEPARATOR} ${r}${t.NEWLINE}${t.QUOTE}`);s.push("")}if(e.messages)for(const i of e.messages){if(i.description&&s.push(`${t.COMMENTS.EXTRACTED} ${i.description}`),i.references&&i.references.length>0)for(const e of i.references)s.push(`${t.COMMENTS.REFERENCE} ${e.path}`);let e,r;const n=i.id.lastIndexOf(t.NAMESPACE_SEPARATOR);n>0?(e=i.id.substring(0,n),r=i.id.substring(n+1)):r=i.id,e&&s.push(`${t.KEYWORDS.MSGCTXT} ${t.QUOTE}${e}${t.QUOTE}`),s.push(`${t.KEYWORDS.MSGID} ${t.QUOTE}${r}${t.QUOTE}`),s.push(`${t.KEYWORDS.MSGSTR} ${t.QUOTE}${i.message}${t.QUOTE}`),s.push("")}return s.join("\n")}static lineStartsWithPrefix(t,e){return t.startsWith(e+" ")}static throwWithLine(t,e){throw new Error(`${t}:\n> ${e}`)}static splitLines(t){return t.includes("\r")&&(t=t.replace(/\r\n/g,"\n")),t.split("\n")}static ensureEntry(t){return t||{}}static finishEntry(e){if(null==e.msgid||null==e.msgstr)throw new Error("Incomplete message entry: both msgid and msgstr are required");let s=e.msgid;return e.msgctxt&&(s=e.msgctxt+t.NAMESPACE_SEPARATOR+e.msgid),{id:s,message:e.msgstr,description:e.description,references:e.references}}static extractQuotedString(e,s){const i=e.trim(),r=i.indexOf(t.QUOTE,t.QUOTE.length);if(-1===r){if("meta"===s)return i.substring(t.QUOTE.length);t.throwWithLine("Incomplete quoted string",e)}return i.substring(t.QUOTE.length,r)}}export{t as default};
@@ -1,24 +0,0 @@
1
- import type { ExtractedMessage } from '../types.js';
2
- type Catalog = {
3
- meta?: Record<string, string>;
4
- messages?: Array<ExtractedMessage>;
5
- };
6
- export default class POParser {
7
- private static readonly KEYWORDS;
8
- private static readonly COMMENTS;
9
- private static readonly NAMESPACE_SEPARATOR;
10
- private static readonly QUOTE;
11
- private static readonly NEWLINE;
12
- private static readonly FILE_COLUMN_SEPARATOR;
13
- private static readonly META_SEPARATOR;
14
- static parse(content: string): Catalog;
15
- private static isMetaEntry;
16
- static serialize(catalog: Catalog): string;
17
- private static lineStartsWithPrefix;
18
- private static throwWithLine;
19
- private static splitLines;
20
- private static ensureEntry;
21
- private static finishEntry;
22
- private static extractQuotedString;
23
- }
24
- export {};