Skip to content

Commit

Permalink
refactor: Enable TypeScript strict mode (#1007)
Browse files Browse the repository at this point in the history
* refactor: Enable TypeScript strict mode

* refactor: Fix i18n === null case

* refactor: Fix tests
  • Loading branch information
isaachinman authored Mar 2, 2021
1 parent a522402 commit 365921d
Show file tree
Hide file tree
Showing 15 changed files with 125 additions and 45 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"@babel/preset-react": "^7.10.4",
"@babel/preset-typescript": "^7.10.4",
"@testing-library/react": "^11.2.5",
"@types/i18next-fs-backend": "^1.0.0",
"@types/jest": "^24.0.16",
"@types/node": "^14.14.19",
"@types/react": "^16.8.4",
Expand Down Expand Up @@ -101,6 +102,7 @@
"typescript": "^4.1.3"
},
"dependencies": {
"@types/hoist-non-react-statics": "^3.3.1",
"core-js": "^3",
"hoist-non-react-statics": "^3.2.0",
"i18next": "^19.8.4",
Expand Down
19 changes: 14 additions & 5 deletions src/appWithTranslation.client.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jest.mock('fs', () => ({
readdirSync: jest.fn(),
}))

const DummyI18nextProvider = ({ children }) => (
const DummyI18nextProvider: React.FC = ({ children }) => (
<>{children}</>
)

Expand All @@ -30,9 +30,14 @@ const renderComponent = () =>
pageProps={{
_nextI18Next: {
initialLocale: 'en',
userConfig: {},
userConfig: {
i18n: {
defaultLocale: 'en',
locales: ['en', 'de'],
},
},
},
}}
} as any}
/>
)

Expand All @@ -54,14 +59,18 @@ describe('appWithTranslation', () => {
<div>Hello world</div>
), {
configOverride: 'custom-value',
i18n: {
defaultLocale: 'en',
locales: ['en', 'de'],
},
} as any)
render(
<DummyAppConfigOverride
pageProps={{
_nextI18Next: {
initialLocale: 'en',
},
}}
} as any}
/>
)
const [args] = (I18nextProvider as jest.Mock).mock.calls
Expand All @@ -82,7 +91,7 @@ describe('appWithTranslation', () => {
initialLocale: 'en',
userConfig: null,
},
}}
} as any}
/>
)
).toThrow('appWithTranslation was called without a next-i18next config')
Expand Down
10 changes: 8 additions & 2 deletions src/appWithTranslation.server.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jest.mock('fs', () => ({
readdirSync: jest.fn(),
}))

const DummyI18nextProvider = ({ children }) => (
const DummyI18nextProvider: React.FC = ({ children }) => (
<>{children}</>
)

Expand All @@ -34,8 +34,14 @@ const renderComponent = () =>
pageProps={{
_nextI18Next: {
initialLocale: 'en',
userConfig: {
i18n: {
defaultLocale: 'en',
locales: ['en', 'fr'],
},
},
},
}}
} as any}
/>,
)

Expand Down
43 changes: 28 additions & 15 deletions src/appWithTranslation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@ type AppProps = {
pageProps: SSRConfig
}

