Skip to content

Commit

Permalink
refactor: avoid using templates
Browse files Browse the repository at this point in the history
Since using lodash templates makes it impossible to properly lint files,
create options.js file that has all options stringified and exported so
that all other files can just import those without using templates.

That makes it possible to lint files and also see exactly which
variables are used and which are missing which was really hard when
whole functions were inlined when processing templates.

Instead of passing various functions through templates and stringifying
them, keep them in templates/utils.js and import directly. This also
has benefit of avoiding code duplication in processed files.

Compute localeCodes and some other variables up-front as when those are
based on user settings, they are guaranteed not to change so we don't
have to processes them on each request/route change.

`loadLanguageAsync` is one tricky function that needs to use templates
so that we don't include `import` statement when `langDir` is not defined.
To not break linting, added some hacky block comments to hide template
directives from linter.
  • Loading branch information
rchl committed Sep 10, 2019
1 parent 5988600 commit 462e3b3
Show file tree
Hide file tree
Showing 12 changed files with 253 additions and 256 deletions.
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@
"docs:build": "vuepress build docs"
},
"eslintIgnore": [
"src/templates/*.*",
"src/plugins/*.*",
"src/templates/options.js",
"**/*.d.ts"
],
"files": [
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/components.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ exports.extractComponentOptions = (path) => {
}
})
} catch (error) {
// eslint-disable-next-line
// eslint-disable-next-line no-console
console.warn('[' + MODULE_NAME + `] Error parsing "${COMPONENT_OPTIONS_KEY}" component option in file "${path}".`)
}

