Skip to content

Commit

Permalink
refactor: move various helpers from utils to utils-common
Browse files Browse the repository at this point in the history
  • Loading branch information
rchl committed Apr 30, 2020
1 parent 15ada7d commit f688177
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 152 deletions.
76 changes: 16 additions & 60 deletions src/templates/plugin.main.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,90 +5,45 @@ import {
baseUrl,
beforeLanguageSwitch,
defaultLocale,
defaultLocaleRouteNameSuffix,
detectBrowserLanguage,
differentDomains,
lazy,
LOCALE_CODE_KEY,
LOCALE_DOMAIN_KEY,
localeCodes,
locales,
MODULE_NAME,
onLanguageSwitched,
rootRedirect,
routesNameSeparator,
STRATEGIES,
strategy,
vueI18n,
vuex
} from './options'
import {
getLocaleDomain,
getLocaleFromRoute,
syncVuex,
validateRouteParams
} from './utils'
import {
createLocaleFromRouteGetter,
getLocaleCookie,
getLocaleDomain,
resolveBaseUrl,
matchBrowserLocale,
parseAcceptLanguage,
setLocaleCookie
registerStore,
setLocaleCookie,
syncVuex
} from './utils-common'

Vue.use(VueI18n)

const getLocaleFromRoute = createLocaleFromRouteGetter(localeCodes, { routesNameSeparator, defaultLocaleRouteNameSuffix })