export const appWithTranslation = <P extends Record<string, unknown>>(
WrappedComponent: React.ComponentType | React.ElementType,
configOverride: UserConfig = null,
):
React.ComponentType<P> | React.ElementType<P> => {
export const appWithTranslation = (
WrappedComponent: React.ComponentType<AppProps> | React.ElementType<AppProps>,
configOverride: UserConfig | null = null,
) => {
const AppWithTranslation = (props: AppProps) => {
let i18n = null
let locale = null
Expand All @@ -34,6 +33,10 @@ export const appWithTranslation = <P extends Record<string, unknown>>(
userConfig = configOverride
}

if (!userConfig?.i18n) {
throw new Error('appWithTranslation was called without config.i18n')
}

locale = initialLocale;

({ i18n } = createClient({
Expand All @@ -46,17 +49,27 @@ export const appWithTranslation = <P extends Record<string, unknown>>(
}))
}

return (
<I18nextProvider
i18n={i18n}
>
<WrappedComponent
key={locale}
{...props}
/>
</I18nextProvider>
return i18n !== null ? (
(
<I18nextProvider
i18n={i18n}
>
<WrappedComponent
key={locale}
{...props}
/>
</I18nextProvider>
)
) : (
<WrappedComponent
key={locale}
{...props}
/>
)
}

return hoistNonReactStatics(AppWithTranslation, WrappedComponent)
return hoistNonReactStatics(
AppWithTranslation,
WrappedComponent,
)
}
4 changes: 2 additions & 2 deletions src/config/createConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe('createConfig', () => {
describe('server side', () => {
beforeAll(() => {
Object.assign(process, { browser: false })
delete global.window
delete (global as any).window
})

describe('when filesystem is as expected', () => {
Expand Down Expand Up @@ -106,7 +106,7 @@ describe('createConfig', () => {

const config = createConfig.bind(null, {
lng: 'en',
})
} as any)

expect(config).toThrow('Default namespace not found at public/locales/en/common.json')
})
Expand Down
10 changes: 6 additions & 4 deletions src/config/createConfig.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { defaultConfig } from './defaultConfig'
import { InternalConfig, UserConfig } from '../../types'

const deepMergeObjects = ['backend', 'detection']
const deepMergeObjects = ['backend', 'detection'] as (keyof Pick<UserConfig, 'backend' | 'detection'>)[]

export const createConfig = (userConfig: UserConfig): InternalConfig => {
if (typeof userConfig?.lng !== 'string') {
Expand Down Expand Up @@ -55,7 +55,7 @@ export const createConfig = (userConfig: UserConfig): InternalConfig => {
// Validate defaultNS
// https://github.com/isaachinman/next-i18next/issues/358
//
if (typeof defaultNS === 'string') {
if (typeof defaultNS === 'string' && typeof lng !== 'undefined') {
const defaultLocaleStructure = localeStructure.replace('{{lng}}', lng).replace('{{ns}}', defaultNS)
const defaultFile = `/${defaultLocaleStructure}.${localeExtension}`
const defaultNSPath = path.join(localePath, defaultFile)
Expand All @@ -77,7 +77,10 @@ export const createConfig = (userConfig: UserConfig): InternalConfig => {
// Set server side preload (namespaces)
//
if (!combinedConfig.ns) {
const getAllNamespaces = p => fs.readdirSync(p).map(file => file.replace(`.${localeExtension}`, ''))
const getAllNamespaces = (p: string) =>
fs.readdirSync(p).map(
(file: string) => file.replace(`.${localeExtension}`, '')
)
combinedConfig.ns = getAllNamespaces(path.resolve(process.cwd(), `${serverLocalePath}/${lng}`))
}
}
Expand Down Expand Up @@ -109,7 +112,6 @@ export const createConfig = (userConfig: UserConfig): InternalConfig => {
deepMergeObjects.forEach((obj) => {
if (userConfig[obj]) {
combinedConfig[obj] = {
...defaultConfig[obj],
...combinedConfig[obj],
...userConfig[obj],
}
Expand Down
3 changes: 1 addition & 2 deletions src/createClient/browser.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { InternalConfig } from '../../types'
import createClientBrowser from './browser'

const config = {
defaultLocale: 'en',
locales: ['en', 'de'],
use: [],
} as InternalConfig
} as any

describe('createClientBrowser', () => {
it('returns a browser client', () => {
Expand Down
4 changes: 3 additions & 1 deletion src/createClient/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ export default (config: InternalConfig): CreateClientReturn => {
let initPromise: InitPromise

if (!instance.isInitialized) {
config.use.forEach(x => instance.use(x))
config?.use?.forEach(x => instance.use(x))
initPromise = instance.init(config)
} else {
initPromise = Promise.resolve(i18n.t)
}

return { i18n: instance, initPromise }
Expand Down
3 changes: 1 addition & 2 deletions src/createClient/node.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { InternalConfig } from '../../types'
import createClientNode from './node'

const config = {
defaultLocale: 'en',
locales: ['en', 'de'],
use: [],
} as InternalConfig
} as any

describe('createClientBrowser', () => {
it('returns a node client', () => {
Expand Down
7 changes: 5 additions & 2 deletions src/createClient/node.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import i18n from 'i18next'
import i18nextFSBackend from 'i18next-fs-backend/cjs'
import i18nextFSBackend from 'i18next-fs-backend'

import { InternalConfig, CreateClientReturn, InitPromise } from '../../types'

Expand All @@ -13,8 +13,11 @@ export default (config: InternalConfig): CreateClientReturn => {
instance.use(i18nextFSBackend)
}

config.use.forEach(x => instance.use(x))
config?.use?.forEach(x => instance.use(x))
initPromise = instance.init(config)
} else {
initPromise = Promise.resolve(i18n.t)
}

return { i18n: instance, initPromise }
}
20 changes: 15 additions & 5 deletions src/serverSideTranslations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe('serverSideTranslations', () => {
afterEach(jest.resetAllMocks)

it('throws if initialLocale is not passed', async () => {
await expect(serverSideTranslations(undefined))
await expect(serverSideTranslations(undefined as any))
.rejects
.toThrow('Initial locale argument was not passed into serverSideTranslations')
})
Expand All @@ -36,15 +36,25 @@ describe('serverSideTranslations', () => {
})

it('returns props', async () => {
const props = await serverSideTranslations('en')
const props = await serverSideTranslations('en-US', [], {
i18n: {
defaultLocale: 'en-US',
locales: ['en-US', 'fr-CA'],
},
} as UserConfig)

expect(props).toEqual({
_nextI18Next: {
initialI18nStore: {
en: {},
'en-US': {},
},
initialLocale: 'en-US',
userConfig: {
i18n: {
defaultLocale: 'en-US',
locales: ['en-US', 'fr-CA'],
},
},
initialLocale: 'en',
userConfig: null,
},
})
})
Expand Down
8 changes: 6 additions & 2 deletions src/serverSideTranslations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const DEFAULT_CONFIG_PATH = './next-i18next.config.js'
export const serverSideTranslations = async (
initialLocale: string,
namespacesRequired: string[] = [],
configOverride: UserConfig = null,
configOverride: UserConfig | null = null,
): Promise<SSRConfig> => {
if (typeof initialLocale !== 'string') {
throw new Error('Initial locale argument was not passed into serverSideTranslations')
Expand All @@ -23,6 +23,10 @@ export const serverSideTranslations = async (
userConfig = await import(path.resolve(DEFAULT_CONFIG_PATH))
}

if (userConfig === null) {
throw new Error('next-i18next was unable to find a user config')
}

const config = createConfig({
...userConfig,
lng: initialLocale,
Expand All @@ -41,7 +45,7 @@ export const serverSideTranslations = async (

await initPromise

const initialI18nStore = {
const initialI18nStore: Record<string, any> = {
[initialLocale]: {},
}

Expand Down
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"compilerOptions": {
"esModuleInterop": true,
"noEmit": true,
"strict": true,
"skipLibCheck": true,
"lib": ["esnext", "dom"],
"target": "es5",
Expand Down
4 changes: 2 additions & 2 deletions types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export type UserConfig = {
localeExtension?: string
localePath?: string
localeStructure?: string
serializeConfig: boolean
serializeConfig?: boolean
strictMode?: boolean
use?: any[]
} & InitOptions
Expand Down Expand Up @@ -50,7 +50,7 @@ export type SSRConfig = {
_nextI18Next: {
initialI18nStore: any
initialLocale: string
userConfig: UserConfig
userConfig: UserConfig | null
}
}

Expand Down
Loading

1 comment on commit 365921d

@vercel
Copy link

@vercel vercel bot commented on 365921d Mar 2, 2021

Choose a reason for hiding this comment

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

Please sign in to comment.