Expand Down
2 changes: 1 addition & 1 deletion src/helpers/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ exports.makeRoutes = (baseRoutes, {

// Skip if locale not in module's configuration
if (locales.indexOf(locale) === -1) {
// eslint-disable-next-line
// eslint-disable-next-line no-console
console.warn(`[${MODULE_NAME}] Can't generate localized route for route '${name}' with locale '${locale}' because locale is not in the module's configuration`)
continue
}
Expand Down
88 changes: 1 addition & 87 deletions src/helpers/utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
/* global app, req, vuex, store */

const { LOCALE_CODE_KEY, LOCALE_DOMAIN_KEY } = require('./constants')
const { LOCALE_CODE_KEY } = require('./constants')

/**
* Get an array of locale codes from a list of locales
Expand Down Expand Up @@ -66,87 +64,3 @@ exports.getPageOptions = (route, pages, locales, pagesDir, defaultLocale) => {

return options
}

/**
* 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
* @param {String} routesNameSeparator Separator used to add locale suffixes in routes names
* @param {String} defaultLocaleRouteNameSuffix Suffix added to default locale routes names
* @param {Array} locales Locales list from nuxt config
* @return {String} Locale code found if any
*/
exports.getLocaleFromRoute = (route = {}, routesNameSeparator = '', defaultLocaleRouteNameSuffix = '', locales = []) => {
const codes = getLocaleCodes(locales)
const localesPattern = `(${codes.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
}

/**
* Get x-forwarded-host
* @return {String} x-forwarded-host
*/
const getForwarded = () => (
process.browser ? window.location.href.split('/')[2] : (req.headers['x-forwarded-host'] ? req.headers['x-forwarded-host'] : req.headers.host)
)

exports.getForwarded = getForwarded

/**
* Get hostname
* @return {String} Hostname
*/
const getHostname = () => (
process.browser ? window.location.href.split('/')[2] : req.headers.host // eslint-disable-line
)

exports.getHostname = getHostname

/**
* Get locale code that corresponds to current hostname
* @return {String} Locade code found if any
*/
exports.getLocaleDomain = () => {
const hostname = app.i18n.forwardedHost ? getForwarded() : getHostname()
if (hostname) {
const localeDomain = app.i18n.locales.find(l => l[LOCALE_DOMAIN_KEY] === hostname) // eslint-disable-line
if (localeDomain) {
return localeDomain[LOCALE_CODE_KEY]
}
}
return null
}

/**
* Dispatch store module actions to keep it in sync with app's locale data
* @param {String} locale Current locale
* @param {Object} messages Current messages
* @return {Promise(void)}
*/
exports.syncVuex = async (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)
}
}
}
14 changes: 2 additions & 12 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,7 @@ const {
} = require('./helpers/constants')

const {
getLocaleCodes,
getLocaleFromRoute,
getForwarded,
getHostname,
getLocaleDomain,
syncVuex
getLocaleCodes
} = require('./helpers/utils')

module.exports = function (userOptions) {
Expand Down Expand Up @@ -54,12 +49,7 @@ module.exports = function (userOptions) {
LOCALE_FILE_KEY,
STRATEGIES,
COMPONENT_OPTIONS_KEY,
getLocaleCodes,
getLocaleFromRoute,
getForwarded,
getHostname,
getLocaleDomain,
syncVuex
localeCodes: getLocaleCodes(options.locales)
}

// Generate localized routes
Expand Down
151 changes: 73 additions & 78 deletions src/plugins/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,84 +3,82 @@ import JsCookie from 'js-cookie'
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import { nuxtI18nSeo } from './seo-head'
import { validateRouteParams } from './utils'
import {
beforeLanguageSwitch,
defaultLocale,
detectBrowserLanguage,
differentDomains,
forwardedHost,
lazy,
localeCodes,
locales,
onLanguageSwitched,
STRATEGIES,
strategy,
vueI18n,
vuex
} from './options'
import { getLocaleDomain, getLocaleFromRoute, syncVuex, validateRouteParams } from './utils'

Vue.use(VueI18n)

// Options
const LOCALE_CODE_KEY = '<%= options.LOCALE_CODE_KEY %>'
const LOCALE_DOMAIN_KEY = '<%= options.LOCALE_DOMAIN_KEY %>'
const STRATEGIES = <%= JSON.stringify(options.STRATEGIES) %>
const STRATEGY = '<%= options.strategy %>'
const lazy = <%= options.lazy %>
const vuex = <%= JSON.stringify(options.vuex) %>
// Helpers
const getLocaleCodes = <%= options.getLocaleCodes %>
const localeCodes = getLocaleCodes(<%= JSON.stringify(options.locales) %>)

export default async (context) => {
const { app, route, store, req, res, redirect } = context

// Helpers
const getLocaleFromRoute = <%= options.getLocaleFromRoute %>
const getHostname = <%= options.getHostname %>
const getForwarded = <%= options.getForwarded %>
const getLocaleDomain = <%= options.getLocaleDomain %>
const syncVuex = <%= options.syncVuex %>

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

const detectBrowserLanguage = <%= JSON.stringify(options.detectBrowserLanguage) %>
const { useCookie, cookieKey } = detectBrowserLanguage

const getLocaleCookie = () => {
Expand All @@ -106,7 +104,7 @@ export default async (context) => {
})
} else if (res) {
let headers = res.getHeader('Set-Cookie') || []
if (typeof headers == 'string') {
if (typeof headers === 'string') {
headers = [headers]
}

Expand Down Expand Up @@ -159,9 +157,9 @@ export default async (context) => {
app.i18n.onLanguageSwitched(oldLocale, newLocale)
}

await syncVuex(newLocale, app.i18n.getLocaleMessage(newLocale))
await syncVuex(store, newLocale, app.i18n.getLocaleMessage(newLocale))

if (!initialSetup && STRATEGY !== STRATEGIES.NO_PREFIX) {
if (!initialSetup && strategy !== STRATEGIES.NO_PREFIX) {
const route = app.i18n.__route
const routeName = route && route.name ? app.getRouteBaseName(route) : 'index'
const redirectPath = app.localePath(Object.assign({}, route, { name: routeName }), newLocale)
Expand All @@ -173,13 +171,13 @@ export default async (context) => {
}

// Set instance options
app.i18n = new VueI18n(<%= JSON.stringify(options.vueI18n) %>)
app.i18n.locales = <%= JSON.stringify(options.locales) %>
app.i18n.defaultLocale = '<%= options.defaultLocale %>'
app.i18n.differentDomains = <%= options.differentDomains %>
app.i18n.forwardedHost = <%= options.forwardedHost %>
app.i18n.beforeLanguageSwitch = <%= options.beforeLanguageSwitch %>
app.i18n.onLanguageSwitched = <%= options.onLanguageSwitched %>
app.i18n = new VueI18n(vueI18n)
app.i18n.locales = locales
app.i18n.defaultLocale = defaultLocale
app.i18n.differentDomains = differentDomains
app.i18n.forwardedHost = forwardedHost
app.i18n.beforeLanguageSwitch = beforeLanguageSwitch
app.i18n.onLanguageSwitched = onLanguageSwitched
app.i18n.setLocaleCookie = setLocaleCookie
app.i18n.getLocaleCookie = getLocaleCookie
app.i18n.setLocale = (locale) => loadAndSetLocale(locale)
Expand All @@ -199,24 +197,21 @@ export default async (context) => {
let locale = app.i18n.defaultLocale || null

if (app.i18n.differentDomains) {
const domainLocale = getLocaleDomain()
locale = domainLocale ? domainLocale : locale
} else if (STRATEGY !== STRATEGIES.NO_PREFIX) {
const routesNameSeparator = '<%= options.routesNameSeparator %>'
const defaultLocaleRouteNameSuffix = '<%= options.defaultLocaleRouteNameSuffix %>'

const routeLocale = getLocaleFromRoute(route, routesNameSeparator, defaultLocaleRouteNameSuffix, app.i18n.locales)
locale = routeLocale ? routeLocale : locale
const domainLocale = getLocaleDomain(app.i18n, req)
locale = domainLocale || locale
} else if (strategy !== STRATEGIES.NO_PREFIX) {
const routeLocale = getLocaleFromRoute(route)
locale = routeLocale || locale
} else if (useCookie) {
locale = getLocaleCookie() || locale
locale = getLocaleCookie() || locale
}

await loadAndSetLocale(locale, { initialSetup: true })

app.i18n.__detectBrowserLanguage = async route => {
const { alwaysRedirect, fallbackLocale } = detectBrowserLanguage

if (detectBrowserLanguage) {
const { alwaysRedirect, fallbackLocale } = detectBrowserLanguage

let browserLocale

if (useCookie && (browserLocale = getLocaleCookie()) && browserLocale !== 1 && browserLocale !== '1') {
Expand Down
Loading

0 comments on commit 462e3b3

Please sign in to comment.