From 82774d027c880706a95cdfead83eaf89e2129a40 Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Sat, 22 Jun 2019 16:32:58 +0200 Subject: [PATCH] refactor(i18n): convert to TS (#774) --- jest.d.ts | 1 + package.json | 1 + packages/actions-global/package.json | 4 +- packages/application-components/package.json | 10 +- .../application-shell-connectors/package.json | 4 +- packages/application-shell/package.json | 4 +- packages/browser-history/package.json | 4 +- packages/constants/package.json | 4 +- packages/i18n/{index.js => index.ts} | 0 packages/i18n/package.json | 15 +- .../async-locale-data/async-locale-data.js | 87 ---------- .../async-locale-data.spec.js | 160 ------------------ .../async-locale-data.spec.tsx | 115 +++++++++++++ .../async-locale-data/async-locale-data.tsx | 95 +++++++++++ .../async-locale-data/{index.js => index.ts} | 0 packages/i18n/src/{index.js => index.ts} | 0 .../i18n/src/{load-i18n.js => load-i18n.ts} | 37 +++- packages/i18n/src/locales.d.ts | 31 ++++ packages/i18n/src/{utils.js => utils.ts} | 9 +- packages/i18n/src/{version.js => version.ts} | 0 packages/i18n/tsconfig.declarations.json | 5 + packages/i18n/tsconfig.json | 5 + packages/l10n/package.json | 4 +- packages/permissions/package.json | 7 +- packages/sentry/package.json | 1 - 25 files changed, 317 insertions(+), 286 deletions(-) rename packages/i18n/{index.js => index.ts} (100%) delete mode 100644 packages/i18n/src/async-locale-data/async-locale-data.js delete mode 100644 packages/i18n/src/async-locale-data/async-locale-data.spec.js create mode 100644 packages/i18n/src/async-locale-data/async-locale-data.spec.tsx create mode 100644 packages/i18n/src/async-locale-data/async-locale-data.tsx rename packages/i18n/src/async-locale-data/{index.js => index.ts} (100%) rename packages/i18n/src/{index.js => index.ts} (100%) rename packages/i18n/src/{load-i18n.js => load-i18n.ts} (82%) create mode 100644 packages/i18n/src/locales.d.ts rename packages/i18n/src/{utils.js => utils.ts} (60%) rename packages/i18n/src/{version.js => version.ts} (100%) create mode 100644 packages/i18n/tsconfig.declarations.json create mode 100644 packages/i18n/tsconfig.json diff --git a/jest.d.ts b/jest.d.ts index 0c6cd7799a..a656475e9f 100644 --- a/jest.d.ts +++ b/jest.d.ts @@ -1,5 +1,6 @@ declare namespace jest { interface Matchers { + toHaveTextContent(selector: string): R; // The following matchers are part of https://www.npmjs.com/package/@commercetools/jest-enzyme-matchers toHaveProp(selector: string, expected?: unknown): R; toRender(selector: string): R; diff --git a/package.json b/package.json index b29948f4a4..e8b87b614f 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,7 @@ "prettier": "1.18.2", "puppeteer": "1.17.0", "rcfile": "1.0.3", + "react-testing-library": "7.0.1", "read-pkg-up": "6.0.0", "replace": "1.1.0", "rollup": "1.15.6", diff --git a/packages/actions-global/package.json b/packages/actions-global/package.json index 0e406268fb..578d3a0501 100644 --- a/packages/actions-global/package.json +++ b/packages/actions-global/package.json @@ -18,8 +18,8 @@ "publishConfig": { "access": "public" }, - "main": "dist/actions-global.cjs.js", - "module": "dist/actions-global.es.js", + "main": "./dist/actions-global.cjs.js", + "module": "./dist/actions-global.es.js", "files": [ "dist", "package.json", diff --git a/packages/application-components/package.json b/packages/application-components/package.json index 720341088b..dba6144900 100644 --- a/packages/application-components/package.json +++ b/packages/application-components/package.json @@ -18,8 +18,8 @@ "publishConfig": { "access": "public" }, - "main": "dist/application-components.cjs.js", - "module": "dist/application-components.es.js", + "main": "./dist/application-components.cjs.js", + "module": "./dist/application-components.es.js", "files": [ "dist", "materials/media-queries.css", @@ -50,14 +50,12 @@ "@commercetools-frontend/ui-kit": "9.9.0", "react": "16.8.6", "react-dom": "16.8.6", - "react-intl": "2.9.0", - "react-testing-library": "7.0.1" + "react-intl": "2.9.0" }, "peerDependencies": { "@commercetools-frontend/ui-kit": "^9.2", "react": "16.x", "react-dom": "16.x", - "react-intl": "2.x", - "react-testing-library": "6.x || 7.x" + "react-intl": "2.x" } } diff --git a/packages/application-shell-connectors/package.json b/packages/application-shell-connectors/package.json index 9c61f1498a..d3e0b4f695 100644 --- a/packages/application-shell-connectors/package.json +++ b/packages/application-shell-connectors/package.json @@ -18,8 +18,8 @@ "publishConfig": { "access": "public" }, - "main": "dist/application-shell-connectors.cjs.js", - "module": "dist/application-shell-connectors.es.js", + "main": "./dist/application-shell-connectors.cjs.js", + "module": "./dist/application-shell-connectors.es.js", "typings": "./dist/typings/index.d.ts", "types": "./dist/typings/index.d.ts", "files": [ diff --git a/packages/application-shell/package.json b/packages/application-shell/package.json index 6f10240106..385153328f 100644 --- a/packages/application-shell/package.json +++ b/packages/application-shell/package.json @@ -18,8 +18,8 @@ "publishConfig": { "access": "public" }, - "main": "dist/application-shell-index.cjs.js", - "module": "dist/application-shell-index.es.js", + "main": "./dist/application-shell-index.cjs.js", + "module": "./dist/application-shell-index.es.js", "files": [ "dist", "test-utils", diff --git a/packages/browser-history/package.json b/packages/browser-history/package.json index e2993b9128..be7043f0e2 100644 --- a/packages/browser-history/package.json +++ b/packages/browser-history/package.json @@ -18,8 +18,8 @@ "publishConfig": { "access": "public" }, - "main": "dist/browser-history.cjs.js", - "module": "dist/browser-history.es.js", + "main": "./dist/browser-history.cjs.js", + "module": "./dist/browser-history.es.js", "files": [ "dist", "package.json", diff --git a/packages/constants/package.json b/packages/constants/package.json index c6ac04f197..ef0fd219fe 100644 --- a/packages/constants/package.json +++ b/packages/constants/package.json @@ -18,8 +18,8 @@ "publishConfig": { "access": "public" }, - "main": "dist/constants.cjs.js", - "module": "dist/constants.es.js", + "main": "./dist/constants.cjs.js", + "module": "./dist/constants.es.js", "typings": "./dist/typings/index.d.ts", "types": "./dist/typings/index.d.ts", "files": [ diff --git a/packages/i18n/index.js b/packages/i18n/index.ts similarity index 100% rename from packages/i18n/index.js rename to packages/i18n/index.ts diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 650402cb8f..93403714d4 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -18,8 +18,10 @@ "publishConfig": { "access": "public" }, - "main": "dist/i18n-index.cjs.js", - "module": "dist/i18n-index.es.js", + "main": "./dist/i18n-index.cjs.js", + "module": "./dist/i18n-index.es.js", + "typings": "./dist/typings/index.d.ts", + "types": "./dist/typings/index.d.ts", "files": [ "dist", "package.json", @@ -29,10 +31,11 @@ "scripts": { "prepare": "./../../scripts/version.js replace", "prebuild": "rimraf dist/**", - "build": "yarn build:es && yarn build:cjs", - "build:cjs": "cross-env NODE_ENV=production rollup -c ../../rollup.config.js -f cjs ./src/index.js --dir ./dist --chunkFileNames i18n-[name]-[hash].cjs.js --entryFileNames i18n-[name].cjs.js", - "build:es": "cross-env NODE_ENV=production rollup -c ../../rollup.config.js -f es ./src/index.js --dir ./dist --chunkFileNames i18n-[name]-[hash].es.js --entryFileNames i18n-[name].es.js", - "build:es:watch": "yarn build:es -w" + "build": "yarn build:es && yarn build:cjs && yarn build:typings", + "build:cjs": "cross-env NODE_ENV=production rollup -c ../../rollup.config.js -f cjs ./src/index.ts --dir ./dist --chunkFileNames i18n-[name]-[hash].cjs.js --entryFileNames i18n-[name].cjs.js", + "build:es": "cross-env NODE_ENV=production rollup -c ../../rollup.config.js -f es ./src/index.ts --dir ./dist --chunkFileNames i18n-[name]-[hash].es.js --entryFileNames i18n-[name].es.js", + "build:es:watch": "yarn build:es -w", + "build:typings": "cross-env tsc -p tsconfig.declarations.json --emitDeclarationOnly --declarationDir dist/typings" }, "dependencies": { "@babel/runtime-corejs3": "7.4.5", diff --git a/packages/i18n/src/async-locale-data/async-locale-data.js b/packages/i18n/src/async-locale-data/async-locale-data.js deleted file mode 100644 index 917a193213..0000000000 --- a/packages/i18n/src/async-locale-data/async-locale-data.js +++ /dev/null @@ -1,87 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { reportErrorToSentry } from '@commercetools-frontend/sentry'; -import { extractLanguageTagFromLocale, mergeMessages } from '../utils'; -import loadI18n from '../load-i18n'; - -const getMessagesForLocale = (data, locale) => { - if (!data || !locale) return {}; - if (data[locale]) return data[locale]; - const fallbackLanguage = extractLanguageTagFromLocale(locale); - return data[fallbackLanguage]; -}; - -class AsyncLocaleData extends React.Component { - static displayName = 'AsyncLocaleData'; - static propTypes = { - children: PropTypes.func.isRequired, - // The locale is optional, which indicates that we don't know yet - // which locale data should be loaded. - // This is important in order to avoid loading different locales and - // therefore causing flashing of translated content on subsequent re-renders. - locale: PropTypes.string, - applicationMessages: PropTypes.oneOfType([ - PropTypes.func, - PropTypes.object, - ]), - }; - - state = { - isLoading: true, - locale: null, - messages: null, - }; - - isUnmounting = false; - - componentDidMount() { - if (this.props.locale) this.loadLocaleData(this.props.locale); - } - - componentDidUpdate(prevProps) { - if (this.props.locale && prevProps.locale !== this.props.locale) { - this.loadLocaleData(this.props.locale); - } - } - - componentWillUnmount() { - this.isUnmounting = true; - } - - loadLocaleData = async locale => { - let applicationMessagePromise = null; - - if (typeof this.props.applicationMessages === 'function') { - applicationMessagePromise = this.props.applicationMessages(locale); - } - - const messageFetchingPromises = applicationMessagePromise - ? [loadI18n(locale), applicationMessagePromise] - : [loadI18n(locale)]; - - try { - const [messages, applicationMessages] = await Promise.all( - messageFetchingPromises - ); - if (!this.isUnmounting) { - this.setState({ - isLoading: false, - locale, - messages: mergeMessages( - messages, - applicationMessages || - getMessagesForLocale(this.props.applicationMessages, locale) - ), - }); - } - } catch (error) { - reportErrorToSentry(error, {}); - } - }; - - render() { - return this.props.children(this.state); - } -} - -export default AsyncLocaleData; diff --git a/packages/i18n/src/async-locale-data/async-locale-data.spec.js b/packages/i18n/src/async-locale-data/async-locale-data.spec.js deleted file mode 100644 index 9743089362..0000000000 --- a/packages/i18n/src/async-locale-data/async-locale-data.spec.js +++ /dev/null @@ -1,160 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import { reportErrorToSentry } from '@commercetools-frontend/sentry'; -import loadI18n from '../load-i18n'; -import AsyncLocaleData from './async-locale-data'; - -jest.mock('@commercetools-frontend/sentry'); - -jest.mock('../load-i18n', () => jest.fn()); - -const ChildComponent = () =>
Child
; - -const createTestProps = props => ({ - children: jest.fn(() => ), - locale: 'en-US', - applicationMessages: { - en: { 'CustomApp.title': 'Title en' }, - }, - ...props, -}); - -describe('rendering', () => { - let wrapper; - let props; - describe('if there is an error', () => { - let error; - beforeEach(() => { - error = new Error('oh no!'); - loadI18n.mockClear(); - loadI18n.mockImplementation(jest.fn(() => Promise.reject(error))); - props = createTestProps(); - wrapper = shallow(); - }); - describe('when component is mounted', () => { - beforeEach(() => { - reportErrorToSentry.mockClear(); - wrapper.instance().componentDidMount(); - }); - it('should report the error to sentry', () => { - expect(reportErrorToSentry).toHaveBeenCalledWith(error, {}); - }); - }); - }); - - describe('if there is no error', () => { - beforeEach(() => { - loadI18n.mockClear(); - loadI18n.mockImplementation( - jest.fn(() => Promise.resolve({ title: 'Title en' })) - ); - props = createTestProps(); - wrapper = shallow(); - }); - describe('when component is mounted', () => { - beforeEach(() => { - wrapper.instance().componentDidMount(); - }); - - it('should call `loadIntl`', () => { - expect(loadI18n).toHaveBeenCalled(); - }); - it('should call `children` with state', () => { - expect(props.children).toHaveBeenCalledWith({ - isLoading: false, - locale: 'en-US', - messages: { title: 'Title en', ...props.applicationMessages.en }, - }); - }); - }); - - describe('when component is updated but with same locale', () => { - beforeEach(() => { - wrapper.instance().componentDidUpdate({ - locale: 'en-in', - }); - }); - - it('should call `loadIntl`', () => { - expect(loadI18n).toHaveBeenCalledTimes(1); - }); - }); - describe('when component is updated with new locale', () => { - beforeEach(() => { - loadI18n.mockClear(); - props = createTestProps({ - locale: 'de-AT', - }); - wrapper = shallow(); - wrapper.instance().componentDidUpdate({ - locale: 'en-CA', - }); - }); - - it('should call `loadIntl`', () => { - expect(loadI18n).toHaveBeenCalled(); - }); - - it('should call `loadIntl` with `de`', () => { - expect(loadI18n).toHaveBeenCalledWith('de-AT'); - }); - }); - }); - - describe('when applicationMessages is a function', () => { - beforeEach(() => { - loadI18n.mockClear(); - loadI18n.mockClear(); - loadI18n.mockImplementation( - jest.fn(() => Promise.resolve({ title: 'Title en' })) - ); - props = createTestProps({ - locale: 'en-CA', - applicationMessages: jest - .fn(() => Promise.resolve({ 'CustomApp.title': 'New title en' })) - .mockName('applicationMessages'), - }); - wrapper = shallow(); - }); - - describe('when component is mounted', () => { - beforeEach(() => { - wrapper.instance().componentDidMount(); - }); - - it('should call `applicationMessagePromise`', () => { - expect(props.applicationMessages).toHaveBeenCalled(); - }); - - it('should call `applicationMessagePromise` with `en-CA`', () => { - expect(props.applicationMessages).toHaveBeenCalledWith('en-CA'); - }); - - it('should call `children` with state', () => { - expect(props.children).toHaveBeenCalledWith({ - isLoading: false, - locale: 'en-CA', - messages: { title: 'Title en', 'CustomApp.title': 'New title en' }, - }); - }); - }); - }); - - describe('when locale is not defined', () => { - beforeEach(() => { - loadI18n.mockClear(); - props = createTestProps({ locale: null }); - wrapper = shallow(); - }); - it('should not call `loadIntl`', () => { - expect(loadI18n).not.toHaveBeenCalled(); - }); - it('should call `children` with state', () => { - expect(props.children).toHaveBeenCalledWith({ - isLoading: true, - locale: null, - messages: null, - }); - }); - }); -}); diff --git a/packages/i18n/src/async-locale-data/async-locale-data.spec.tsx b/packages/i18n/src/async-locale-data/async-locale-data.spec.tsx new file mode 100644 index 0000000000..bce7b14083 --- /dev/null +++ b/packages/i18n/src/async-locale-data/async-locale-data.spec.tsx @@ -0,0 +1,115 @@ +import { mocked } from 'ts-jest/utils'; +import React from 'react'; +import { reportErrorToSentry } from '@commercetools-frontend/sentry'; +import { render, wait } from 'react-testing-library'; +import loadI18n from '../load-i18n'; +import AsyncLocaleData, { + State, + Props, + TMessageTranslations, +} from './async-locale-data'; + +jest.mock('@commercetools-frontend/sentry'); + +jest.mock('../load-i18n'); + +type ChildComponentProps = { + locale?: string; + messages?: TMessageTranslations; +}; +const ChildComponent = (props: ChildComponentProps) => { + if (props.locale && props.messages) { + return ( + <> +
{`Locale: ${props.locale}`}
+
{`Messages: ${props.messages['CustomApp.title']}`}
+ + ); + } + return
{'Nothing'}
; +}; + +const createTestProps = (props: Partial = {}) => ({ + locale: 'en-US', + applicationMessages: { + en: { 'CustomApp.title': 'Custom title en' }, + }, + // eslint-disable-next-line react/display-name + children: (state: State) => , + ...props, +}); + +describe('rendering', () => { + let props: Props; + describe('if there is an error', () => { + let error: Error; + beforeEach(() => { + error = new Error('oh no!'); + mocked(loadI18n).mockClear(); + mocked(loadI18n).mockImplementation(jest.fn(() => Promise.reject(error))); + props = createTestProps(); + }); + it('should report the error to sentry', async () => { + const { container } = render(); + await wait(() => { + expect(container).toHaveTextContent('Nothing'); + expect(reportErrorToSentry).toHaveBeenCalledWith(error, {}); + }); + }); + }); + + describe('if there is no error', () => { + beforeEach(() => { + mocked(loadI18n).mockClear(); + mocked(loadI18n).mockImplementation( + jest.fn(() => Promise.resolve({ title: 'Title en' })) + ); + props = createTestProps(); + }); + it('should render children with state', async () => { + const { container } = render(); + await wait(() => { + expect(container).toHaveTextContent('Locale: en-US'); + expect(container).toHaveTextContent('Messages: Custom title en'); + }); + }); + }); + + describe('when applicationMessages is a function', () => { + beforeEach(() => { + mocked(loadI18n).mockClear(); + mocked(loadI18n).mockClear(); + mocked(loadI18n).mockImplementation( + jest.fn(() => Promise.resolve({ title: 'Title en' })) + ); + props = createTestProps({ + locale: 'en-CA', + applicationMessages: jest + .fn(() => Promise.resolve({ 'CustomApp.title': 'New title en' })) + .mockName('applicationMessages'), + }); + }); + it('should render children with state', async () => { + const { container } = render(); + await wait(() => { + expect(container).toHaveTextContent('Locale: en-CA'); + expect(container).toHaveTextContent('Messages: New title en'); + }); + }); + }); + + describe('when locale is not defined', () => { + beforeEach(() => { + mocked(loadI18n).mockClear(); + props = createTestProps(); + }); + it('should render without values', async () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { locale, ...withoutLocale } = props; + const { container } = render(); + await wait(() => { + expect(container).toHaveTextContent('Nothing'); + }); + }); + }); +}); diff --git a/packages/i18n/src/async-locale-data/async-locale-data.tsx b/packages/i18n/src/async-locale-data/async-locale-data.tsx new file mode 100644 index 0000000000..ca25ad7821 --- /dev/null +++ b/packages/i18n/src/async-locale-data/async-locale-data.tsx @@ -0,0 +1,95 @@ +import React from 'react'; +import { reportErrorToSentry } from '@commercetools-frontend/sentry'; +import { extractLanguageTagFromLocale, mergeMessages } from '../utils'; +import loadI18n from '../load-i18n'; + +export type TMessageTranslations = { + [key: string]: string; +}; +export type TMessages = { + [key: string]: TMessageTranslations; +}; +export type TMessagesAsync = (locale: string) => Promise; +export type State = { + isLoading: boolean; + locale?: string; + messages?: TMessageTranslations; +}; +export type Props = { + // The locale is optional, which indicates that we don't know yet + // which locale data should be loaded. + // This is important in order to avoid loading different locales and + // therefore causing flashing of translated content on subsequent re-renders. + locale?: string; + applicationMessages: TMessages | TMessagesAsync; + children: (state: State) => React.ReactNode; +}; + +const getMessagesForLocale = (data?: TMessages, locale?: string) => { + if (!data || !locale) return {}; + if (data[locale]) return data[locale]; + const fallbackLanguage = extractLanguageTagFromLocale(locale); + return data[fallbackLanguage]; +}; + +const loadApplicationMessages = async ( + applicationMessagesOrAsyncFunction: TMessages | TMessagesAsync, + locale: string +): Promise => { + if (typeof applicationMessagesOrAsyncFunction === 'function') { + return applicationMessagesOrAsyncFunction(locale); + } + return getMessagesForLocale(applicationMessagesOrAsyncFunction, locale); +}; + +class AsyncLocaleData extends React.Component { + static displayName = 'AsyncLocaleData'; + + state = { + isLoading: true, + locale: undefined, + messages: undefined, + }; + + isUnmounting = false; + + componentDidMount() { + if (this.props.locale) this.loadLocaleData(this.props.locale); + } + + componentDidUpdate(prevProps: Props) { + if (this.props.locale && prevProps.locale !== this.props.locale) { + this.loadLocaleData(this.props.locale); + } + } + + componentWillUnmount() { + this.isUnmounting = true; + } + + loadLocaleData = async (locale: string) => { + try { + if (!this.isUnmounting) { + const messages = await loadI18n(locale); + const applicationMessages = await loadApplicationMessages( + this.props.applicationMessages, + locale + ); + + this.setState({ + isLoading: false, + locale, + messages: mergeMessages(messages, applicationMessages), + }); + } + } catch (error) { + reportErrorToSentry(error, {}); + } + }; + + render() { + return this.props.children(this.state); + } +} + +export default AsyncLocaleData; diff --git a/packages/i18n/src/async-locale-data/index.js b/packages/i18n/src/async-locale-data/index.ts similarity index 100% rename from packages/i18n/src/async-locale-data/index.js rename to packages/i18n/src/async-locale-data/index.ts diff --git a/packages/i18n/src/index.js b/packages/i18n/src/index.ts similarity index 100% rename from packages/i18n/src/index.js rename to packages/i18n/src/index.ts diff --git a/packages/i18n/src/load-i18n.js b/packages/i18n/src/load-i18n.ts similarity index 82% rename from packages/i18n/src/load-i18n.js rename to packages/i18n/src/load-i18n.ts index e0ead0b06e..ae81cc7916 100644 --- a/packages/i18n/src/load-i18n.js +++ b/packages/i18n/src/load-i18n.ts @@ -1,4 +1,5 @@ import { addLocaleData } from 'react-intl'; +import moment from 'moment'; import { extractLanguageTagFromLocale, mergeMessages, @@ -6,7 +7,29 @@ import { mapLocaleToIntlLocale, } from './utils'; -const getReactIntlChunkImport = locale => { +type ReactIntlImportData = { + default: ReactIntl.Locale | ReactIntl.Locale[]; +}; +type MomentImportData = { + default: moment.Locale; +}; +type UIKitImportData = { + default: { + [key: string]: string; + }; +}; +type AppKitImportData = { + default: { + [key: string]: string; + }; +}; +type MergedMessages = { + [key: string]: string; +}; + +const getReactIntlChunkImport = ( + locale: string +): Promise => { // NOTE: react-intl only has locale data for language tags const language = extractLanguageTagFromLocale(locale); switch (language) { @@ -33,7 +56,7 @@ const getReactIntlChunkImport = locale => { } }; -const getMomentChunkImport = locale => { +const getMomentChunkImport = (locale: string): Promise => { const momentLocale = mapLocaleToMomentLocale(locale); switch (momentLocale) { case 'de': @@ -59,7 +82,7 @@ const getMomentChunkImport = locale => { } }; -const getUiKitChunkImport = locale => { +const getUiKitChunkImport = (locale: string): Promise => { const intlLocale = mapLocaleToIntlLocale(locale); switch (intlLocale) { case 'de': @@ -85,7 +108,7 @@ const getUiKitChunkImport = locale => { } }; -const getAppKitChunkImport = locale => { +const getAppKitChunkImport = (locale: string): Promise => { const intlLocale = mapLocaleToIntlLocale(locale); switch (intlLocale) { case 'de': @@ -113,7 +136,9 @@ const getAppKitChunkImport = locale => { // Use default (lazy) so that we will receive one chunk per // locale. https://webpack.js.org/api/module-methods/#import- -export default async function loadI18n(locale) { +export default async function loadI18n( + locale: string +): Promise { // Load react-intl localizations const reactIntlChunkImport = await getReactIntlChunkImport(locale); addLocaleData(reactIntlChunkImport.default); @@ -130,7 +155,7 @@ export default async function loadI18n(locale) { // Prefer loading `default` (for ESM bundles) and // fall back to normal import (for CJS bundles). return mergeMessages( - uiKitChunkImport, + uiKitChunkImport.default || uiKitChunkImport, appKitChunkImport.default || appKitChunkImport ); } diff --git a/packages/i18n/src/locales.d.ts b/packages/i18n/src/locales.d.ts new file mode 100644 index 0000000000..36518b8c2c --- /dev/null +++ b/packages/i18n/src/locales.d.ts @@ -0,0 +1,31 @@ +/* eslint-disable import/no-duplicates */ + +declare module 'moment/locale/de' { + import moment from 'moment'; + const data: moment.Locale; + export = data; +} + +declare module 'moment/locale/es' { + import moment from 'moment'; + const data: moment.Locale; + export = data; +} + +declare module 'moment/locale/fr' { + import moment from 'moment'; + const data: moment.Locale; + export = data; +} + +declare module 'moment/locale/zh-cn' { + import moment from 'moment'; + const data: moment.Locale; + export = data; +} + +declare module 'moment/locale/en-gb' { + import moment from 'moment'; + const data: moment.Locale; + export = data; +} diff --git a/packages/i18n/src/utils.js b/packages/i18n/src/utils.ts similarity index 60% rename from packages/i18n/src/utils.js rename to packages/i18n/src/utils.ts index d1269e3a89..b9620b49ec 100644 --- a/packages/i18n/src/utils.js +++ b/packages/i18n/src/utils.ts @@ -1,9 +1,10 @@ -export const extractLanguageTagFromLocale = locale => +export const extractLanguageTagFromLocale = (locale: string) => locale.includes('-') ? locale.split('-')[0] : locale; -export const mergeMessages = (...messages) => Object.assign({}, ...messages); +export const mergeMessages = (...messages: { [key: string]: string }[]) => + Object.assign({}, ...messages); -export const mapLocaleToMomentLocale = locale => { +export const mapLocaleToMomentLocale = (locale: string) => { if (locale.startsWith('de')) return 'de'; if (locale.startsWith('es')) return 'es'; if (locale.startsWith('fr')) return 'fr'; @@ -11,7 +12,7 @@ export const mapLocaleToMomentLocale = locale => { return 'en-gb'; }; -export const mapLocaleToIntlLocale = locale => { +export const mapLocaleToIntlLocale = (locale: string) => { if (locale.startsWith('de')) return 'de'; if (locale.startsWith('es')) return 'es'; if (locale.startsWith('fr')) return 'fr-FR'; diff --git a/packages/i18n/src/version.js b/packages/i18n/src/version.ts similarity index 100% rename from packages/i18n/src/version.js rename to packages/i18n/src/version.ts diff --git a/packages/i18n/tsconfig.declarations.json b/packages/i18n/tsconfig.declarations.json new file mode 100644 index 0000000000..3055f440d9 --- /dev/null +++ b/packages/i18n/tsconfig.declarations.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { "declaration": true, "isolatedModules": false }, + "exclude": ["**/*.spec.ts", "**/*.spec.tsx"] +} diff --git a/packages/i18n/tsconfig.json b/packages/i18n/tsconfig.json new file mode 100644 index 0000000000..4695f772d9 --- /dev/null +++ b/packages/i18n/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.json", + "files": ["./src/index.ts"], + "include": ["./src/locales.d.ts"] +} diff --git a/packages/l10n/package.json b/packages/l10n/package.json index 050ea8fd5d..54bbe376fa 100644 --- a/packages/l10n/package.json +++ b/packages/l10n/package.json @@ -18,8 +18,8 @@ "publishConfig": { "access": "public" }, - "main": "dist/l10n-index.cjs.js", - "module": "dist/l10n-index.es.js", + "main": "./dist/l10n-index.cjs.js", + "module": "./dist/l10n-index.es.js", "typings": "./dist/typings/index.d.ts", "types": "./dist/typings/index.d.ts", "files": [ diff --git a/packages/permissions/package.json b/packages/permissions/package.json index 52689cc4b7..ac236247ee 100644 --- a/packages/permissions/package.json +++ b/packages/permissions/package.json @@ -18,8 +18,8 @@ "publishConfig": { "access": "public" }, - "main": "dist/permissions.cjs.js", - "module": "dist/permissions.es.js", + "main": "./dist/permissions.cjs.js", + "module": "./dist/permissions.es.js", "typings": "./dist/typings/index.d.ts", "types": "./dist/typings/index.d.ts", "files": [ @@ -47,8 +47,7 @@ "warning": "4.0.3" }, "devDependencies": { - "react": "16.8.6", - "react-testing-library": "7.0.1" + "react": "16.8.6" }, "peerDependencies": { "react": "16.x" diff --git a/packages/sentry/package.json b/packages/sentry/package.json index 2b04e60275..e2e4ca6ce7 100644 --- a/packages/sentry/package.json +++ b/packages/sentry/package.json @@ -44,7 +44,6 @@ }, "devDependencies": { "react": "16.8.6", - "react-testing-library": "7.0.1", "sentry-testkit": "2.2.1", "wait-for-expect": "1.2.0" },