Skip to content

Commit

Permalink
fix(1181): Get namespaces from current locale/all locales + all fallb…
Browse files Browse the repository at this point in the history
…ackLng
  • Loading branch information
Nikoms committed May 13, 2021
1 parent ed50614 commit 37831e3
Show file tree
Hide file tree
Showing 4 changed files with 245 additions and 26 deletions.
20 changes: 18 additions & 2 deletions src/config/createConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ describe('createConfig', () => {
describe('when filesystem is as expected', () => {
beforeAll(() => {
(fs.existsSync as jest.Mock).mockReturnValue(true);
(fs.readdirSync as jest.Mock).mockReturnValue([])
(fs.readdirSync as jest.Mock).mockImplementation((locale)=>[`namespace-of-${locale.split('/').pop()}`])
})

it('throws when lng is not provided', () => {
Expand All @@ -41,7 +41,7 @@ describe('createConfig', () => {
expect(config.localePath).toEqual('./public/locales')
expect(config.localeStructure).toEqual('{{lng}}/{{ns}}')
expect(config.locales).toEqual(['en'])
expect(config.ns).toEqual([])
expect(config.ns).toEqual(['namespace-of-en'])
expect(config.preload).toEqual(['en'])
expect(config.strictMode).toEqual(true)
expect(config.use).toEqual([])
Expand All @@ -50,6 +50,22 @@ describe('createConfig', () => {
expect(fs.readdirSync).toHaveBeenCalledTimes(1)
})

it('gets namespaces from current language + fallback (as string) when ns is not provided', ()=>{
const config = createConfig({ fallbackLng:'en', lng: 'en-US' } as UserConfig)
expect(config.ns).toEqual(['namespace-of-en-US', 'namespace-of-en'])
})

it('gets namespaces from current language + fallback (as array) when ns is not provided', ()=>{
const config = createConfig({ fallbackLng: ['en', 'fr'], lng: 'en-US' } as UserConfig)
expect(config.ns).toEqual(['namespace-of-en-US', 'namespace-of-en', 'namespace-of-fr'])
})

it('gets namespaces from current language + fallback (as object) when ns is not provided', ()=>{
const fallbackLng = {default: ['fr'], 'en-US': ['en']} as unknown
const config = createConfig({ fallbackLng, lng: 'en-US' } as UserConfig)
expect(config.ns).toEqual(['namespace-of-en-US', 'namespace-of-fr', 'namespace-of-en'])
})

it('deep merges backend', () => {
const config = createConfig({
backend: {
Expand Down
49 changes: 42 additions & 7 deletions src/config/createConfig.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { defaultConfig } from './defaultConfig'
import { InternalConfig, UserConfig } from '../types'
import { FallbackLng } from 'i18next'

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

Expand Down Expand Up @@ -40,7 +41,6 @@ export const createConfig = (userConfig: UserConfig): InternalConfig => {
if (typeof combinedConfig.fallbackLng === 'undefined') {
combinedConfig.fallbackLng = combinedConfig.defaultLocale
}

if (!process.browser && typeof window === 'undefined') {
combinedConfig.preload = locales

Expand Down Expand Up @@ -76,12 +76,47 @@ export const createConfig = (userConfig: UserConfig): InternalConfig => {
//
// Set server side preload (namespaces)
//
if (!combinedConfig.ns) {
const getAllNamespaces = (p: string) =>
fs.readdirSync(p).map(
(file: string) => file.replace(`.${localeExtension}`, '')
)
combinedConfig.ns = getAllNamespaces(path.resolve(process.cwd(), `${serverLocalePath}/${lng}`))
if (!combinedConfig.ns && typeof lng !== 'undefined') {
const unique = (list:string[]) => Array.from(new Set<string>(list))
const getNamespaces = (locales: string[]): string[] => {
const getLocaleNamespaces = (p: string) =>
fs.readdirSync(p).map(
(file: string) => file.replace(`.${localeExtension}`, '')
)

const namespaces: string[] = locales
.map(locale => getLocaleNamespaces(path.resolve(process.cwd(), `${serverLocalePath}/${locale}`)))
.reduce(
(flattenNamespaces, namespaces) =>
[...flattenNamespaces,...namespaces],
[]
)

return unique(namespaces)
}

const getAllLocales = (
lng: string,
fallbackLng: false | FallbackLng
): string[] => {
if (typeof fallbackLng === 'string')
return unique([lng, fallbackLng])

if (Array.isArray(fallbackLng))
return unique([lng, ...fallbackLng])

if (typeof fallbackLng === 'object') {
const flattenedFallbacks = Object
.values(fallbackLng)
.reduce(((all, fallbackLngs) => [...all,...fallbackLngs]),[])
return unique([lng, ...flattenedFallbacks])
}
return [lng]
}

combinedConfig.ns = getNamespaces(
getAllLocales(lng, combinedConfig.fallbackLng)
)
}
}
} else {
Expand Down
171 changes: 156 additions & 15 deletions src/serverSideTranslations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,162 @@ describe('serverSideTranslations', () => {
.toThrow('Initial locale argument was not passed into serverSideTranslations')
})

it('returns all namespaces if namespacesRequired is not provided', async () => {
(fs.readdirSync as jest.Mock).mockReturnValue(['one', 'two', 'three'])
const props = await serverSideTranslations('en-US', undefined, {
i18n: {
defaultLocale: 'en-US',
locales: ['en-US', 'fr-CA'],
},
} as UserConfig)
expect(fs.readdirSync).toHaveBeenCalledTimes(1)
expect(fs.readdirSync).toHaveBeenCalledWith(expect.stringMatching('/public/locales/en-US'))
expect(props._nextI18Next.initialI18nStore)
.toEqual({
'en-US': { one: {}, three: {}, two: {} },
'fr-CA': { one: {}, three: {}, two: {} }}
)
describe('When namespacesRequired is not provided', ()=>{
beforeEach(() =>{
(fs.readdirSync as jest.Mock).mockImplementation((path)=>['common', `namespace-of-${path.split('/').pop()}`])
})

it('returns all namespaces', async () => {
const props = await serverSideTranslations('en-US', undefined, {
i18n: {
defaultLocale: 'en-US',
locales: ['en-US', 'fr-CA'],
},
} as UserConfig)
expect(fs.readdirSync).toHaveBeenCalledTimes(2)
expect(fs.readdirSync).toHaveBeenCalledWith(expect.stringMatching('/public/locales/en-US'))
expect(fs.readdirSync).toHaveBeenCalledWith(expect.stringMatching('/public/locales/fr-CA'))
expect(props._nextI18Next.initialI18nStore)
.toEqual({
'en-US': {
common: {},
'namespace-of-en-US': {},
'namespace-of-fr-CA': {},
},
'fr-CA': {
common: {},
'namespace-of-en-US': {},
'namespace-of-fr-CA': {},
},
})
})

it('returns all namespaces with fallbackLng (as string)', async () => {
const props = await serverSideTranslations('en-US', undefined, {
i18n: {
defaultLocale: 'fr-BE',
fallbackLng: 'fr',
locales: ['nl-BE', 'fr-BE'],
},
} as UserConfig)
expect(fs.readdirSync).toHaveBeenCalledTimes(3)
expect(fs.readdirSync).toHaveBeenCalledWith(expect.stringMatching('/public/locales/nl-BE'))
expect(fs.readdirSync).toHaveBeenCalledWith(expect.stringMatching('/public/locales/fr-BE'))
expect(fs.readdirSync).toHaveBeenCalledWith(expect.stringMatching('/public/locales/fr'))
expect(props._nextI18Next.initialI18nStore)
.toEqual({
fr: {
common: {},
'namespace-of-fr': {},
'namespace-of-fr-BE': {},
'namespace-of-nl-BE': {},
},
'fr-BE': {
common: {},
'namespace-of-fr': {},
'namespace-of-fr-BE': {},
'namespace-of-nl-BE': {},
},
'nl-BE': {
common: {},
'namespace-of-fr': {},
'namespace-of-fr-BE': {},
'namespace-of-nl-BE': {},
},
})
})

it('returns all namespaces with fallbackLng (as array)', async () => {
const props = await serverSideTranslations('en-US', undefined, {
i18n: {
defaultLocale: 'en-US',
fallbackLng: ['en','fr'],
locales: ['en-US', 'fr-CA'],
},
} as UserConfig)
expect(fs.readdirSync).toHaveBeenCalledTimes(4)
expect(fs.readdirSync).toHaveBeenCalledWith(expect.stringMatching('/public/locales/fr-CA'))
expect(fs.readdirSync).toHaveBeenCalledWith(expect.stringMatching('/public/locales/en-US'))
expect(fs.readdirSync).toHaveBeenCalledWith(expect.stringMatching('/public/locales/en'))
expect(fs.readdirSync).toHaveBeenCalledWith(expect.stringMatching('/public/locales/fr'))
expect(props._nextI18Next.initialI18nStore)
.toEqual({
en: {
common: {},
'namespace-of-en': {},
'namespace-of-en-US': {},
'namespace-of-fr': {},
'namespace-of-fr-CA': {},
},
'en-US': {
common: {},
'namespace-of-en': {},
'namespace-of-en-US': {},
'namespace-of-fr': {},
'namespace-of-fr-CA': {},
},
fr: {
common: {},
'namespace-of-en': {},
'namespace-of-en-US': {},
'namespace-of-fr': {},
'namespace-of-fr-CA': {},
},
'fr-CA': {
common: {},
'namespace-of-en': {},
'namespace-of-en-US': {},
'namespace-of-fr': {},
'namespace-of-fr-CA': {},
},
})
})

it('returns all namespaces with fallbackLng (as object)', async () => {
const props = await serverSideTranslations('en-US', undefined, {
i18n: {
defaultLocale: 'nl-BE',
fallbackLng: {default:['fr'], 'nl-BE':['en']},
locales: ['nl-BE', 'fr-BE'],
},
} as UserConfig)
expect(fs.readdirSync).toHaveBeenCalledTimes(4)
expect(fs.readdirSync).toHaveBeenCalledWith(expect.stringMatching('/public/locales/fr-BE'))
expect(fs.readdirSync).toHaveBeenCalledWith(expect.stringMatching('/public/locales/nl-BE'))
expect(fs.readdirSync).toHaveBeenCalledWith(expect.stringMatching('/public/locales/en'))
expect(fs.readdirSync).toHaveBeenCalledWith(expect.stringMatching('/public/locales/fr'))
expect(props._nextI18Next.initialI18nStore)
.toEqual({
en: {
common: {},
'namespace-of-en': {},
'namespace-of-fr': {},
'namespace-of-fr-BE': {},
'namespace-of-nl-BE': {},
},
fr: {
common: {},
'namespace-of-en': {},
'namespace-of-fr': {},
'namespace-of-fr-BE': {},
'namespace-of-nl-BE': {},
},
'fr-BE': {
common: {},
'namespace-of-en': {},
'namespace-of-fr': {},
'namespace-of-fr-BE': {},
'namespace-of-nl-BE': {},
},
'nl-BE': {
common: {},
'namespace-of-en': {},
'namespace-of-fr': {},
'namespace-of-fr-BE': {},
'namespace-of-nl-BE': {},
},
})
})
})

it('returns props', async () => {
Expand Down
31 changes: 29 additions & 2 deletions src/serverSideTranslations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,25 @@ import { createConfig } from './config/createConfig'
import createClient from './createClient'

import { UserConfig, SSRConfig } from './types'
import { FallbackLng } from 'i18next'

const DEFAULT_CONFIG_PATH = './next-i18next.config.js'

const getFallBackLocales = (fallbackLng: false | FallbackLng) => {
if (typeof fallbackLng === 'string') {
return [fallbackLng]
}
if (Array.isArray(fallbackLng)) {
return fallbackLng
}
if (typeof fallbackLng === 'object') {
return Object
.values(fallbackLng)
.reduce((all, locales) => [...all, ...locales],[])
}
return []
}

export const serverSideTranslations = async (
initialLocale: string,
namespacesRequired: string[] = [],
Expand All @@ -33,9 +49,9 @@ export const serverSideTranslations = async (
})

const {
defaultLocale,
localeExtension,
localePath,
fallbackLng,
} = config

const { i18n, initPromise } = createClient({
Expand All @@ -51,12 +67,23 @@ export const serverSideTranslations = async (
initialI18nStore[lng] = {}
})

getFallBackLocales(fallbackLng).forEach(lng => {
initialI18nStore[lng] = {}
})

if (namespacesRequired.length === 0) {
const getAllNamespaces = (path: string) =>
fs.readdirSync(path)
.map(file => file.replace(`.${localeExtension}`, ''))

namespacesRequired = getAllNamespaces(path.resolve(process.cwd(), `${localePath}/${defaultLocale}`))
const allNamespaces = Object.keys(initialI18nStore)
.map(locale => getAllNamespaces(path.resolve(process.cwd(), `${localePath}/${locale}`)))
.reduce(
(allNamespaces, namespaces) => [...allNamespaces,...namespaces],
[]
)

namespacesRequired = Array.from(new Set(allNamespaces))
}

namespacesRequired.forEach((ns) => {
Expand Down

0 comments on commit 37831e3

Please sign in to comment.