Skip to content

Commit ebc53d8

Browse files
committed
adjust docs, ignore more directories, fix tests
1 parent b90c991 commit ebc53d8

File tree

3 files changed

+132
-116
lines changed

3 files changed

+132
-116
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ If your project is split into multiple folders, you can provide an array of path
128128

129129
```tsx
130130
// Not using a `src` folder
131-
srcPath: ['./app', './components'],
131+
srcPath: ['./'],
132132
```
133133

134134
```tsx
@@ -141,6 +141,8 @@ srcPath: ['./src', '../ui'],
141141
srcPath: ['./src', './node_modules/@acme/components'],
142142
```
143143

144+
Note that the directories `node_modules`, `.next` and `.git` are automatically excluded from extraction, except for if they appear explicitly in the `srcPath` array.
145+
144146
If you want to provide messages along with your package, you can also extract them [manually](/docs/usage/extraction#manual).
145147

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

packages/next-intl/src/extractor/ExtractionCompiler.test.tsx

Lines changed: 119 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import fs from 'fs/promises';
22
import path from 'path';
33
import {beforeEach, describe, expect, it, vi} from 'vitest';
44
import ExtractionCompiler from './ExtractionCompiler.js';
5-
import SourceFileScanner from './source/SourceFileScanner.js';
65

76
const filesystem: {
87
project: {
98
src: Record<string, string>;
109
messages: Record<string, string> | undefined;
10+
node_modules?: Record<'@acme', Record<'ui', Record<string, string>>>;
11+
'.next'?: Record<string, Record<string, string>>;
12+
'.git'?: Record<string, Record<string, string>>;
1113
};
1214
} = {
1315
project: {
@@ -16,19 +18,19 @@ const filesystem: {
1618
}
1719
};
1820

19-
describe('json format', () => {
20-
beforeEach(() => {
21-
filesystem.project = {
22-
src: {},
23-
messages: {}
24-
};
25-
delete (filesystem as Record<string, unknown>).ui;
26-
fileTimestamps.clear();
27-
watchCallbacks.clear();
28-
mockWatchers.clear();
29-
vi.clearAllMocks();
30-
});
21+
beforeEach(() => {
22+
filesystem.project = {
23+
src: {},
24+
messages: {}
25+
};
26+
delete (filesystem as Record<string, unknown>).ui;
27+
fileTimestamps.clear();
28+
watchCallbacks.clear();
29+
mockWatchers.clear();
30+
vi.clearAllMocks();
31+
});
3132

33+
describe('json format', () => {
3234
function createCompiler() {
3335
return new ExtractionCompiler(
3436
{
@@ -314,28 +316,28 @@ describe('json format', () => {
314316
`
315317
);
316318
expect(filesystem).toMatchInlineSnapshot(`
317-
{
318-
"project": {
319-
"messages": {
320-
"de.json": "{
321-
"+YJVTi": "Hallo!"
322-
}",
323-
"en.json": "{
324-
"+YJVTi": "Hey!"
325-
}",
326-
},
327-
"src": {
328-
"Greeting.tsx": "
329-
import {useExtracted} from 'next-intl';
330-
function Greeting() {
331-
const t = useExtracted();
332-
return <div>{t('Hey!')}</div>;
333-
}
334-
",
319+
{
320+
"project": {
321+
"messages": {
322+
"de.json": "{
323+
"+YJVTi": "Hallo!"
324+
}",
325+
"en.json": "{
326+
"+YJVTi": "Hey!"
327+
}",
328+
},
329+
"src": {
330+
"Greeting.tsx": "
331+
import {useExtracted} from 'next-intl';
332+
function Greeting() {
333+
const t = useExtracted();
334+
return <div>{t('Hey!')}</div>;
335+
}
336+
",
337+
},
335338
},
336-
},
337-
}
338-
`);
339+
}
340+
`);
339341

340342
simulateManualFileEdit(
341343
'messages/de.json',
@@ -696,18 +698,6 @@ describe('json format', () => {
696698
});
697699

698700
describe('po format', () => {
699-
beforeEach(() => {
700-
filesystem.project = {
701-
src: {},
702-
messages: {}
703-
};
704-
delete (filesystem as Record<string, unknown>).ui;
705-
fileTimestamps.clear();
706-
watchCallbacks.clear();
707-
mockWatchers.clear();
708-
vi.clearAllMocks();
709-
});
710-
711701
function createCompiler() {
712702
return new ExtractionCompiler(
713703
{
@@ -1159,83 +1149,103 @@ msgstr "Hallo!"
11591149
});
11601150
});
11611151

1162-
describe('SourceFileScanner filtering', () => {
1152+
describe('`srcPath` filtering', () => {
11631153
beforeEach(() => {
1164-
filesystem.project = {
1165-
src: {},
1166-
messages: {}
1167-
};
1168-
delete (filesystem as Record<string, unknown>).ui;
1169-
fileTimestamps.clear();
1170-
watchCallbacks.clear();
1171-
mockWatchers.clear();
1172-
vi.clearAllMocks();
1173-
});
1154+
filesystem.project.src['Greeting.tsx'] = `
1155+
import {useExtracted} from 'next-intl';
1156+
import Panel from '@acme/ui/panel';
1157+
function Greeting() {
1158+
const t = useExtracted();
1159+
return <Panel>{t('Hey!')}</Panel>;
1160+
}
1161+
`;
11741162

1175-
it('skips node_modules unless requested', async () => {
1176-
(filesystem.project.src as Record<string, string>)['App.tsx'] =
1177-
'export default 1;';
1178-
(filesystem.project as Record<string, unknown>).node_modules = {
1179-
'@acme': {
1180-
'design-system': {
1181-
'Button.tsx': 'export default 1;'
1182-
}
1163+
function createNodeModule(moduleName: string) {
1164+
return `
1165+
import {useExtracted} from 'next-intl';
1166+
export default function Module({children}) {
1167+
const t = useExtracted();
1168+
return (
1169+
<div>
1170+
<h1>{t('${moduleName}')}</h1>
1171+
{children}
1172+
</div>
1173+
)
11831174
}
1184-
};
1185-
1186-
const files = await SourceFileScanner.getSourceFiles([path.join('/project')]);
1187-
1188-
expect(files).toContain(path.join('/project', 'src', 'App.tsx'));
1189-
expect(files).not.toContain(
1190-
path.join(
1191-
'/project',
1192-
'node_modules',
1193-
'@acme',
1194-
'design-system',
1195-
'Button.tsx'
1196-
)
1197-
);
1198-
});
1175+
`;
1176+
}
11991177

1200-
it('includes explicit node_modules paths', async () => {
1201-
const designSystemDir = path.join(
1202-
'/project',
1203-
'node_modules',
1204-
'@acme',
1205-
'design-system'
1206-
);
1207-
(filesystem.project as Record<string, unknown>).node_modules = {
1178+
filesystem.project.node_modules = {
12081179
'@acme': {
1209-
'design-system': {
1210-
'Button.tsx': 'export default 1;'
1180+
ui: {
1181+
'panel.tsx': createNodeModule('panel.source')
12111182
}
12121183
}
12131184
};
1214-
1215-
const files = await SourceFileScanner.getSourceFiles([designSystemDir]);
1216-
1217-
expect(files).toEqual([path.join(designSystemDir, 'Button.tsx')]);
1185+
filesystem.project['.next'] = {
1186+
build: {
1187+
'panel.tsx': createNodeModule('panel.compiled')
1188+
}
1189+
};
1190+
filesystem.project['.git'] = {
1191+
config: {
1192+
'panel.tsx': createNodeModule('panel.config')
1193+
}
1194+
};
12181195
});
12191196

1220-
it('skips node_modules in external directories', async () => {
1221-
const externalRoot = '/ui';
1222-
(filesystem as Record<string, unknown>).ui = {
1223-
'index.tsx': 'export default 1;',
1224-
node_modules: {
1225-
'@scope': {
1226-
'Widget.tsx': 'export default 1;'
1197+
function createCompiler(srcPath: string | Array<string>) {
1198+
return new ExtractionCompiler(
1199+
{
1200+
srcPath,
1201+
sourceLocale: 'en',
1202+
messages: {
1203+
path: './messages',
1204+
format: 'json',
1205+
locales: 'infer'
12271206
}
1228-
}
1229-
};
1207+
},
1208+
{isDevelopment: true, projectRoot: '/project'}
1209+
);
1210+
}
12301211

1231-
const files = await SourceFileScanner.getSourceFiles([
1232-
path.join('/project', '../ui')
1233-
]);
1212+
it('skips node_modules, .next and .git by default', async () => {
1213+
using compiler = createCompiler('./');
1214+
await compiler.compile(
1215+
'/project/src/Greeting.tsx',
1216+
filesystem.project.src['Greeting.tsx']
1217+
);
1218+
await waitForWriteFileCalls(1);
1219+
expect(vi.mocked(fs.writeFile).mock.calls).toMatchInlineSnapshot(`
1220+
[
1221+
[
1222+
"messages/en.json",
1223+
"{
1224+
"+YJVTi": "Hey!"
1225+
}",
1226+
],
1227+
]
1228+
`);
1229+
});
12341230

1235-
expect(files).toContain(path.join(externalRoot, 'index.tsx'));
1236-
expect(files).not.toContain(
1237-
path.join(externalRoot, 'node_modules', '@scope', 'Widget.tsx')
1231+
it('includes node_modules if explicitly requested', async () => {
1232+
using compiler = createCompiler(['./', './node_modules/@acme/ui']);
1233+
await compiler.compile(
1234+
'/project/src/Greeting.tsx',
1235+
filesystem.project.src['Greeting.tsx']
12381236
);
1237+
await waitForWriteFileCalls(1);
1238+
expect(vi.mocked(fs.writeFile).mock.calls).toMatchInlineSnapshot(`
1239+
[
1240+
[
1241+
"messages/en.json",
1242+
"{
1243+
"JwjlWH": "panel.source",
1244+
"+YJVTi": "Hey!"
1245+
}",
1246+
],
1247+
]
1248+
`);
12391249
});
12401250
});
12411251

packages/next-intl/src/extractor/source/SourceFileFilter.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
import path from 'path';
22

33
export default class SourceFileFilter {
4-
static readonly EXTENSIONS = ['ts', 'tsx', 'js', 'jsx'];
5-
private static readonly IGNORED_DIRECTORIES = ['node_modules', '.next'];
4+
public static readonly EXTENSIONS = ['ts', 'tsx', 'js', 'jsx'];
5+
6+
// Will not be entered, except if explicitly asked for
7+
// TODO: At some point we should infer these from .gitignore
8+
private static readonly IGNORED_DIRECTORIES = [
9+
'node_modules',
10+
'.next',
11+
'.git'
12+
];
613

714
static isSourceFile(filePath: string) {
815
const ext = path.extname(filePath);
@@ -32,10 +39,7 @@ export default class SourceFileFilter {
3239
);
3340
}
3441

35-
private static isWithinPath(
36-
targetPath: string,
37-
basePath: string
38-
): boolean {
42+
private static isWithinPath(targetPath: string, basePath: string): boolean {
3943
const relativePath = path.relative(basePath, targetPath);
4044
return relativePath === '' || !relativePath.startsWith('..');
4145
}

0 commit comments

Comments
 (0)