Skip to content

Commit 5a7f7be

Browse files
authored
fix: Move locale validation to defineRouting (#1560)
1 parent c8eb4ad commit 5a7f7be

File tree

5 files changed

+92
-88
lines changed

5 files changed

+92
-88
lines changed

packages/next-intl/src/routing/defineRouting.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type {
55
Locales,
66
Pathnames
77
} from './types.tsx';
8+
import validateLocales from './validateLocales.tsx';
89

910
export default function defineRouting<
1011
const AppLocales extends Locales,
@@ -19,5 +20,8 @@ export default function defineRouting<
1920
AppDomains
2021
>
2122
) {
23+
if (process.env.NODE_ENV !== 'production') {
24+
validateLocales(config.locales);
25+
}
2226
return config;
2327
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
2+
import validateLocales from './validateLocales.tsx';
3+
4+
describe('accepts valid formats', () => {
5+
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
6+
7+
beforeEach(() => {
8+
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
9+
});
10+
11+
afterEach(() => {
12+
consoleErrorSpy.mockRestore();
13+
});
14+
15+
it.each([
16+
'en',
17+
'en-US',
18+
'EN-US',
19+
'en-us',
20+
'en-GB',
21+
'zh-Hans-CN',
22+
'es-419',
23+
'en-Latn',
24+
'zh-Hans',
25+
'en-US-u-ca-buddhist',
26+
'en-x-private1',
27+
'en-US-u-nu-thai',
28+
'ar-u-nu-arab',
29+
'en-t-m0-true',
30+
'zh-Hans-CN-x-private1-private2',
31+
'en-US-u-ca-gregory-nu-latn',
32+
'en-US-x-usd',
33+
34+
// Somehow tolerated by Intl.Locale
35+
'english'
36+
])('accepts: %s', (locale) => {
37+
validateLocales([locale]);
38+
expect(consoleErrorSpy).not.toHaveBeenCalled();
39+
});
40+
});
41+
42+
describe('warns for invalid formats', () => {
43+
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
44+
45+
beforeEach(() => {
46+
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
47+
});
48+
49+
afterEach(() => {
50+
consoleErrorSpy.mockRestore();
51+
});
52+
53+
it.each([
54+
'en_US',
55+
'en-',
56+
'e-US',
57+
'en-USA',
58+
'und',
59+
'123',
60+
'-en',
61+
'en--US',
62+
'toolongstring',
63+
'en-US-',
64+
'@#$',
65+
'en US',
66+
'en.US'
67+
])('rejects: %s', (locale) => {
68+
validateLocales([locale]);
69+
expect(consoleErrorSpy).toHaveBeenCalled();
70+
});
71+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type {Locales} from './types.tsx';
2+
3+
export default function validateLocales(locales: Locales) {
4+
for (const locale of locales) {
5+
try {
6+
const constructed = new Intl.Locale(locale);
7+
if (!constructed.language) {
8+
throw new Error('Language is required');
9+
}
10+
} catch {
11+
console.error(
12+
`Found invalid locale within provided \`locales\`: "${locale}"\nPlease ensure you're using a valid Unicode locale identifier (e.g. "en-US").`
13+
);
14+
}
15+
}
16+
}
+1-70
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
1+
import {it} from 'vitest';
22
import hasLocale from './hasLocale.tsx';
33

44
it('narrows down the type', () => {
@@ -24,72 +24,3 @@ it('can be called with a non-matching narrow candidate', () => {
2424
candidate satisfies never;
2525
}
2626
});
27-
28-
describe('accepts valid formats', () => {
29-
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
30-
31-
beforeEach(() => {
32-
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
33-
});
34-
35-
afterEach(() => {
36-
consoleErrorSpy.mockRestore();
37-
});
38-
39-
it.each([
40-
'en',
41-
'en-US',
42-
'EN-US',
43-
'en-us',
44-
'en-GB',
45-
'zh-Hans-CN',
46-
'es-419',
47-
'en-Latn',
48-
'zh-Hans',
49-
'en-US-u-ca-buddhist',
50-
'en-x-private1',
51-
'en-US-u-nu-thai',
52-
'ar-u-nu-arab',
53-
'en-t-m0-true',
54-
'zh-Hans-CN-x-private1-private2',
55-
'en-US-u-ca-gregory-nu-latn',
56-
'en-US-x-usd',
57-
58-
// Somehow tolerated by Intl.Locale
59-
'english'
60-
])('accepts: %s', (locale) => {
61-
expect(hasLocale([locale] as const, locale)).toBe(true);
62-
expect(consoleErrorSpy).not.toHaveBeenCalled();
63-
});
64-
});
65-
66-
describe('warns for invalid formats', () => {
67-
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
68-
69-
beforeEach(() => {
70-
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
71-
});
72-
73-
afterEach(() => {
74-
consoleErrorSpy.mockRestore();
75-
});
76-
77-
it.each([
78-
'en_US',
79-
'en-',
80-
'e-US',
81-
'en-USA',
82-
'und',
83-
'123',
84-
'-en',
85-
'en--US',
86-
'toolongstring',
87-
'en-US-',
88-
'@#$',
89-
'en US',
90-
'en.US'
91-
])('rejects: %s', (locale) => {
92-
hasLocale([locale] as const, locale);
93-
expect(consoleErrorSpy).toHaveBeenCalled();
94-
});
95-
});

packages/use-intl/src/core/hasLocale.tsx

-18
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,11 @@ import type {Locale} from './AppConfig.tsx';
33
/**
44
* Checks if a locale exists in a list of locales.
55
*
6-
* Additionally, in development, the provided locales are validated to
7-
* ensure they follow the Unicode language identifier standard.
8-
*
96
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale
107
*/
118
export default function hasLocale<LocaleType extends Locale>(
129
locales: ReadonlyArray<LocaleType>,
1310
candidate?: string | null
1411
): candidate is LocaleType {
15-
if (process.env.NODE_ENV !== 'production') {
16-
for (const locale of locales) {
17-
try {
18-
const constructed = new Intl.Locale(locale);
19-
if (!constructed.language) {
20-
throw new Error('Language is required');
21-
}
22-
} catch {
23-
console.error(
24-
`Found invalid locale within provided \`locales\`: "${locale}"\nPlease ensure you're using a valid Unicode locale identifier (e.g. "en-US").`
25-
);
26-
}
27-
}
28-
}
29-
3012
return locales.includes(candidate as LocaleType);
3113
}

0 commit comments

Comments
 (0)