Skip to content

Commit b75afc6

Browse files
amannnclaude
andauthored
fix: Deprecate toJSONString (no longer needed) (#2350)
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 375b031 commit b75afc6

7 files changed

Lines changed: 55 additions & 67 deletions

File tree

docs/src/pages/docs/usage/plugin.mdx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -261,10 +261,6 @@ export default defineCodec(() => ({
261261

262262
encode(messages, context) {
263263
// ...
264-
},
265-
266-
toJSONString(content, context) {
267-
// ...
268264
}
269265
}));
270266
```

packages/next-intl/src/extractor/format/ExtractorCodec.tsx

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,11 @@ export default interface ExtractorCodec {
2626
): string;
2727

2828
/**
29-
* Turns the content of a file into a JSON string that represents extracted
30-
* messages. The returned value will be passed to `JSON.parse`.
31-
*
32-
* @return E.g. `[{"id":"-YJVTi","message":"Hey!"}]`
33-
*
34-
* This is used when loading messages into your application, typically via a
35-
* dynamic import (e.g. `import(`../messages/${locale}.json`)`).
36-
*
37-
* If your file content is JSON and should be used as-is, you can set this to
38-
* an identity function.
29+
* @deprecated No longer used. Catalogs are loaded into your application via
30+
* `decode`, so you can remove `toJSONString` from your codec. Providing it
31+
* logs a deprecation warning and has no effect.
3932
*/
40-
toJSONString(content: string, context: ExtractorCodecContext): string;
33+
toJSONString?(content: string, context: ExtractorCodecContext): string;
4134
}
4235

4336
export function defineCodec(factory: () => ExtractorCodec) {

packages/next-intl/src/extractor/format/codecs/JSONCodec.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,6 @@ export default defineCodec(() => ({
2828
setNestedProperty(root, message.id, message.message);
2929
}
3030
return JSON.stringify(root, null, 2) + '\n';
31-
},
32-
33-
toJSONString(source) {
34-
return source;
3531
}
3632
}));
3733

packages/next-intl/src/extractor/format/codecs/POCodec.tsx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import POParser from 'po-parser';
2-
import {getSortedMessages, setNestedProperty} from '../../utils.js';
2+
import {getSortedMessages} from '../../utils.js';
33
import {defineCodec} from '../ExtractorCodec.js';
44

55
export default defineCodec(() => {
@@ -77,15 +77,6 @@ export default defineCodec(() => {
7777
},
7878
messages: encodedMessages
7979
});
80-
},
81-
82-
toJSONString(source, context) {
83-
const parsed = this.decode(source, context);
84-
const messagesObject = {};
85-
for (const message of parsed) {
86-
setNestedProperty(messagesObject, message.id, message.message);
87-
}
88-
return JSON.stringify(messagesObject);
8980
}
9081
};
9182
});

packages/next-intl/src/extractor/format/codecs/fixtures/JSONCodecStructured.tsx

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,5 @@ export default defineCodec(() => ({
2525
};
2626
}
2727
return JSON.stringify(obj, null, 2) + '\n';
28-
},
29-
30-
toJSONString(content) {
31-
const data = JSON.parse(content);
32-
const result: Record<string, string> = {};
33-
for (const [id, value] of Object.entries(data)) {
34-
const obj = value as StoredMessage;
35-
result[id] = obj.message;
36-
}
37-
return JSON.stringify(result);
3828
}
3929
}));

packages/next-intl/src/extractor/format/codecs/fixtures/POCodecSourceMessageKey.tsx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import POParser from 'po-parser';
2-
import {getSortedMessages, setNestedProperty} from '../../../utils.js';
2+
import {getSortedMessages} from '../../../utils.js';
33
import {defineCodec} from '../../ExtractorCodec.js';
44

55
export default defineCodec(() => {
@@ -81,15 +81,6 @@ export default defineCodec(() => {
8181
},
8282
messages: encodedMessages
8383
});
84-
},
85-
86-
toJSONString(source, context) {
87-
const parsed = this.decode(source, context);
88-
const messagesObject = {};
89-
for (const message of parsed) {
90-
setNestedProperty(messagesObject, message.id, message.message);
91-
}
92-
return JSON.stringify(messagesObject);
9384
}
9485
};
9586
});

packages/next-intl/src/plugin/catalog/catalogLoader.tsx

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import type {TurbopackLoaderContext} from '../types.js';
1616
// create multiple loader instances so don't expect a singleton.
1717
let cachedCodec: ExtractorCodec | null = null;
1818

19+
let hasWarnedAboutToJSONString = false;
20+
1921
type CompiledMessageCacheEntry = {
2022
compiledMessage: unknown;
2123
messageValue: string;
@@ -62,26 +64,66 @@ export default function catalogLoader(
6264

6365
getCodec(options, this.rootContext)
6466
.then((codec) => {
67+
warnIfToJSONStringProvided(codec);
68+
6569
const locale = path.basename(this.resourcePath, extension);
66-
let outputString: string;
70+
const decoded = codec.decode(source, {locale});
6771

72+
let messages: Record<string, unknown>;
6873
if (options.messages.precompile) {
69-
const decoded = codec.decode(source, {locale});
7074
const cache = getMessageCache(this.resourcePath);
71-
const precompiled = precompileMessages(decoded, cache);
72-
outputString = JSON.stringify(precompiled);
75+
messages = precompileMessages(decoded, cache);
7376
} else {
74-
outputString = codec.toJSONString(source, {locale});
77+
messages = buildMessages(decoded);
7578
}
7679

7780
// https://v8.dev/blog/cost-of-javascript-2019#json
78-
const result = `export default JSON.parse(${JSON.stringify(outputString)});`;
81+
const result = `export default JSON.parse(${JSON.stringify(JSON.stringify(messages))});`;
7982

8083
callback(null, result);
8184
})
8285
.catch(callback);
8386
}
8487

88+
function warnIfToJSONStringProvided(codec: ExtractorCodec) {
89+
if (hasWarnedAboutToJSONString || !codec.toJSONString) return;
90+
hasWarnedAboutToJSONString = true;
91+
console.warn(
92+
'`toJSONString` is deprecated and no longer needed, please remove it.'
93+
);
94+
}
95+
96+
/**
97+
* Builds a nested messages object from decoded messages, mirroring the shape
98+
* produced by `precompileMessages` but keeping the raw message strings.
99+
*/
100+
function buildMessages(
101+
messages: Array<ExtractorMessage>
102+
): Record<string, unknown> {
103+
const result = Object.create(null) as Record<string, unknown>;
104+
for (const message of messages) {
105+
assertStringMessage(message);
106+
setNestedProperty(result, message.id, message.message);
107+
}
108+
return result;
109+
}
110+
111+
function assertStringMessage(message: ExtractorMessage) {
112+
const messageValue = message.message;
113+
114+
if (Array.isArray(messageValue)) {
115+
throw new Error(
116+
`Message at \`${message.id}\` resolved to an array, but only strings are supported. See https://next-intl.dev/docs/usage/translations#arrays-of-messages`
117+
);
118+
}
119+
120+
if (typeof messageValue === 'object') {
121+
throw new Error(
122+
`Message at \`${message.id}\` resolved to \`${typeof messageValue}\`, but only strings are supported. Use a \`.\` to retrieve nested messages. See https://next-intl.dev/docs/usage/translations#structuring-messages`
123+
);
124+
}
125+
}
126+
85127
/**
86128
* Recursively precompiles all ICU message strings in a messages object
87129
* using icu-minify/compile for smaller runtime bundles.
@@ -95,20 +137,9 @@ function precompileMessages(
95137

96138
for (const message of messages) {
97139
cacheKeysToEvict.delete(message.id);
140+
assertStringMessage(message);
98141
const messageValue = message.message;
99142

100-
if (Array.isArray(messageValue)) {
101-
throw new Error(
102-
`Message at \`${message.id}\` resolved to an array, but only strings are supported. See https://next-intl.dev/docs/usage/translations#arrays-of-messages`
103-
);
104-
}
105-
106-
if (typeof messageValue === 'object') {
107-
throw new Error(
108-
`Message at \`${message.id}\` resolved to \`${typeof messageValue}\`, but only strings are supported. Use a \`.\` to retrieve nested messages. See https://next-intl.dev/docs/usage/translations#structuring-messages`
109-
);
110-
}
111-
112143
const cachedEntry = cache.get(message.id);
113144
const hasCacheMatch = cachedEntry?.messageValue === messageValue;
114145

0 commit comments

Comments
 (0)