Skip to content

Commit

Permalink
feat: Warn for invalid namespace keys (#106)
Browse files Browse the repository at this point in the history
  • Loading branch information
amannn committed Apr 28, 2022
1 parent 61303a7 commit e86ab28
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 2 deletions.
1 change: 1 addition & 0 deletions packages/use-intl/src/IntlError.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export enum IntlErrorCode {
MISSING_FORMAT = 'MISSING_FORMAT',
INSUFFICIENT_PATH = 'INSUFFICIENT_PATH',
INVALID_MESSAGE = 'INVALID_MESSAGE',
INVALID_KEY = 'INVALID_KEY',
FORMATTING_ERROR = 'FORMATTING_ERROR'
}

Expand Down
15 changes: 13 additions & 2 deletions packages/use-intl/src/IntlProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, {ReactNode} from 'react';
import React, {ReactNode, useEffect} from 'react';
import AbstractIntlMessages from './AbstractIntlMessages';
import Formats from './Formats';
import IntlContext from './IntlContext';
import {RichTranslationValues} from './TranslationValues';
import validateMessages from './validateMessages';
import {IntlError} from '.';

type Props = {
Expand Down Expand Up @@ -64,11 +65,21 @@ export default function IntlProvider({
children,
onError = defaultOnError,
getMessageFallback = defaultGetMessageFallback,
messages,
...contextValues
}: Props) {
if (__DEV__) {
// eslint-disable-next-line react-hooks/rules-of-hooks
useEffect(() => {
if (messages) {
validateMessages(messages, onError);
}
}, [messages, onError]);
}

return (
<IntlContext.Provider
value={{...contextValues, onError, getMessageFallback}}
value={{...contextValues, messages, onError, getMessageFallback}}
>
{children}
</IntlContext.Provider>
Expand Down
42 changes: 42 additions & 0 deletions packages/use-intl/src/validateMessages.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import AbstractIntlMessages from './AbstractIntlMessages';
import IntlError, {IntlErrorCode} from './IntlError';

function validateMessagesSegment(
messages: AbstractIntlMessages,
invalidKeyLabels: Array<string>,
parentPath?: string
) {
Object.entries(messages).forEach(([key, messageOrMessages]) => {
if (key.includes('.')) {
let keyLabel = key;
if (parentPath) keyLabel += ` (at ${parentPath})`;
invalidKeyLabels.push(keyLabel);
}
if (typeof messageOrMessages === 'object') {
validateMessagesSegment(
messageOrMessages,
invalidKeyLabels,
[parentPath, key].filter((part) => part != null).join('.')
);
}
});
}

export default function validateMessages(
messages: AbstractIntlMessages,
onError: (error: IntlError) => void
) {
const invalidKeyLabels: Array<string> = [];
validateMessagesSegment(messages, invalidKeyLabels);

if (invalidKeyLabels.length > 0) {
onError(
new IntlError(
IntlErrorCode.INVALID_KEY,
`Namespace keys can not contain the character "." as this is used to express nesting. Please remove it or replace it with another character.\n\nInvalid ${
invalidKeyLabels.length === 1 ? 'key' : 'keys'
}: ${invalidKeyLabels.join(', ')}`
)
);
}
}
2 changes: 2 additions & 0 deletions packages/use-intl/test/useLocale.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import {render, screen} from '@testing-library/react';
import React from 'react';
import {IntlProvider, useLocale} from '../src';

(global as any).__DEV__ = true;

it('returns the current locale', () => {
function Component() {
return <>{useLocale()}</>;
Expand Down
2 changes: 2 additions & 0 deletions packages/use-intl/test/useTimeZone.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import {render, screen} from '@testing-library/react';
import React from 'react';
import {IntlProvider, useTimeZone} from '../src';

(global as any).__DEV__ = true;

it('returns the time zone when it is configured', () => {
function Component() {
return <>{useTimeZone()}</>;
Expand Down
20 changes: 20 additions & 0 deletions packages/use-intl/test/useTranslations.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,26 @@ describe('error handling', () => {
expect(error.code).toBe(IntlErrorCode.INVALID_MESSAGE);
screen.getByText('rich');
});

it('warns for invalid namespace keys', () => {
const onError = jest.fn();

render(
<IntlProvider
locale="en"
messages={{'a.b': {'c.d': 'ABCD', e: 'E'}, f: {g: {'h.j': 'FGHJ'}}}}
onError={onError}
>
<span />
</IntlProvider>
);

const error: IntlError = onError.mock.calls[0][0];
expect(error.message).toBe(
'INVALID_KEY: Namespace keys can not contain the character "." as this is used to express nesting. Please remove it or replace it with another character.\n\nInvalid keys: a.b, c.d (at a.b), h.j (at f.g)'
);
expect(error.code).toBe(IntlErrorCode.INVALID_KEY);
});
});

describe('global formats', () => {
Expand Down

1 comment on commit e86ab28

@vercel
Copy link

@vercel vercel bot commented on e86ab28 Apr 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

next-intl – ./

next-intl-git-main-amann.vercel.app
next-intl-amann.vercel.app
next-intl-docs.vercel.app

Please sign in to comment.