/** @type {import('@nuxt/types').Plugin} */
export default async (context) => {
const { app, route, store, req, res, redirect } = context

if (vuex && store) {
// Register Vuex module
store.registerModule(vuex.moduleName, {
namespaced: true,
state: () => ({
...(vuex.syncLocale ? { locale: '' } : {}),
...(vuex.syncMessages ? { messages: {} } : {}),
...(vuex.syncRouteParams ? { routeParams: {} } : {})
}),
actions: {
...(vuex.syncLocale ? {
setLocale ({ commit }, locale) {
commit('setLocale', locale)
}
} : {}),
...(vuex.syncMessages ? {
setMessages ({ commit }, messages) {
commit('setMessages', messages)
}
} : {}),
...(vuex.syncRouteParams ? {
setRouteParams ({ commit }, params) {
if (process.env.NODE_ENV === 'development') {
validateRouteParams(params)
}
commit('setRouteParams', params)
}
} : {})
},
mutations: {
...(vuex.syncLocale ? {
setLocale (state, locale) {
state.locale = locale
}
} : {}),
...(vuex.syncMessages ? {
setMessages (state, messages) {
state.messages = messages
}
} : {}),
...(vuex.syncRouteParams ? {
setRouteParams (state, params) {
state.routeParams = params
}
} : {})
},
getters: {
...(vuex.syncRouteParams ? {
localeRouteParams: ({ routeParams }) => locale => routeParams[locale] || {}
} : {})
}
}, { preserveState: !!store.state[vuex.moduleName] })
registerStore(store, vuex, localeCodes, MODULE_NAME)
}

const { useCookie, cookieKey, cookieDomain } = detectBrowserLanguage
Expand Down Expand Up @@ -128,7 +83,7 @@ export default async (context) => {

app.i18n.locale = newLocale

await syncVuex(store, newLocale, app.i18n.getLocaleMessage(newLocale))
await syncVuex(store, newLocale, app.i18n.getLocaleMessage(newLocale), { vuex })

const redirectPath = getRedirectPathForLocale(newLocale)

Expand Down Expand Up @@ -267,7 +222,8 @@ export default async (context) => {
if (vuex && vuex.syncLocale && store && store.state[vuex.moduleName].locale !== '') {
finalLocale = store.state[vuex.moduleName].locale
} else if (app.i18n.differentDomains) {
const domainLocale = getLocaleDomain(app.i18n, req)
const options = { localDomainKey: LOCALE_DOMAIN_KEY, localeCodeKey: LOCALE_CODE_KEY }
const domainLocale = getLocaleDomain(locales, req, options)
finalLocale = domainLocale || finalLocale
} else if (strategy !== STRATEGIES.NO_PREFIX) {
const routeLocale = getLocaleFromRoute(route)
Expand Down
160 changes: 160 additions & 0 deletions src/templates/utils-common.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,68 @@ export const resolveBaseUrl = (baseUrl, context) => {
return baseUrl
}

/**
* Get locale code that corresponds to current hostname
* @param {object} locales
* @param {object} [req] Request object
* @param {{ localDomainKey: string, localeCodeKey: string }} options
* @return {string | null} Locade code found if any
*/
export const getLocaleDomain = (locales, req, { localDomainKey, localeCodeKey }) => {
const hostname = process.client ? window.location.hostname : (req.headers['x-forwarded-host'] || req.headers.host)

if (hostname) {
const localeDomain = locales.find(l => l[localDomainKey] === hostname)
if (localeDomain) {
return localeDomain[localeCodeKey]
}
}

return null
}

/**
* Creates getter for getLocaleFromRoute
* @param {string[]} localeCodes
* @param {{ routesNameSeparator: string, defaultLocaleRouteNameSuffix: string }} options
* @return {(route) => string| null}
*/
export const createLocaleFromRouteGetter = (localeCodes, { routesNameSeparator, defaultLocaleRouteNameSuffix }) => {
const localesPattern = `(${localeCodes.join('|')})`
const defaultSuffixPattern = `(?:${routesNameSeparator}${defaultLocaleRouteNameSuffix})?`
const regexpName = new RegExp(`${routesNameSeparator}${localesPattern}${defaultSuffixPattern}$`, 'i')
const regexpPath = new RegExp(`^/${localesPattern}/`, 'i')

/**
* Extract locale code from given route:
* - If route has a name, try to extract locale from it
* - Otherwise, fall back to using the routes'path
* @param {Object} route
* @param {string[]} localeCodes
* @param {{ routesNameSeparator: string, defaultLocaleRouteNameSuffix: string }} options
* @return {string | null} Locale code found if any
*/
const getLocaleFromRoute = route => {
// Extract from route name
if (route.name) {
const matches = route.name.match(regexpName)
if (matches && matches.length > 1) {
return matches[1]
}
} else if (route.path) {
// Extract from path
const matches = route.path.match(regexpPath)
if (matches && matches.length > 1) {
return matches[1]
}
}

return null
}

return getLocaleFromRoute
}

/**
* @param {object} [req]
* @param {{ useCookie: boolean, localeCodes: string[], cookieKey: string}} options
Expand Down Expand Up @@ -133,3 +195,101 @@ export const setLocaleCookie = (locale, res, { useCookie, cookieDomain, cookieKe
res.setHeader('Set-Cookie', headers)
}
}

export const registerStore = (store, vuex, localeCodes, moduleName) => {
store.registerModule(vuex.moduleName, {
namespaced: true,
state: () => ({
...(vuex.syncLocale ? { locale: '' } : {}),
...(vuex.syncMessages ? { messages: {} } : {}),
...(vuex.syncRouteParams ? { routeParams: {} } : {})
}),
actions: {
...(vuex.syncLocale ? {
setLocale ({ commit }, locale) {
commit('setLocale', locale)
}
} : {}),
...(vuex.syncMessages ? {
setMessages ({ commit }, messages) {
commit('setMessages', messages)
}
} : {}),
...(vuex.syncRouteParams ? {
setRouteParams ({ commit }, params) {
if (process.env.NODE_ENV === 'development') {
validateRouteParams(params, localeCodes, moduleName)
}
commit('setRouteParams', params)
}
} : {})
},
mutations: {
...(vuex.syncLocale ? {
setLocale (state, locale) {
state.locale = locale
}
} : {}),
...(vuex.syncMessages ? {
setMessages (state, messages) {
state.messages = messages
}
} : {}),
...(vuex.syncRouteParams ? {
setRouteParams (state, params) {
state.routeParams = params
}
} : {})
},
getters: {
...(vuex.syncRouteParams ? {
localeRouteParams: ({ routeParams }) => locale => routeParams[locale] || {}
} : {})
}
}, { preserveState: !!store.state[vuex.moduleName] })
}

/**
* Dispatch store module actions to keep it in sync with app's locale data
* @param {Store} store Vuex store
* @param {String} locale Current locale
* @param {Object} messages Current messages
* @param {{ vuex: object }} options
* @return {Promise(void)}
*/
export const syncVuex = async (store, locale = null, messages = null, { vuex }) => {
if (vuex && store) {
if (locale !== null && vuex.syncLocale) {
await store.dispatch(vuex.moduleName + '/setLocale', locale)
}
if (messages !== null && vuex.syncMessages) {
await store.dispatch(vuex.moduleName + '/setMessages', messages)
}
}
}

const isObject = value => value && !Array.isArray(value) && typeof value === 'object'

/**
* Validate setRouteParams action's payload
* @param {object} routeParams The action's payload
* @param {string[]} localeCodes
* @param {string} moduleName
*/
export const validateRouteParams = (routeParams, localeCodes, moduleName) => {
if (!isObject(routeParams)) {
// eslint-disable-next-line no-console
console.warn(`[${moduleName}] Route params should be an object`)
return
}

for (const [key, value] of Object.entries(routeParams)) {
if (!localeCodes.includes(key)) {
// eslint-disable-next-line no-console
console.warn(`[${moduleName}] Trying to set route params for key ${key} which is not a valid locale`)
} else if (!isObject(value)) {
// eslint-disable-next-line no-console
console.warn(`[${moduleName}] Trying to set route params for locale ${key} with a non-object value`)
}
}
}
93 changes: 1 addition & 92 deletions src/templates/utils.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import {
defaultLocaleRouteNameSuffix,
localeCodes,
LOCALE_CODE_KEY,
LOCALE_DOMAIN_KEY,
LOCALE_FILE_KEY,
MODULE_NAME,
routesNameSeparator,
vuex
MODULE_NAME
} from './options'

/**
Expand Down Expand Up @@ -46,89 +41,3 @@ export async function loadLanguageAsync (context, locale) {
}
}
}

const isObject = value => value && !Array.isArray(value) && typeof value === 'object'

/**
* Validate setRouteParams action's payload
* @param {*} routeParams The action's payload
*/
export const validateRouteParams = routeParams => {
if (!isObject(routeParams)) {
// eslint-disable-next-line no-console
console.warn(`[${MODULE_NAME}] Route params should be an object`)
return
}
Object.entries(routeParams).forEach(([key, value]) => {
if (!localeCodes.includes(key)) {
// eslint-disable-next-line no-console
console.warn(`[${MODULE_NAME}] Trying to set route params for key ${key} which is not a valid locale`)
} else if (!isObject(value)) {
// eslint-disable-next-line no-console
console.warn(`[${MODULE_NAME}] Trying to set route params for locale ${key} with a non-object value`)
}
})
}

/**
* Get locale code that corresponds to current hostname
* @param {VueI18n} nuxtI18n Instance of VueI18n
* @param {object} req Request object
* @return {String} Locade code found if any
*/
export const getLocaleDomain = (nuxtI18n, req) => {
const hostname = process.client ? window.location.hostname : (req.headers['x-forwarded-host'] || req.headers.host)
if (hostname) {
const localeDomain = nuxtI18n.locales.find(l => l[LOCALE_DOMAIN_KEY] === hostname)
if (localeDomain) {
return localeDomain[LOCALE_CODE_KEY]
}
}
return null
}

/**
* Extract locale code from given route:
* - If route has a name, try to extract locale from it
* - Otherwise, fall back to using the routes'path
* @param {Object} route Route
* @return {String} Locale code found if any
*/
export const getLocaleFromRoute = (route = {}) => {
const localesPattern = `(${localeCodes.join('|')})`
const defaultSuffixPattern = `(?:${routesNameSeparator}${defaultLocaleRouteNameSuffix})?`
// Extract from route name
if (route.name) {
const regexp = new RegExp(`${routesNameSeparator}${localesPattern}${defaultSuffixPattern}$`, 'i')
const matches = route.name.match(regexp)
if (matches && matches.length > 1) {
return matches[1]
}
} else if (route.path) {
// Extract from path
const regexp = new RegExp(`^/${localesPattern}/`, 'i')
const matches = route.path.match(regexp)
if (matches && matches.length > 1) {
return matches[1]
}
}
return null
}

/**
* Dispatch store module actions to keep it in sync with app's locale data
* @param {Store} store Vuex store
* @param {String} locale Current locale
* @param {Object} messages Current messages
* @return {Promise(void)}
*/
export const syncVuex = async (store, locale = null, messages = null) => {
if (vuex && store) {
if (locale !== null && vuex.syncLocale) {
await store.dispatch(vuex.moduleName + '/setLocale', locale)
}
if (messages !== null && vuex.syncMessages) {
await store.dispatch(vuex.moduleName + '/setMessages', messages)
}
}
}

0 comments on commit f688177

Please sign in to comment.