Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
135 commits
Select commit Hold shift + click to select a range
b73586f
fix: Follow-up for #1573 to also handle the case when a non-default l…
amannn Nov 26, 2024
b44c8ef
Merge branch 'main' into canary
amannn Jan 21, 2025
b4de1a0
Merge remote-tracking branch 'origin/main' into canary
amannn Feb 17, 2025
84fe6d0
fix: Add workaround for OpenTelemetry/Zone.js (#1718)
amannn Feb 17, 2025
eade8d9
Merge remote-tracking branch 'origin/main' into canary
amannn Mar 12, 2025
19e339e
fix: Handle default exports correctly for `moduleResolution: 'node'` …
amannn Mar 12, 2025
ad6f306
fix: downgrade deps
amannn Mar 12, 2025
fdaddc5
fix: Patch version
amannn Mar 12, 2025
ad8d7c5
feat: Support stable turbo config in Next.js 15.3
amannn Apr 17, 2025
5cb0f4d
Upgrade to Next.js 15.3
amannn Apr 17, 2025
ac88b91
Merge remote-tracking branch 'origin/main' into canary
amannn Apr 17, 2025
b2e2aa5
fix lint
amannn Apr 17, 2025
bac7310
Merge remote-tracking branch 'origin/canary' into feat/1838-stable-tu…
amannn Apr 17, 2025
58b4a57
fix: Support stable Turbopack config (#1849)
amannn Apr 17, 2025
79fdcdc
Handle case if using experimental turbo config
amannn Apr 22, 2025
6918a47
Merge branch 'feat/1838-stable-turbo-config' into canary
amannn Apr 22, 2025
d5ffd72
Merge remote-tracking branch 'origin/main' into canary
amannn Apr 23, 2025
09b34ea
feat: Add `forcePrefix` option for `redirect` and `getPathname` (#1864)
amannn Apr 23, 2025
e629aa8
Update packages/next-intl/src/navigation/shared/createSharedNavigatio…
amannn Apr 23, 2025
69ae8e7
Wording
amannn Apr 24, 2025
0b3a5c6
Merge remote-tracking branch 'origin/main' into canary
amannn Jun 11, 2025
282196c
feat: Encode non-ASCII characters in pathnames returned from navigati…
amannn Jun 11, 2025
69cdf47
Add test for alternate links
amannn Jun 11, 2025
8172ea2
fix: Don't encode hashes in unknown pathnames
amannn Jun 24, 2025
c596c85
fix: Handle hashes in pathnames correctly when using `trailingSlash: …
amannn Jun 24, 2025
3f78906
Merge branch 'main' into canary
amannn Jun 24, 2025
025af7b
Merge branch 'canary' into fix/unknown-pathnames-encoding
amannn Jun 24, 2025
4ada87c
fix: Release
amannn Jun 24, 2025
f4ce836
Merge remote-tracking branch 'origin/main' into canary
amannn Jun 30, 2025
92d8a58
fix: Ensure cookie is synced before navigation with `useRouter` (#1946)
amannn Jun 30, 2025
7a3e132
Merge branch 'main' into canary
amannn Oct 15, 2025
f0249be
fix: Trusted publishers
amannn Oct 15, 2025
f500482
Merge remote-tracking branch 'origin/main' into canary
amannn Nov 4, 2025
2b3a9ce
feat: Add `useExtracted` (experimental) (#2078)
amannn Nov 5, 2025
5dfd0f5
Minor improvement for LRU cache eviction
amannn Nov 5, 2025
d29ce7b
plugin reference docs
amannn Nov 5, 2025
c8efecc
fix script
amannn Nov 5, 2025
e7eb9dd
fix: keep ids used in other files
amannn Nov 6, 2025
e99a61d
docs
amannn Nov 6, 2025
4481378
blog post draft
amannn Nov 6, 2025
75ea108
Merge remote-tracking branch 'origin/main' into canary
amannn Nov 7, 2025
d478b4e
post wording updates
amannn Nov 7, 2025
0ba5a18
more robust meta parsing for po parser
amannn Nov 7, 2025
75af761
fix: don't export _useExtracted
amannn Nov 7, 2025
1d5dd9f
size
amannn Nov 7, 2025
2070129
fix test
amannn Nov 7, 2025
2bb68f8
finalize blog article
amannn Nov 7, 2025
7e95eb2
docs finishing touches
amannn Nov 7, 2025
79f89da
styling fix
amannn Nov 7, 2025
1c7309e
fix(useExtracted): Handle source maps
amannn Nov 11, 2025
4de0271
Merge remote-tracking branch 'origin/main' into canary
amannn Nov 11, 2025
a20b694
Refactor: Rename 'source' to 'code' in extractor
cursoragent Nov 11, 2025
9fbf9d2
Refactor: Rename 'code' parameter to 'source' for clarity
cursoragent Nov 11, 2025
96a26af
fix(useExtracted): Ensure deterministic translation key order (#2091)
amannn Nov 12, 2025
afa0598
fix(useExtracted): Filter common directories from `srcPath`, if not e…
amannn Nov 12, 2025
13ce943
pass more options
amannn Nov 12, 2025
00e2a17
fix: Allow passing a read-only array as `locales` (#2100)
amannn Nov 12, 2025
e182109
Merge remote-tracking branch 'origin/main' into canary
amannn Nov 12, 2025
3e3648d
fix(useExtracted): Fix inconsistent translation message ordering (#2101)
amannn Nov 13, 2025
8f1d542
fix: Release
amannn Nov 13, 2025
89a45c9
initialize SWC plugin
kdy1 Nov 14, 2025
85ab9dc
Update swc/core
kdy1 Nov 14, 2025
6f91e11
Cargo workspace
kdy1 Nov 14, 2025
f6d5d92
emit results
kdy1 Nov 14, 2025
16d064f
TODO
kdy1 Nov 14, 2025
e954e18
TODos
kdy1 Nov 14, 2025
de232ff
Well
kdy1 Nov 14, 2025
7794c5b
fmt
kdy1 Nov 14, 2025
29b634c
rename_all
kdy1 Nov 14, 2025
8cb9cb7
filePath
kdy1 Nov 14, 2025
377c425
Fix hardcoded
kdy1 Nov 14, 2025
a9467ff
Add a rust test
kdy1 Nov 14, 2025
6fe148e
fix callee rewrite
kdy1 Nov 14, 2025
f79deb3
Update test refs
kdy1 Nov 14, 2025
817088d
Remove dbg!
kdy1 Nov 14, 2025
a16a0a2
Fix syntax context
kdy1 Nov 14, 2025
7b5c870
lint
kdy1 Nov 14, 2025
ef6f4f7
Update tests
amannn Nov 14, 2025
cf8b3d8
Merge remote-tracking branch 'origin/main' into canary
amannn Nov 14, 2025
2b10e99
fix: Move implementation of `POParser` to `po-parser` (#2113)
amannn Nov 14, 2025
5fe7d7d
Latest `@swc/core`
amannn Nov 17, 2025
dec3fab
Merge remote-tracking branch 'origin/main' into fix/useextracted-sour…
amannn Nov 17, 2025
77b4fa9
Merge branch 'canary' into fix/useextracted-sourcemap
amannn Nov 17, 2025
a262b53
some cleanup
amannn Nov 17, 2025
ffae195
Refactor: Consolidate MessageExtractor tests into integration suite
cursoragent Nov 18, 2025
c00832d
Refactor: Remove unused MessageExtractor tests
cursoragent Nov 18, 2025
dc2c0f5
clean up tests
amannn Nov 18, 2025
302bd06
clean up swc tests
amannn Nov 18, 2025
2b8d5be
fix(useExtracted): Migrate message extractor tests to Rust (#2118)
amannn Nov 18, 2025
f1262db
packaging
amannn Nov 18, 2025
efcb065
contributors
amannn Nov 19, 2025
30ebbce
Merge branch 'cursor/migrate-message-extractor-tests-to-rust-514e' in…
amannn Nov 19, 2025
b6ff530
wip
amannn Nov 19, 2025
95a6881
swc config for turbo
amannn Nov 19, 2025
b96687a
cleanup
amannn Nov 19, 2025
2403765
cleanup
amannn Nov 19, 2025
eef4d5c
wip
amannn Nov 19, 2025
6f0d640
public
amannn Nov 19, 2025
b440b3e
rename
amannn Nov 19, 2025
677837a
test turbo caching
amannn Nov 19, 2025
c36123a
fix: Move AST transformer of `useExtracted` to SWC plugin & handle so…
amannn Nov 19, 2025
5af53d4
fix: update ci script
amannn Nov 19, 2025
4dfc91a
Merge branch 'fix/useextracted-sourcemap' into canary
amannn Nov 19, 2025
0c55fd4
fix: release
amannn Nov 19, 2025
78767ee
Merge remote-tracking branch 'origin/main' into canary
amannn Nov 24, 2025
8d81357
fix: Handle race condition when detecting locale changes which could …
amannn Nov 25, 2025
ee70d3b
fix: Don't depend on environment locale for sorting of keys with `use…
amannn Nov 26, 2025
94d7fae
fix: Retain .po flags for `useExtracted` and update `po-parser` to fi…
amannn Nov 27, 2025
a6a334e
Merge remote-tracking branch 'origin/main' into canary
amannn Dec 2, 2025
873c3af
fix: Append newline with `.json` formatter for `useExtracted` (#2148)
amannn Dec 2, 2025
1ccdee0
fix: Handle multiple calls to `useTranslations` and `useExtracted` (#…
amannn Dec 2, 2025
067b6e7
fix: Conflate existing `useTranslations` import for `useExtracted` (#…
amannn Dec 2, 2025
839c94e
test: Fix flaky `useExtracted` test (#2151)
amannn Dec 2, 2025
3362faf
Merge remote-tracking branch 'origin/main' into canary
amannn Dec 3, 2025
3ad9bf4
fix: Fail loud if messages can't be read for usage with `useExtracted…
amannn Dec 3, 2025
03d4f28
fix: Try trusted publishers
amannn Dec 3, 2025
a3dd55e
fix: update lerna lite and conventional-changelog-conventionalcommits
amannn Dec 3, 2025
028d37a
fix: no prevenance, log npm version
amannn Dec 3, 2025
dd24f9d
fix: flip some flags
amannn Dec 3, 2025
bcf7147
fix: minor fixes
amannn Dec 3, 2025
fb5f78a
fix: revert some stuff
amannn Dec 3, 2025
65289bd
fix: cleanup
amannn Dec 3, 2025
853114a
fix: one more
amannn Dec 3, 2025
683476f
feat: Custom formats for `useExtracted` (#2158)
amannn Dec 5, 2025
38e3f96
Merge branch 'main' into canary
amannn Dec 9, 2025
6876534
fix: Handle messages with namespaces and with dots in message correct…
amannn Dec 9, 2025
632693c
fix: Improve dot escaping in regex
amannn Dec 9, 2025
1ef30f5
fix: Stable `po-parser@2`
amannn Dec 10, 2025
658487e
fix: Update references of `useExtracted` after renames / deletions (#…
amannn Dec 11, 2025
62e3004
fix: Prune unused messages from `useExtracted` (during build) (#2169)
amannn Dec 11, 2025
919a0e3
fix: Prune unused messages from `useExtracted` (during `dev`) (#2170)
amannn Dec 11, 2025
d4f4cde
fix: thanks cursor
amannn Dec 11, 2025
63b8301
fix: Cleanup custom formats for `useExtracted` (#2173)
amannn Dec 12, 2025
1cc54ff
fix: Another source of inconsistency fixed
amannn Dec 12, 2025
d43359c
fix: Early bailout
amannn Dec 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .github/workflows/prerelease-canary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@ jobs:
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
registry-url: "https://registry.npmjs.org"
node-version: 20.x
cache: "pnpm"
- run: npm install -g npm@latest # Trusted publishers
node-version: 20.x
registry-url: "https://registry.npmjs.org"
- run: pnpm install
- run: pnpm turbo run build --filter './packages/**'
- run: |
Expand Down
7 changes: 3 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ jobs:
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
registry-url: 'https://registry.npmjs.org'
cache: "pnpm"
node-version: 20.x
cache: 'pnpm'
registry-url: "https://registry.npmjs.org"
- run: npm install -g npm@latest # Trusted publishers
- run: pnpm install
- run: |
Expand All @@ -28,5 +28,4 @@ jobs:
if: "${{startsWith(github.event.head_commit.message, 'fix: ') || startsWith(github.event.head_commit.message, 'feat: ') || startsWith(github.event.head_commit.message, 'feat!: ')}}"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_CONFIG_PROVENANCE: true
# No NODE_AUTH_TOKEN since we use Trusted Publishers
1 change: 0 additions & 1 deletion docs/src/pages/docs/usage/configuration.mdx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import PartnerContentLink from '@/components/PartnerContentLink';
import Callout from '@/components/Callout';
import {Tabs} from 'nextra/components';
import Details from '@/components/Details';
Expand Down
70 changes: 59 additions & 11 deletions docs/src/pages/docs/usage/extraction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Image from 'next/image';
import Callout from '@/components/Callout';
import Details from '@/components/Details';

# `useExtracted`
# `useExtracted` (experimental)

As an alternative to managing namespaces and keys manually, `next-intl` provides an additional API that works similar to [`useTranslations`](/docs/usage/translations) but automatically extracts messages from your source files.

Expand Down Expand Up @@ -48,7 +48,7 @@ function InlineMessages() {

**Links:**

- [Blog post](/blog/use-extracted)
- [Introduction blog post](/blog/use-extracted)
- [Example app](/examples#app-router-extracted)

## Getting started
Expand All @@ -73,7 +73,7 @@ const withNextIntl = createNextIntlPlugin({
// Relative path to the directory
path: './messages',

// Either 'json' or 'po'
// Either 'json', 'po', or a custom format (see below)
format: 'json',

// Either 'infer' to automatically detect locales based on
Expand Down Expand Up @@ -268,11 +268,11 @@ it('renders', () => {
});
```

## Formatters
## Formats [#formats]

Currently, messages can be extracted as either JSON or PO files. Support for custom formatters is planned for a future release.
Messages can be extracted as JSON, PO, or with custom file formats.

When [`messages`](/docs/usage/plugin#messages) is configured, this will also set up a Turbo- or Webpack loader that will ensure loaded messages can be imported as plain JavaScript objects.
When [`messages`](/docs/usage/plugin#messages) is configured, this will also set up a Turbo- or Webpack loader that will ensure imported messages can be used as plain JavaScript objects.

For example, when using `format: 'po'`, messages can be imported as:

Expand All @@ -283,14 +283,14 @@ import {getRequestConfig} from 'next-intl/server';
export default getRequestConfig(async () => {
const locale = 'en';

// E.g. `{"NhX4DJ": "Hello"}`
// E.g. `[{"NhX4DJ": "Hello"}]`
const messages = (await import(`../../messages/${locale}.po`)).default;

// ...
});
```

### JSON formatter [#formatters-json]
### JSON format [#formats-json]

When using this option, your messages will look like this:

Expand All @@ -300,7 +300,7 @@ When using this option, your messages will look like this:
}
```

Note that JSON files can only hold pairs of keys and values. To provide more context about a message like file references and descriptions, it's therefore recommended to use [PO files](#po) instead. Another alternative will be to use a custom JSON formatter in the future.
Note that JSON files can only hold pairs of keys and values. To provide more context about a message like file references and descriptions, it's therefore recommended to use [PO files](#formats-po) instead. Alternatively, you can create a [custom format](#formats-custom) to store additional metadata.

For local editing of JSON messages, you can use e.g. a [VSCode integration](/docs/workflows/vscode-integration) like i18n Ally:

Expand All @@ -318,7 +318,7 @@ For local editing of JSON messages, you can use e.g. a [VSCode integration](/doc

</Callout>

### PO formatter [#formatters-po]
### PO format [#formats-po]

When using this option, your messages will look like this:

Expand All @@ -331,10 +331,58 @@ msgstr "Right"

Besides the message key and the label itself, this format also supports optional descriptions and file references to all modules that consume this message.

For local editing of PO messages, you can use e.g. a tool like [Poedit](https://poedit.net/) (replacing keys with source text requires a pro license).
For local editing of .po files, you can use e.g. a tool like [Poedit](https://poedit.net/) (note however that replacing keys with source text requires a pro license).

<Callout>

**Tip:** AI-based translation can be automated with a translation management system like [Crowdin](/docs/workflows/localization-management).

</Callout>

### Custom format [#formats-custom]

To configure a custom format, you need to specify a codec along with an extension.

The codec can be created via `defineCodec` from `next-intl/extractor`:

```tsx filename="./CustomCodec.ts"
import {defineCodec} from 'next-intl/extractor';

export default defineCodec(() => ({
decode(content, context) {
// ...
},

encode(messages, context) {
// ...
},

toJSONString(content, context) {
// ...
}
}));
```

Then, reference it in your configuration along with an `extension`:

```tsx filename="next.config.ts"
const withNextIntl = createNextIntlPlugin({
experimental: {
messages: {
format: {
codec: './CustomCodec.ts',
extension: '.json'
}
// ...
}
}
});
```

See also the built-in [`codecs`](https://github.com/amannn/next-intl/tree/main/packages/next-intl/src/extractor/format/codecs) for inspiration.

<Callout>

Node.js supports native TypeScript execution like it's needed for the example above starting with v22.18. If you're on an older version, you should define your codec as a JavaScript file.

</Callout>
4 changes: 2 additions & 2 deletions docs/src/pages/docs/usage/plugin.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ const withNextIntl = createNextIntlPlugin({
// Automatically detects locales based on `path`
locales: 'infer',

// Either 'json' or 'po'
// Either 'json', 'po', or a custom format
format: 'json'
}
// ...
Expand All @@ -107,7 +107,7 @@ If you want to specify the locales explicitly, you can provide an array for `loc
locales: ['en', 'de'];
```

Configuring `experimental.messages` will also set up a Turbo- or Webpack loader that will ensure loaded messages can be imported as plain JavaScript objects (see [formatters](/docs/usage/extraction#formatters)).
Configuring `experimental.messages` will also set up a Turbo- or Webpack loader that will ensure loaded messages can be imported as plain JavaScript objects (see [formats](/docs/usage/extraction#formats)).

**Note:** The `messages` option should be used together with [`extract`](#extract) and [`srcPath`](#src-path).

Expand Down
2 changes: 1 addition & 1 deletion examples/example-app-router-extracted/src/app/Counter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import {useExtracted} from 'next-intl';
import {useState} from 'react';

export default function Client() {
export default function Counter() {
const [count, setCount] = useState(1000);
const t = useExtracted();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
/* eslint-disable import/no-extraneous-dependencies */
import type {PlaywrightTestConfig} from '@playwright/test';
import {devices} from '@playwright/test';

// Use a distinct port on CI to avoid conflicts during concurrent tests
const PORT = process.env.CI ? 3002 : 3000;

const config: PlaywrightTestConfig = {
retries: process.env.CI ? 1 : 0,
retries: process.env.CI ? 2 : 0,
testDir: './tests',
projects: [
{
Expand Down
2 changes: 1 addition & 1 deletion examples/example-app-router-next-auth/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {devices} from '@playwright/test';
const PORT = process.env.CI ? 3003 : 3000;

const config: PlaywrightTestConfig = {
retries: process.env.CI ? 1 : 0,
retries: process.env.CI ? 2 : 0,
testDir: './tests',
projects: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const PORT = process.env.CI ? 3004 : 3000;
process.env.PORT = PORT.toString();

const config: PlaywrightTestConfig = {
retries: process.env.CI ? 1 : 0,
retries: process.env.CI ? 2 : 0,
testMatch: process.env.TEST_MATCH || 'main.spec.ts',
testDir: './tests',
projects: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {devices} from '@playwright/test';
const PORT = process.env.CI ? 3005 : 3000;

const config: PlaywrightTestConfig = {
retries: process.env.CI ? 1 : 0,
retries: process.env.CI ? 2 : 0,
testDir: './tests',
projects: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {devices} from '@playwright/test';
const PORT = process.env.CI ? 3006 : 3000;

const config: PlaywrightTestConfig = {
retries: process.env.CI ? 1 : 0,
retries: process.env.CI ? 2 : 0,
testDir: './tests',
projects: [
{
Expand Down
2 changes: 1 addition & 1 deletion examples/example-app-router/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {devices} from '@playwright/test';
const PORT = process.env.CI ? 3001 : 3000;

const config: PlaywrightTestConfig = {
retries: process.env.CI ? 1 : 0,
retries: process.env.CI ? 2 : 0,
testDir: './tests',
projects: [
{
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
"build-packages": "turbo run build --filter './packages/**' --filter '!./packages/swc-plugin-extractor'"
},
"devDependencies": {
"@lerna-lite/cli": "3.9.0",
"@lerna-lite/publish": "3.9.0",
"conventional-changelog-conventionalcommits": "7.0.2",
"@lerna-lite/cli": "^4.9.4",
"@lerna-lite/publish": "^4.9.4",
"conventional-changelog-conventionalcommits": "^8.0.0",
"turbo": "^2.4.4"
},
"pnpm": {
Expand Down
3 changes: 2 additions & 1 deletion packages/next-intl/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,11 @@
],
"dependencies": {
"@formatjs/intl-localematcher": "^0.5.4",
"@parcel/watcher": "^2.4.1",
"@swc/core": "^1.15.2",
"negotiator": "^1.0.0",
"next-intl-swc-plugin-extractor": "workspace:^",
"po-parser": "^1.0.2",
"po-parser": "^2.0.0",
"use-intl": "workspace:^"
},
"peerDependencies": {
Expand Down
3 changes: 2 additions & 1 deletion packages/next-intl/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ export default [
output: {
dir: 'dist/cjs/development',
format: 'cjs',
entryFileNames: '[name].cjs'
entryFileNames: '[name].cjs',
chunkFileNames: '[name]-[hash].cjs'
}
})
];
Loading