diff --git a/src/templates/head-meta.js b/src/templates/head-meta.js index 93a07019d..2cc9b199e 100644 --- a/src/templates/head-meta.js +++ b/src/templates/head-meta.js @@ -85,7 +85,7 @@ export function nuxtI18nHead ({ addDirAttribute = true, addSeoAttributes = false link.push({ hid: `i18n-alt-${iso}`, rel: 'alternate', - href: baseUrl + this.switchLocalePath(mapLocale.code), + href: toAbsoluteUrl(this.switchLocalePath(mapLocale.code), baseUrl), hreflang: iso }) } @@ -94,7 +94,7 @@ export function nuxtI18nHead ({ addDirAttribute = true, addSeoAttributes = false link.push({ hid: 'i18n-xd', rel: 'alternate', - href: baseUrl + this.switchLocalePath(defaultLocale), + href: toAbsoluteUrl(this.switchLocalePath(defaultLocale), baseUrl), hreflang: 'x-default' }) } @@ -105,17 +105,16 @@ export function nuxtI18nHead ({ addDirAttribute = true, addSeoAttributes = false ...this.$route, name: this.getRouteBaseName() }) + const canonicalPath = currentRoute ? currentRoute.path : null - if (!canonicalPath) { - return + if (canonicalPath) { + link.push({ + hid: 'i18n-can', + rel: 'canonical', + href: toAbsoluteUrl(canonicalPath, baseUrl) + }) } - - link.push({ - hid: 'i18n-can', - rel: 'canonical', - href: baseUrl + canonicalPath - }) } function addCurrentOgLocale (currentLocale, currentLocaleIso, meta) { @@ -156,6 +155,13 @@ export function nuxtI18nHead ({ addDirAttribute = true, addSeoAttributes = false return isoFromLocale(locale).replace(/-/g, '_') } + function toAbsoluteUrl (urlOrPath, baseUrl) { + if (urlOrPath.match(/^https?:\/\//)) { + return urlOrPath + } + return baseUrl + urlOrPath + } + return metaObject } diff --git a/src/templates/plugin.main.js b/src/templates/plugin.main.js index 9e5879f1a..107145dc9 100644 --- a/src/templates/plugin.main.js +++ b/src/templates/plugin.main.js @@ -189,7 +189,8 @@ export default async (context) => { return [302, storedRedirect] } - app.i18n.__baseUrl = resolveBaseUrl(baseUrl, context) + const options = { differentDomains, locales, localeDomainKey: LOCALE_DOMAIN_KEY, localeCodeKey: LOCALE_CODE_KEY, moduleName: MODULE_NAME } + app.i18n.__baseUrl = resolveBaseUrl(baseUrl, context, app.i18n.locale, options) const finalLocale = (detectBrowserLanguage && doDetectBrowserLanguage(route)) || @@ -273,7 +274,8 @@ export default async (context) => { app.i18n.localeProperties = { code: '' } app.i18n.fallbackLocale = vueI18nOptions.fallbackLocale || '' extendVueI18nInstance(app.i18n) - app.i18n.__baseUrl = resolveBaseUrl(baseUrl, context) + const options = { differentDomains, locales, localeDomainKey: LOCALE_DOMAIN_KEY, localeCodeKey: LOCALE_CODE_KEY, moduleName: MODULE_NAME } + app.i18n.__baseUrl = resolveBaseUrl(baseUrl, context, '', options) app.i18n.__onNavigate = onNavigate Vue.prototype.$nuxtI18nSeo = nuxtI18nSeo @@ -296,7 +298,7 @@ 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 options = { localDomainKey: LOCALE_DOMAIN_KEY, localeCodeKey: LOCALE_CODE_KEY } + const options = { localeDomainKey: LOCALE_DOMAIN_KEY, localeCodeKey: LOCALE_CODE_KEY } const domainLocale = getLocaleDomain(locales, req, options) finalLocale = domainLocale } else if (strategy !== STRATEGIES.NO_PREFIX) { diff --git a/src/templates/plugin.routing.js b/src/templates/plugin.routing.js index 6964d78a6..03525d5e7 100644 --- a/src/templates/plugin.routing.js +++ b/src/templates/plugin.routing.js @@ -12,6 +12,7 @@ import { trailingSlash, vuex } from './options' +import { getDomainFromLocale } from './utils-common' function localePath (route, locale) { const localizedRoute = localeRoute.call(this, route, locale) @@ -109,21 +110,19 @@ function switchLocalePath (locale) { // Handle different domains if (i18n.differentDomains) { - const lang = i18n.locales.find(l => l[LOCALE_CODE_KEY] === locale) - if (lang && lang[LOCALE_DOMAIN_KEY]) { - let protocol - if (process.server) { - const isHTTPS = require('is-https') - protocol = (this.req && isHTTPS(this.req)) ? 'https' : 'http' - } else { - protocol = window.location.protocol.split(':')[0] - } - path = protocol + '://' + lang[LOCALE_DOMAIN_KEY] + path - } else { - // eslint-disable-next-line no-console - console.warn(`[${MODULE_NAME}] Could not find domain name for locale ${locale}`) + const options = { + differentDomains: i18n.differentDomains, + locales: i18n.locales, + localeDomainKey: LOCALE_DOMAIN_KEY, + localeCodeKey: LOCALE_CODE_KEY, + moduleName: MODULE_NAME + } + const domain = getDomainFromLocale(locale, this.req, options) + if (domain) { + path = domain + path } } + return path } diff --git a/src/templates/utils-common.js b/src/templates/utils-common.js index efcd4cb8f..320b74af9 100644 --- a/src/templates/utils-common.js +++ b/src/templates/utils-common.js @@ -70,24 +70,60 @@ export const matchBrowserLocale = (appLocales, browserLocales) => { * Resolves base URL value if provided as function. Otherwise just returns verbatim. * @param {string | function} baseUrl * @param {import('@nuxt/types').Context} context + * @param {import('../../types').NuxtVueI18n.Locale} localeCode + * @param {object} options * @return {string} */ -export const resolveBaseUrl = (baseUrl, context) => { +export const resolveBaseUrl = (baseUrl, context, localeCode, { differentDomains, locales, localeDomainKey, localeCodeKey, moduleName }) => { if (typeof baseUrl === 'function') { return baseUrl(context) } + if (differentDomains && localeCode) { + // Lookup the `differentDomain` origin associated with given locale. + const domain = getDomainFromLocale(localeCode, context.req, { locales, localeDomainKey, localeCodeKey, moduleName }) + if (domain) { + return domain + } + } + return baseUrl } +/** + * Gets the `differentDomain` domain from locale. + * + * @param {string} localeCode The locale code + * @param {import('connect').IncomingMessage} [req] Request object + * @param {object} options + * @return {string | undefined} + */ +export const getDomainFromLocale = (localeCode, req, { locales, localeDomainKey, localeCodeKey, moduleName }) => { +// Lookup the `differentDomain` origin associated with given locale. + const lang = locales.find(locale => locale[localeCodeKey] === localeCode) + if (lang && lang[localeDomainKey]) { + let protocol + if (process.server) { + const isHTTPS = require('is-https') + protocol = (req && isHTTPS(req)) ? 'https' : 'http' + } else { + protocol = window.location.protocol.split(':')[0] + } + return `${protocol}://${lang[localeDomainKey]}` + } + + // eslint-disable-next-line no-console + console.warn(`[${moduleName}] Could not find domain name for locale ${localeCode}`) +} + /** * Get locale code that corresponds to current hostname * @param {object} locales * @param {object} [req] Request object - * @param {{ localDomainKey: string, localeCodeKey: string }} options + * @param {{ localeDomainKey: string, localeCodeKey: string }} options * @return {string | null} Locade code found if any */ -export const getLocaleDomain = (locales, req, { localDomainKey, localeCodeKey }) => { +export const getLocaleDomain = (locales, req, { localeDomainKey, localeCodeKey }) => { let host = null if (process.client) { @@ -97,7 +133,7 @@ export const getLocaleDomain = (locales, req, { localDomainKey, localeCodeKey }) } if (host) { - const matchingLocale = locales.find(l => l[localDomainKey] === host) + const matchingLocale = locales.find(l => l[localeDomainKey] === host) if (matchingLocale) { return matchingLocale[localeCodeKey] } diff --git a/test/fixture/basic/pages/locale.vue b/test/fixture/basic/pages/locale.vue index 24c32acda..0e31fa67d 100644 --- a/test/fixture/basic/pages/locale.vue +++ b/test/fixture/basic/pages/locale.vue @@ -5,7 +5,7 @@ diff --git a/test/module.test.js b/test/module.test.js index 42a738882..e3a337775 100644 --- a/test/module.test.js +++ b/test/module.test.js @@ -147,6 +147,67 @@ describe('differentDomains enabled', () => { const dom = getDom(html) expect(dom.documentElement.getAttribute('dir')).toEqual('auto') }) + + test('dir and SEO attributes exists', async () => { + const requestOptions = { + headers: { + Host: 'en.nuxt-app.localhost' + } + } + const html = await get('/locale', requestOptions) + const dom = getDom(html) + expect(dom.documentElement.getAttribute('dir')).toEqual('ltr') + + const seoTags = getSeoTags(dom) + const expectedSeoTags = [ + { + tagName: 'meta', + property: 'og:locale', + content: 'en_US' + }, + { + tagName: 'meta', + property: 'og:locale:alternate', + content: 'fr_FR' + }, + { + tagName: 'link', + rel: 'alternate', + href: 'http://en.nuxt-app.localhost/locale', + hreflang: 'en' + }, + { + tagName: 'link', + rel: 'alternate', + href: 'http://en.nuxt-app.localhost/locale', + hreflang: 'en-US' + }, + { + tagName: 'link', + rel: 'alternate', + href: 'http://fr.nuxt-app.localhost/locale', + hreflang: 'fr' + }, + { + tagName: 'link', + rel: 'alternate', + href: 'http://fr.nuxt-app.localhost/locale', + hreflang: 'fr-FR' + }, + { + tagName: 'link', + rel: 'alternate', + href: 'http://en.nuxt-app.localhost/locale', + hreflang: 'x-default' + }, + { + tagName: 'link', + rel: 'canonical', + href: 'http://en.nuxt-app.localhost/locale' + } + ] + expect(seoTags).toEqual(expectedSeoTags) + }) }) const TRAILING_SLASHES = [undefined, false, true]