Skip to content

Commit

Permalink
refactor: Require absolute localePath, and refactor out usage of eval
Browse files Browse the repository at this point in the history
  • Loading branch information
isaachinman committed Jul 9, 2020
1 parent 77a6038 commit 826a9a5
Show file tree
Hide file tree
Showing 17 changed files with 1,292 additions and 331 deletions.
3 changes: 2 additions & 1 deletion .eslintignore
@@ -1,2 +1,3 @@
**/node_modules
**/out
**/out
src/create-client/package.json
23 changes: 17 additions & 6 deletions .eslintrc
Expand Up @@ -13,9 +13,15 @@
"browser": true
},
"rules": {
"@typescript-eslint/indent": ["error", 2],
"@typescript-eslint/indent": [
"error",
2
],
"semi": "off",
"@typescript-eslint/semi": ["error", "never"],
"@typescript-eslint/semi": [
"error",
"never"
],
"@typescript-eslint/no-var-requires": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/explicit-function-return-type": 0,
Expand All @@ -35,9 +41,14 @@
"react/prefer-stateless-function": 0,
"no-restricted-syntax": 0,
"no-useless-escape": 0,
"no-eval": 0,
"no-console": ["error", {
"allow": ["warn", "error"]
}]
"no-console": [
"error",
{
"allow": [
"warn",
"error"
]
}
]
}
}
44 changes: 24 additions & 20 deletions __tests__/config/create-config.test.ts
Expand Up @@ -39,7 +39,9 @@ describe('create configuration in non-production environment', () => {
existsSync: jest.fn().mockImplementation(() => false),
}))

