Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
37 changes: 0 additions & 37 deletions .github/workflows/prerelease-canary.yml

This file was deleted.

44 changes: 41 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
name: release

# A single workflow file so npm Trusted Publishing can be configured against it.
# The branch determines which release runs: `main` → stable (latest), `canary`
# → prerelease (canary). Only the job matching the pushed branch runs.
on:
push:
branches:
- main
- canary

jobs:
main:
stable:
name: stable (latest)
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: write
Expand All @@ -16,9 +22,9 @@ jobs:
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
cache: "pnpm"
cache: 'pnpm'
node-version: 20.x
registry-url: "https://registry.npmjs.org"
registry-url: 'https://registry.npmjs.org'
- run: npm install -g npm@latest # Trusted publishers
- run: pnpm install
- run: |
Expand All @@ -29,3 +35,35 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# No NODE_AUTH_TOKEN since we use Trusted Publishers

canary:
name: prerelease (canary)
if: github.ref == 'refs/heads/canary'
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
cache: 'pnpm'
node-version: 20.x
registry-url: 'https://registry.npmjs.org'
- run: npm install -g npm@latest # Trusted publishers
- run: pnpm install
- run: pnpm turbo run build --filter './packages/**'
- run: |
git config --global user.name "${{ github.actor }}"
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
# Change workspace dependencies from "workspace:^" to "workspace:"
- run: |
sed -i 's/"use-intl": "workspace:\^"/"use-intl": "workspace:"/' ./packages/next-intl/package.json && \
sed -i 's/"next-intl-swc-plugin-extractor": "workspace:\^"/"next-intl-swc-plugin-extractor": "workspace:"/' ./packages/next-intl/package.json && \
git commit -am "use fixed version"
- run: pnpm lerna publish 0.0.0-canary-${GITHUB_SHA::7} --no-git-reset --dist-tag canary --no-push --yes
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 }}
# No NODE_AUTH_TOKEN since we use Trusted Publishers
34 changes: 17 additions & 17 deletions packages/next-intl/src/extractor/catalog/CatalogManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type {
ExtractorConfig,
ExtractorMessage,
Locale,
SourceMessage
SourceExtractedMessage
} from '../types.js';
import {
compareReferences,
Expand Down Expand Up @@ -43,17 +43,17 @@ export default class CatalogManager implements Disposable {
*/
private sourceMessagesByFile: Map<
/* File path */ string,
Map</* ID */ string, Array<SourceMessage>>
Map</* ID */ string, Array<SourceExtractedMessage>>
> = new Map();

/**
* Reverse index for rebuilding aggregated messages without scanning all files.
* Contains the same `SourceMessage` arrays as `sourceMessagesByFile` and is
* Contains the same `SourceExtractedMessage` arrays as `sourceMessagesByFile` and is
* kept in sync with it.
*/
private sourceMessagesById: Map<
/* ID */ string,
Map</* File path */ string, Array<SourceMessage>>
Map</* File path */ string, Array<SourceExtractedMessage>>
> = new Map();

/**
Expand Down Expand Up @@ -308,8 +308,8 @@ export default class CatalogManager implements Disposable {

private async extractFile(
absoluteFilePath: string
): Promise<Array<SourceMessage> | undefined> {
let messages: Array<SourceMessage> = [];
): Promise<Array<SourceExtractedMessage> | undefined> {
let messages: Array<SourceExtractedMessage> = [];
try {
const content = await fs.readFile(absoluteFilePath, 'utf8');
let extraction: Awaited<ReturnType<typeof this.extractor.extract>>;
Expand All @@ -330,7 +330,7 @@ export default class CatalogManager implements Disposable {

private applyFileMessages(
absoluteFilePath: string,
messages: Array<SourceMessage>
messages: Array<SourceExtractedMessage>
): boolean {
const prevFileMessages = this.sourceMessagesByFile.get(absoluteFilePath);
const nextFileMessages = this.groupSourceMessagesById(messages);
Expand Down Expand Up @@ -378,9 +378,9 @@ export default class CatalogManager implements Disposable {
}

private groupSourceMessagesById(
messages: Array<SourceMessage>
): Map<string, Array<SourceMessage>> {
const result = new Map<string, Array<SourceMessage>>();
messages: Array<SourceExtractedMessage>
): Map<string, Array<SourceExtractedMessage>> {
const result = new Map<string, Array<SourceExtractedMessage>>();
for (const message of messages) {
const messagesById = result.get(message.id);
if (messagesById) {
Expand Down Expand Up @@ -429,7 +429,7 @@ export default class CatalogManager implements Disposable {
}

private mergeDescriptions(
messages: Array<SourceMessage>
messages: Array<SourceExtractedMessage>
): ExtractorMessage['description'] {
const sortedByReference = messages.toSorted((a, b) =>
compareReferences(a.reference, b.reference)
Expand All @@ -447,8 +447,8 @@ export default class CatalogManager implements Disposable {
}

private haveMessagesChangedForFile(
beforeMessages: Map<string, Array<SourceMessage>> | undefined,
afterMessages: Map<string, Array<SourceMessage>>
beforeMessages: Map<string, Array<SourceExtractedMessage>> | undefined,
afterMessages: Map<string, Array<SourceExtractedMessage>>
): boolean {
// If one exists and the other doesn't, there's a change
if (!beforeMessages) {
Expand Down Expand Up @@ -481,8 +481,8 @@ export default class CatalogManager implements Disposable {
}

private areSourceMessageArraysEqual(
messages1: Array<SourceMessage>,
messages2: Array<SourceMessage>
messages1: Array<SourceExtractedMessage>,
messages2: Array<SourceExtractedMessage>
): boolean {
return (
messages1.length === messages2.length &&
Expand All @@ -493,8 +493,8 @@ export default class CatalogManager implements Disposable {
}

private areSourceMessagesEqual(
msg1: SourceMessage,
msg2: SourceMessage
msg1: SourceExtractedMessage,
msg2: SourceExtractedMessage
): boolean {
return (
msg1.id === msg2.id &&
Expand Down
23 changes: 17 additions & 6 deletions packages/next-intl/src/extractor/extractor/MessageExtractor.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {createRequire} from 'module';
import path from 'path';
import {transform} from '@swc/core';
import type {SourceMessage} from '../types.js';
import type {SourceExtractedMessage} from '../types.js';
import {getDefaultProjectRoot, normalizePathToPosix} from '../utils.js';
import LRUCache from './LRUCache.js';

Expand All @@ -12,7 +12,7 @@ export default class MessageExtractor {
private projectRoot: string;
private sourceMap: boolean;
private compileCache = new LRUCache<{
messages: Array<SourceMessage>;
messages: Array<SourceExtractedMessage>;
code: string;
map?: string;
}>(750);
Expand All @@ -31,7 +31,7 @@ export default class MessageExtractor {
absoluteFilePath: string,
source: string
): Promise<{
messages: Array<SourceMessage>;
messages: Array<SourceExtractedMessage>;
code: string;
map?: string;
}> {
Expand Down Expand Up @@ -79,9 +79,20 @@ export default class MessageExtractor {

// TODO: Improve the typing of @swc/core
const output = (result as any).output as string;
const messages = JSON.parse(
JSON.parse(output).results
) as Array<SourceMessage>;
// The plugin emits a tagged union of extracted messages and
// `useTranslations` usages; this extractor only consumes the former.
const messages = (
JSON.parse(JSON.parse(output).results) as Array<
{type: 'extracted' | 'translation'} & SourceExtractedMessage
>
)
.filter((item) => item.type === 'extracted')
.map((item) => ({
id: item.id,
message: item.message,
description: item.description,
reference: item.reference
}));

const extractionResult = {
code: result.code,
Expand Down
2 changes: 1 addition & 1 deletion packages/next-intl/src/extractor/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export type ExtractorMessageReference = {
};

/** A single statically extracted source-code usage before any aggregation. */
export type SourceMessage = {
export type SourceExtractedMessage = {
id: string;
message: string;
description: string | null;
Expand Down
4 changes: 2 additions & 2 deletions packages/swc-plugin-extractor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
"name": "next-intl-swc-plugin-extractor",
"version": "4.13.0",
"sideEffects": false,
"author": "강동윤 <kdy1997.dev@gmail.com>",
"author": "Jan Amann <jan@amann.work>",
"contributors": [
"Jan Amann <jan@amann.work>"
"강동윤 <kdy1997.dev@gmail.com>"
],
"description": "SWC plugin for extracting inline messages.",
"license": "MIT",
Expand Down
Loading
Loading