expect(() => createConfig({})).toThrow(
expect(() => createConfig({
localePath: '/home/user/public/static/locales'
})).toThrow(
'Default namespace not found at /home/user/public/static/locales/en/common.json',
)
})
Expand All @@ -58,13 +60,15 @@ describe('create configuration in non-production environment', () => {

it('creates default non-production configuration', () => {
isServer.mockReturnValue(true)
const config = createConfig({})
const config = createConfig({
localePath: '/home/user/public/static/locales'
})

expect(config.defaultLanguage).toEqual('en')
expect(config.otherLanguages).toEqual([])
expect(config.fallbackLng).toEqual(false)
expect(config.load).toEqual('currentOnly')
expect(config.localePath).toEqual('public/static/locales')
expect(config.localePath).toEqual('/home/user/public/static/locales')
expect(config.localeStructure).toEqual('{{lng}}/{{ns}}')
expect(config.localeSubpaths).toEqual({})
expect(config.use).toEqual([])
Expand Down Expand Up @@ -105,7 +109,7 @@ describe('create configuration in non-production environment', () => {
expect(config.otherLanguages).toEqual(['fr', 'it'])
expect(config.fallbackLng).toEqual('it')
expect(config.load).toEqual('currentOnly')
expect(config.localePath).toEqual('public/static/translations')
expect(config.localePath).toEqual('/home/user/public/static/locales')
expect(config.localeStructure).toEqual('{{ns}}/{{lng}}')
expect(config.localeSubpaths).toEqual(localeSubpathVariations.FOREIGN)
expect(config.defaultNS).toEqual('universal')
Expand All @@ -114,8 +118,8 @@ describe('create configuration in non-production environment', () => {

expect(config.ns).toEqual(['universal', 'file1', 'file2'])

expect(config.backend.loadPath).toEqual('/home/user/public/static/translations/{{ns}}/{{lng}}.json')
expect(config.backend.addPath).toEqual('/home/user/public/static/translations/{{ns}}/{{lng}}.missing.json')
expect(config.backend.loadPath).toEqual('/home/user/public/static/locales/{{ns}}/{{lng}}.json')
expect(config.backend.addPath).toEqual('/home/user/public/static/locales/{{ns}}/{{lng}}.missing.json')
})

it('falls back to deprecated static folder', () => {
Expand Down Expand Up @@ -145,17 +149,17 @@ describe('create configuration in non-production environment', () => {

describe('localeExtension config option', () => {
it('is set to JSON by default', () => {
const config = createConfig(userConfig)
expect(config.backend.loadPath).toEqual('/home/user/public/static/translations/{{ns}}/{{lng}}.json')
expect(config.backend.addPath).toEqual('/home/user/public/static/translations/{{ns}}/{{lng}}.missing.json')
const config = createConfig(userConfigServerSide)
expect(config.backend.loadPath).toEqual('/home/user/public/static/locales/{{ns}}/{{lng}}.json')
expect(config.backend.addPath).toEqual('/home/user/public/static/locales/{{ns}}/{{lng}}.missing.json')
})
it('accepts any string and modifies backend paths', () => {
const config = createConfig({
...userConfig,
...userConfigServerSide,
localeExtension: 'test-extension',
})
expect(config.backend.loadPath).toEqual('/home/user/public/static/translations/{{ns}}/{{lng}}.test-extension')
expect(config.backend.addPath).toEqual('/home/user/public/static/translations/{{ns}}/{{lng}}.missing.test-extension')
expect(config.backend.loadPath).toEqual('/home/user/public/static/locales/{{ns}}/{{lng}}.test-extension')
expect(config.backend.addPath).toEqual('/home/user/public/static/locales/{{ns}}/{{lng}}.missing.test-extension')
})
})
})
Expand All @@ -169,7 +173,7 @@ describe('create configuration in non-production environment', () => {
expect(config.otherLanguages).toEqual([])
expect(config.fallbackLng).toEqual(false)
expect(config.load).toEqual('currentOnly')
expect(config.localePath).toEqual('public/static/locales')
expect(config.localePath).toEqual('/public/static/locales')
expect(config.localeStructure).toEqual('{{lng}}/{{ns}}')
expect(config.localeSubpaths).toEqual(localeSubpathVariations.NONE)
expect(config.use).toEqual([])
Expand Down Expand Up @@ -204,33 +208,33 @@ describe('create configuration in non-production environment', () => {
expect(config.otherLanguages).toEqual(['fr', 'it'])
expect(config.fallbackLng).toEqual('it')
expect(config.load).toEqual('currentOnly')
expect(config.localePath).toEqual('public/static/translations')
expect(config.localePath).toEqual('/public/static/locales')
expect(config.localeStructure).toEqual('{{ns}}/{{lng}}')
expect(config.localeSubpaths).toEqual(localeSubpathVariations.FOREIGN)
expect(config.defaultNS).toEqual('universal')
expect(config.browserLanguageDetection).toEqual(false)

expect(config.ns).toEqual(['universal'])

expect(config.backend.loadPath).toEqual('/static/translations/{{ns}}/{{lng}}.json')
expect(config.backend.addPath).toEqual('/static/translations/{{ns}}/{{lng}}.missing.json')
expect(config.backend.loadPath).toEqual('/static/locales/{{ns}}/{{lng}}.json')
expect(config.backend.addPath).toEqual('/static/locales/{{ns}}/{{lng}}.missing.json')

expect(config.shallowRender).toEqual(true)
})

describe('localeExtension config option', () => {
it('is set to JSON by default', () => {
const config = createConfig(userConfig)
expect(config.backend.loadPath).toEqual('/static/translations/{{ns}}/{{lng}}.json')
expect(config.backend.addPath).toEqual('/static/translations/{{ns}}/{{lng}}.missing.json')
expect(config.backend.loadPath).toEqual('/static/locales/{{ns}}/{{lng}}.json')
expect(config.backend.addPath).toEqual('/static/locales/{{ns}}/{{lng}}.missing.json')
})
it('accepts any string and modifies backend paths', () => {
const config = createConfig({
...userConfig,
localeExtension: 'test-extension',
})
expect(config.backend.loadPath).toEqual('/static/translations/{{ns}}/{{lng}}.test-extension')
expect(config.backend.addPath).toEqual('/static/translations/{{ns}}/{{lng}}.missing.test-extension')
expect(config.backend.loadPath).toEqual('/static/locales/{{ns}}/{{lng}}.test-extension')
expect(config.backend.addPath).toEqual('/static/locales/{{ns}}/{{lng}}.missing.test-extension')
})
})
}
Expand Down
11 changes: 2 additions & 9 deletions __tests__/config/test-helpers.ts
Expand Up @@ -15,26 +15,19 @@ const userConfig = {
defaultNS: 'universal',
fallbackLng: 'it',
otherLanguages: ['fr', 'it'],
localePath: 'public/static/translations',
localeStructure: '{{ns}}/{{lng}}',
localeSubpaths: localeSubpathVariations.FOREIGN,
shallowRender: true
}

const userConfigClientSide = {
...userConfig,
backend: {
loadPath: '/static/translations/{{ns}}/{{lng}}.json',
addPath: '/static/translations/{{ns}}/{{lng}}.missing.json',
},
localePath: '/public/static/locales',
}

const userConfigServerSide = {
...userConfig,
backend: {
loadPath: '/home/user/public/static/translations/{{ns}}/{{lng}}.json',
addPath: '/home/user/public/static/translations/{{ns}}/{{lng}}.missing.json',
},
localePath: '/home/user/public/static/locales',
}

const setUpTest = () => {
Expand Down
116 changes: 116 additions & 0 deletions __tests__/create-client/browser.test.ts
@@ -0,0 +1,116 @@
import i18next from 'i18next'
import I18nextBrowserLanguageDetector from 'i18next-browser-languagedetector'
import createI18nextClient from '../../src/create-client/browser'

const i18nextMiddleware = require('i18next-http-middleware')

jest.mock('i18next', () => ({
init: jest.fn(() => new Promise(resolve => setTimeout(() => resolve(), 0))),
use: jest.fn(),
}))

jest.mock('i18next-http-middleware', () => ({
LanguageDetector: jest.fn(),
}))

const I18nextBrowserLanguageDetectorInit = I18nextBrowserLanguageDetector.prototype.addDetector = jest.fn()

describe('initializing i18n', () => {
beforeEach(() => {
i18next.isInitialized = false
})

afterEach(() => {
(i18next.init as jest.Mock).mockClear();
(i18next.use as jest.Mock).mockClear()
i18nextMiddleware.LanguageDetector.mockClear()
I18nextBrowserLanguageDetectorInit.mockClear()
})

it('should return both the i18n object and an initPromise', async () => {
const { i18n, initPromise } = createI18nextClient({
use: [],
customDetectors: [],
})
expect(typeof i18n.isInitialized).toBe('boolean')
expect(typeof initPromise.then).toBe('function')
})

it('should not initialize i18n if i18n is already initialized', () => {
i18next.isInitialized = true

createI18nextClient({
use: [],
customDetectors: [],
serverLanguageDetection: true,
browserLanguageDetection: true,
})

expect(i18next.init).not.toBeCalled()
expect(i18next.use).not.toBeCalled()
})

it('should initialize i18n if i18n is not yet initialized', () => {
i18next.isInitialized = false

createI18nextClient({
use: [],
customDetectors: [],
serverLanguageDetection: true,
browserLanguageDetection: true,
})

expect(i18next.use).toBeCalledTimes(2)
expect(i18next.init).toBeCalledTimes(1)
})

it('should not add backend language detector', () => {
i18next.isInitialized = false

createI18nextClient({
use: [],
customDetectors: [],
serverLanguageDetection: true,
browserLanguageDetection: true,
})

expect(i18nextMiddleware.LanguageDetector).not.toBeCalled()
expect(I18nextBrowserLanguageDetectorInit).toBeCalled()
expect(i18next.use).toBeCalledTimes(2)
expect(i18next.init).toBeCalled()
})

it('should call addDetector with the right detector if browserLanguageDetection is true', () => {
i18next.isInitialized = false

createI18nextClient({
use: [],
customDetectors: ['test_custom_detector'],
browserLanguageDetection: true,
})
expect(i18nextMiddleware.LanguageDetector).not.toBeCalled()
expect(I18nextBrowserLanguageDetectorInit).toBeCalled()
expect(I18nextBrowserLanguageDetectorInit).toBeCalledWith('test_custom_detector')
expect(i18next.use).toBeCalledTimes(2)
expect(i18next.init).toBeCalledTimes(1)
})

it('should not call addDetector if browserLanguageDetection is false', () => {
i18next.isInitialized = false
const addDetector = jest.fn()
i18nextMiddleware.LanguageDetector.mockImplementation(() => ({
addDetector,
}))

createI18nextClient({
use: [],
customDetectors: ['test_custom_detector'],
browserLanguageDetection: false,
})
expect(i18nextMiddleware.LanguageDetector).not.toBeCalled()
expect(I18nextBrowserLanguageDetectorInit).not.toBeCalled()
expect(addDetector).not.toBeCalled()
expect(i18next.use).toBeCalledTimes(1)
expect(i18next.init).toBeCalledTimes(1)
})
})
@@ -1,6 +1,6 @@
import i18next from 'i18next'
import I18nextBrowserLanguageDetector from 'i18next-browser-languagedetector'
import createI18nextClient from '../src/create-i18next-client'
import createI18nextClient from '../../src/create-client/node'

const i18nextMiddleware = require('i18next-http-middleware')

Expand Down
10 changes: 7 additions & 3 deletions .babelrc → babel.config.json
Expand Up @@ -12,7 +12,9 @@
}
}
],
["@babel/preset-react"]
[
"@babel/preset-react"
]
]
},
"cjs": {
Expand All @@ -28,7 +30,7 @@
}
],
[
"next/babel",
"next/babel",
{
"transform-runtime": {
"corejs": false,
Expand Down Expand Up @@ -61,7 +63,9 @@
"modules": "commonjs"
}
],
["@babel/preset-react"]
[
"@babel/preset-react"
]
],
"plugins": [
"@babel/proposal-class-properties",
Expand Down
2 changes: 2 additions & 0 deletions examples/simple/i18n.js
@@ -1,7 +1,9 @@
const NextI18Next = require('next-i18next').default
const { localeSubpaths } = require('next/config').default().publicRuntimeConfig
const path = require('path')

module.exports = new NextI18Next({
otherLanguages: ['de'],
localeSubpaths,
localePath: path.resolve('./public/static/locales')
})

0 comments on commit 826a9a5

Please sign in to comment.