Skip to content

Commit e99978d

Browse files
committedApr 30, 2020
fix: redirect from 404 to defaultLocale if there is matching route
Changes the `setLocale` logic to, in case current route is 404, to try to find a matching one for current locale. This is for situations when using `prefix` strategy where the root (`/`) route doesn't exist. We will try to find and redirect to prefixed route matching resolved locale. Also worked around Nuxt issue (nuxt/nuxt#4491 ) with `redirect` not working when called from a plugin in SPA mode. Required for the above fix above to be functional in SPA. Resolves #677 Resolves #491

14 files changed

+232
-173
lines changed
 

‎docs/es/options-reference.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ Aquí están todas las opciones disponibles al configurar el módulo y sus valor
2323
// - code of ISO 639-1 and code of ISO 3166-1 alpha-2, with a hyphen (e.g. 'en-US')
2424
locales: [],
2525

26-
// The app's default locale, URLs for this locale won't have a prefix if
27-
// strategy is prefix_except_default
26+
// The app's default locale.
27+
// When using 'prefix_except_default' strategy, URLs for this locale won't have a prefix.
28+
// It's recommended to set this to some locale regardless of chosen strategy, as it will be
29+
// used as a locale fallback to use when navigating to a non-existent route.
2830
defaultLocale: null,
2931

3032
// Separator used to generated routes name for each locale, you shouldn't

‎docs/es/routing.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ Con esta estrategia, todas las rutas tendrán un prefijo de configuración local
6363
6464
Esta estrategia combina los comportamientos de ambas estrategias anteriores, lo que significa que obtendrá URL con prefijos para cada idioma, pero las URL para el idioma predeterminado también tendrán una versión sin prefijo.
6565
66-
Para configurar la estrategia, use la opción `strategy`. Asegúrese de tener un `defaultLocale` definido si usa **prefix_except_default**, **prefix_and_default** o la estrategia **no_prefix**.
67-
66+
Para configurar la estrategia, use la opción `strategy`.
67+
Make sure that you have a `defaultLocale` defined, especially if using **prefix_except_default**, **prefix_and_default** or **no_prefix** strategy. For other strategies it's also recommended to set it as it's gonna be used as a fallback when attempting to redirect from 404 page.
6868
6969
```js
7070
// nuxt.config.js

‎docs/options-reference.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,10 @@ Here are all the options available when configuring the module and their default
3636
// - code of ISO 639-1 and code of ISO 3166-1 alpha-2, with a hyphen (e.g. 'en-US')
3737
locales: [],
3838

39-
// The app's default locale, URLs for this locale won't have a prefix if
40-
// strategy is prefix_except_default
39+
// The app's default locale.
40+
// When using 'prefix_except_default' strategy, URLs for this locale won't have a prefix.
41+
// It's recommended to set this to some locale regardless of chosen strategy, as it will be
42+
// used as a locale fallback to use when navigating to a non-existent route.
4143
defaultLocale: null,
4244

4345
// Separator used to generated routes name for each locale, you shouldn't

‎docs/routing.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ This strategy combines both previous strategies behaviours, meaning that you wil
6666
6767
### Configuration
6868
69-
To configure the strategy, use the `strategy` option. Make sure that you have a `defaultLocale` defined if using **prefix_except_default**, **prefix_and_default** or **no_prefix** strategy.
70-
69+
To configure the strategy, use the `strategy` option.
70+
Make sure that you have a `defaultLocale` defined, especially if using **prefix_except_default**, **prefix_and_default** or **no_prefix** strategy. For other strategies it's also recommended to set it as it's gonna be used as a fallback when attempting to redirect from 404 page.
7171
7272
```js
7373
// nuxt.config.js

‎package.json

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
},
2424
"scripts": {
2525
"dev:basic": "nuxt -c ./test/fixture/basic/nuxt.config.js",
26+
"dev:basic:generate": "nuxt generate -c ./test/fixture/basic/nuxt.config.js",
2627
"coverage": "codecov",
2728
"lint": "eslint --ext .js,.vue,.ts src test types",
2829
"test": "yarn test:types && yarn test:unit && yarn test:e2e-ssr && yarn test:e2e-browser",

‎src/templates/middleware.js

+2-28
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,11 @@
11
import middleware from '../middleware'
2-
import { baseUrl, detectBrowserLanguage, rootRedirect } from './options'
3-
import { getLocaleFromRoute } from './utils'
4-
import { resolveBaseUrl } from './utils-common'
52

63
middleware.nuxti18n = async (context) => {
7-
const { app, route, redirect, isHMR } = context
4+
const { app, isHMR } = context
85

96
if (isHMR) {
107
return
118
}
129

13-
// Handle root path redirect
14-
if (route.path === '/' && rootRedirect) {
15-
let statusCode = 302
16-
let path = rootRedirect
17-
18-
if (typeof rootRedirect !== 'string') {
19-
statusCode = rootRedirect.statusCode
20-
path = rootRedirect.path
21-
}
22-
23-
redirect(statusCode, '/' + path, route.query)
24-
return
25-
}
26-
27-
app.i18n.__baseUrl = resolveBaseUrl(baseUrl, context)
28-
29-
if (detectBrowserLanguage && await app.i18n.__detectBrowserLanguage()) {
30-
return
31-
}
32-
33-
const locale = app.i18n.locale || app.i18n.defaultLocale || ''
34-
const routeLocale = getLocaleFromRoute(route)
35-
36-
await app.i18n.setLocale(routeLocale || locale)
10+
await app.i18n.__onNavigate()
3711
}

‎src/templates/plugin.main.js

+103-49
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
localeCodes,
1414
locales,
1515
onLanguageSwitched,
16+
rootRedirect,
1617
STRATEGIES,
1718
strategy,
1819
vueI18n,
@@ -21,7 +22,6 @@ import {
2122
import {
2223
getLocaleDomain,
2324
getLocaleFromRoute,
24-
isSameRoute,
2525
syncVuex,
2626
validateRouteParams
2727
} from './utils'
@@ -173,21 +173,105 @@ export default async (context) => {
173173

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

176-
if (!initialSetup) {
176+
const redirectPath = getRedirectPathForLocale(newLocale)
177+
178+
if (initialSetup) {
179+
// Redirect will be delayed until middleware runs as redirecting from plugin does not
180+
// work in SPA (https://github.com/nuxt/nuxt.js/issues/4491).
181+
app.i18n.__redirect = redirectPath
182+
} else {
177183
app.i18n.onLanguageSwitched(oldLocale, newLocale)
184+
185+
if (redirectPath) {
186+
redirect(redirectPath)
187+
}
188+
}
189+
}
190+
191+
const getRedirectPathForLocale = locale => {
192+
if (!locale || app.i18n.differentDomains || strategy === STRATEGIES.NO_PREFIX) {
193+
return
178194
}
179195

180-
if (!initialSetup && strategy !== STRATEGIES.NO_PREFIX) {
181-
const redirectPath = app.switchLocalePath(newLocale) || app.localePath('index', newLocale)
182-
const redirectRoute = app.router.resolve(redirectPath).route
196+
// Must retrieve from context as it might have changed since plugin initialization.
197+
const { route } = context
198+
const routeLocale = getLocaleFromRoute(route)
183199

184-
// Must retrieve from context as it might have changed since plugin initialization.
185-
const { route } = context
200+
if (routeLocale === locale) {
201+
return
202+
}
186203

187-
if (route && !isSameRoute(route, redirectRoute)) {
188-
redirect(redirectPath)
204+
// At this point we are left with route that either no or different locale.
205+
let redirectPath = app.switchLocalePath(locale)
206+
207+
if (!redirectPath) {
208+
// Could be a 404 route in which case we should attemp to find matching route for given locale.
209+
redirectPath = app.localePath(route.path, locale)
210+
}
211+
212+
return redirectPath
213+
}
214+
215+
const doDetectBrowserLanguage = () => {
216+
const { alwaysRedirect, fallbackLocale } = detectBrowserLanguage
217+
218+
let matchedLocale
219+
220+
if (useCookie && (matchedLocale = getLocaleCookie())) {
221+
// Get preferred language from cookie if present and enabled
222+
} else if (process.client && typeof navigator !== 'undefined' && navigator.languages) {
223+
// Get browser language either from navigator if running on client side, or from the headers
224+
matchedLocale = matchBrowserLocale(localeCodes, navigator.languages)
225+
} else if (req && typeof req.headers['accept-language'] !== 'undefined') {
226+
matchedLocale = matchBrowserLocale(localeCodes, parseAcceptLanguage(req.headers['accept-language']))
227+
}
228+
229+
const finalLocale = matchedLocale || fallbackLocale
230+
231+
// Handle cookie option to prevent multiple redirections
232+
if (finalLocale && (!useCookie || alwaysRedirect || !getLocaleCookie())) {
233+
if (finalLocale !== app.i18n.locale) {
234+
return finalLocale
235+
} else if (useCookie && !getLocaleCookie()) {
236+
app.i18n.setLocaleCookie(finalLocale)
189237
}
190238
}
239+
240+
return false
241+
}
242+
243+
// Called by middleware on navigation (also on the initial one).
244+
const onNavigate = async () => {
245+
const { route } = context
246+
247+
// Handle root path redirect
248+
if (route.path === '/' && rootRedirect) {
249+
let statusCode = 302
250+
let path = rootRedirect
251+
252+
if (typeof rootRedirect !== 'string') {
253+
statusCode = rootRedirect.statusCode
254+
path = rootRedirect.path
255+
}
256+
257+
redirect(statusCode, `/${path}`, route.query)
258+
return
259+
}
260+
261+
const storedRedirect = app.i18n.__redirect
262+
if (storedRedirect) {
263+
app.i18n.__redirect = null
264+
redirect(storedRedirect)
265+
return
266+
}
267+
268+
app.i18n.__baseUrl = resolveBaseUrl(baseUrl, context)
269+
270+
const finalLocale =
271+
(detectBrowserLanguage && doDetectBrowserLanguage()) ||
272+
getLocaleFromRoute(route) || app.i18n.locale || app.i18n.defaultLocale || ''
273+
274+
await app.i18n.setLocale(finalLocale)
191275
}
192276

193277
// Set instance options
@@ -198,13 +282,14 @@ export default async (context) => {
198282
app.i18n.fallbackLocale = vueI18nOptions.fallbackLocale || ''
199283
app.i18n.locales = locales
200284
app.i18n.defaultLocale = defaultLocale
201-
app.i18n.__baseUrl = resolveBaseUrl(baseUrl, context)
202285
app.i18n.differentDomains = differentDomains
203286
app.i18n.beforeLanguageSwitch = beforeLanguageSwitch
204287
app.i18n.onLanguageSwitched = onLanguageSwitched
205288
app.i18n.setLocaleCookie = setLocaleCookie
206289
app.i18n.getLocaleCookie = getLocaleCookie
207290
app.i18n.setLocale = (locale) => loadAndSetLocale(locale)
291+
app.i18n.__baseUrl = resolveBaseUrl(baseUrl, context)
292+
app.i18n.__onNavigate = onNavigate
208293

209294
// Inject seo function
210295
Vue.prototype.$nuxtI18nSeo = nuxtI18nSeo
@@ -220,52 +305,21 @@ export default async (context) => {
220305
}
221306
}
222307

223-
let locale = app.i18n.defaultLocale || ''
308+
let finalLocale = app.i18n.defaultLocale || ''
224309

225310
if (vuex && vuex.syncLocale && store && store.state[vuex.moduleName].locale !== '') {
226-
locale = store.state[vuex.moduleName].locale
311+
finalLocale = store.state[vuex.moduleName].locale
227312
} else if (app.i18n.differentDomains) {
228313
const domainLocale = getLocaleDomain(app.i18n, req)
229-
locale = domainLocale || locale
314+
finalLocale = domainLocale || finalLocale
230315
} else if (strategy !== STRATEGIES.NO_PREFIX) {
231316
const routeLocale = getLocaleFromRoute(route)
232-
locale = routeLocale || locale
317+
finalLocale = routeLocale || finalLocale
233318
} else if (useCookie) {
234-
locale = getLocaleCookie() || locale
235-
}
236-
237-
await loadAndSetLocale(locale, { initialSetup: true })
238-
239-
app.i18n.__detectBrowserLanguage = async () => {
240-
if (detectBrowserLanguage) {
241-
const { alwaysRedirect, fallbackLocale } = detectBrowserLanguage
242-
243-
let matchedLocale
244-
245-
if (useCookie && (matchedLocale = getLocaleCookie())) {
246-
// Get preferred language from cookie if present and enabled
247-
} else if (process.client && typeof navigator !== 'undefined' && navigator.languages) {
248-
// Get browser language either from navigator if running on client side, or from the headers
249-
matchedLocale = matchBrowserLocale(localeCodes, navigator.languages)
250-
} else if (req && typeof req.headers['accept-language'] !== 'undefined') {
251-
matchedLocale = matchBrowserLocale(localeCodes, parseAcceptLanguage(req.headers['accept-language']))
252-
}
253-
254-
matchedLocale = matchedLocale || fallbackLocale
255-
256-
// Handle cookie option to prevent multiple redirections
257-
if (matchedLocale && (!useCookie || alwaysRedirect || !getLocaleCookie())) {
258-
if (matchedLocale !== app.i18n.locale) {
259-
await app.i18n.setLocale(matchedLocale)
260-
return true
261-
} else if (useCookie && !getLocaleCookie()) {
262-
app.i18n.setLocaleCookie(matchedLocale)
263-
}
264-
}
265-
}
266-
267-
return false
319+
finalLocale = getLocaleCookie() || finalLocale
268320
}
269321

270-
await app.i18n.__detectBrowserLanguage()
322+
const detectedBrowserLocale = detectBrowserLanguage && doDetectBrowserLanguage()
323+
finalLocale = detectedBrowserLocale || finalLocale
324+
await loadAndSetLocale(finalLocale, { initialSetup: true })
271325
}

‎src/templates/plugin.routing.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ function switchLocalePath (locale) {
131131
}
132132

133133
function getRouteBaseName (givenRoute) {
134-
const route = givenRoute || this.route
134+
const route = givenRoute !== undefined ? givenRoute : this.route
135135
if (!route.name) {
136136
return null
137137
}

‎src/templates/utils.js

-59
Original file line numberDiff line numberDiff line change
@@ -70,65 +70,6 @@ export const validateRouteParams = routeParams => {
7070
})
7171
}
7272

73-
const trailingSlashRE = /\/?$/
74-
75-
/**
76-
* Determines if objects are equal.
77-
*
78-
* @param {Object} [a={}]
79-
* @param {Object} [b={}]
80-
* @return {boolean} True if objects equal, False otherwise.
81-
*/
82-
function isObjectEqual (a = {}, b = {}) {
83-
// handle null value #1566
84-
if (!a || !b) {
85-
return a === b
86-
}
87-
const aKeys = Object.keys(a)
88-
const bKeys = Object.keys(b)
89-
if (aKeys.length !== bKeys.length) {
90-
return false
91-
}
92-
return aKeys.every(key => {
93-
const aVal = a[key]
94-
const bVal = b[key]
95-
// check nested equality
96-
if (typeof aVal === 'object' && typeof bVal === 'object') {
97-
return isObjectEqual(aVal, bVal)
98-
}
99-
return String(aVal) === String(bVal)
100-
})
101-
}
102-
103-
/**
104-
* Determines if two routes are the same.
105-
*
106-
* @param {Route} a
107-
* @param {Route} [b]
108-
* @return {boolean} True if routes the same, False otherwise.
109-
*/
110-
export function isSameRoute (a, b) {
111-
if (!b) {
112-
return false
113-
}
114-
if (a.path && b.path) {
115-
return (
116-
a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') &&
117-
a.hash === b.hash &&
118-
isObjectEqual(a.query, b.query)
119-
)
120-
}
121-
if (a.name && b.name) {
122-
return (
123-
a.name === b.name &&
124-
a.hash === b.hash &&
125-
isObjectEqual(a.query, b.query) &&
126-
isObjectEqual(a.params, b.params)
127-
)
128-
}
129-
return false
130-
}
131-
13273
/**
13374
* Get locale code that corresponds to current hostname
13475
* @param {VueI18n} nuxtI18n Instance of VueI18n

‎test/browser.test.js

+55
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,25 @@ describe(browserString, () => {
8787
expect(await page.getText('body')).toContain('page: À propos')
8888
})
8989

90+
test('changes route and locale with setLocale', async () => {
91+
page = await browser.page(url('/'))
92+
93+
let testData = await page.getTestData()
94+
expect(testData.languageSwitchedListeners).toEqual([])
95+
96+
await page.clickElement('#set-locale-link-fr')
97+
98+
testData = await page.getTestData()
99+
expect(testData.languageSwitchedListeners).toEqual([
100+
{
101+
storeLocale: 'fr',
102+
newLocale: 'fr',
103+
oldLocale: 'en'
104+
}
105+
])
106+
expect(await page.getText('body')).toContain('locale: fr')
107+
})
108+
90109
test('onLanguageSwitched listener triggers after locale was changed', async () => {
91110
page = await browser.page(url('/'))
92111

@@ -103,6 +122,7 @@ describe(browserString, () => {
103122
oldLocale: 'en'
104123
}
105124
])
125+
expect(await page.getText('body')).toContain('locale: fr')
106126
})
107127

108128
test('APIs in app context work after SPA navigation', async () => {
@@ -264,6 +284,41 @@ describe(`${browserString} (SPA with router in hash mode)`, () => {
264284
})
265285
})
266286

287+
describe(`${browserString} (alwaysRedirect)`, () => {
288+
let nuxt
289+
let browser
290+
291+
beforeAll(async () => {
292+
const overrides = {
293+
i18n: {
294+
defaultLocale: 'en',
295+
strategy: 'prefix',
296+
detectBrowserLanguage: {
297+
useCookie: true,
298+
alwaysRedirect: true
299+
}
300+
}
301+
}
302+
303+
nuxt = (await setup(loadConfig(__dirname, 'basic', overrides, { merge: true }))).nuxt
304+
browser = await createDefaultBrowser()
305+
})
306+
307+
afterAll(async () => {
308+
if (browser) {
309+
await browser.close()
310+
}
311+
await nuxt.close()
312+
})
313+
314+
// This seems like a wrong behavior with `alwaysRedirect` enabled...
315+
test('does not redirect to default locale on navigation', async () => {
316+
const page = await browser.page(url('/'))
317+
await page.navigate('/fr')
318+
expect(await page.getText('body')).toContain('locale: fr')
319+
})
320+
})
321+
267322
describe(`${browserString} (vuex disabled)`, () => {
268323
let nuxt
269324
let browser

‎test/fixture/basic/components/LangSwitcher.vue

+18-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
11
<template>
2-
<div id="lang-switcher">
3-
<nuxt-link
4-
v-for="(locale, index) in localesExcludingCurrent"
5-
:key="index"
6-
:exact="true"
7-
:to="switchLocalePath(locale.code)">{{ locale.name }}</nuxt-link>
2+
<div>
3+
<strong>Using nuxt-link</strong>:
4+
<div id="lang-switcher">
5+
<nuxt-link
6+
v-for="(locale, index) in localesExcludingCurrent"
7+
:key="index"
8+
:exact="true"
9+
:to="switchLocalePath(locale.code)">{{ locale.name }}</nuxt-link>
10+
</div>
11+
<strong>Using setLocale()</strong>:
12+
<div>
13+
<a
14+
v-for="(locale, index) in localesExcludingCurrent"
15+
:id="`set-locale-link-${locale.code}`"
16+
:key="`b-${index}`"
17+
href="#"
18+
@click.prevent="$i18n.setLocale(locale.code)">{{ locale.name }}</a>
19+
</div>
820
</div>
921
</template>
1022

‎test/fixture/basic/pages/posts/_slug.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<template>
22
<div>
33
<h2>{{ $route.params.slug }}</h2>
4-
<nuxt-link exact :to="localePath('posts')">index</nuxt-link>
4+
<nuxt-link id="post-link" exact :to="localePath('posts')">index</nuxt-link>
55
</div>
66
</template>
77

‎test/fixture/basic/pages/posts/index.vue

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<template>
22
<div>
33
<nuxt-link
4+
id="post-link"
45
exact
56
:to="localePath({
67
name: 'posts-slug',

‎test/module.test.js

+38-21
Original file line numberDiff line numberDiff line change
@@ -138,20 +138,9 @@ describe('basic', () => {
138138
})
139139

140140
test('/notlocalized & /fr/fr/notlocalized return 404', async () => {
141-
let response
142-
try {
143-
response = await get('/notlocalized')
144-
} catch (error) {
145-
response = error
146-
}
147-
expect(response.statusCode).toBe(404)
148-
149-
try {
150-
response = await get('/fr/fr/notlocalized')
151-
} catch (error) {
152-
response = error
153-
}
154-
expect(response.statusCode).toBe(404)
141+
expect.assertions(2)
142+
await get('/notlocalized').catch(error => expect(error.statusCode).toBe(404))
143+
await get('/fr/fr/notlocalized').catch(error => expect(error.statusCode).toBe(404))
155144
})
156145

157146
test('route specifies options with non-supported locale', async () => {
@@ -168,9 +157,8 @@ describe('basic', () => {
168157
const getElements = () => {
169158
const dom = getDom(html)
170159
title = dom.querySelector('h1')
171-
const links = [...dom.querySelectorAll('a')]
172-
langSwitcherLink = links[0]
173-
link = links[1]
160+
langSwitcherLink = dom.querySelector('#lang-switcher a')
161+
link = dom.querySelector('#post-link')
174162
}
175163

176164
test('/posts contains EN text, link to /fr/articles/ & link to /posts/my-post', async () => {
@@ -227,10 +215,10 @@ describe('basic', () => {
227215
test('navigates to child route with nameless parent and checks path to other locale', async () => {
228216
const window = await nuxt.renderAndGetWindow(url('/posts'))
229217

230-
const links = window.document.querySelectorAll('a')
231-
expect(links.length).toBe(2)
232-
expect(links[0].getAttribute('href')).toEqual('/fr/articles/')
233-
expect(links[1].getAttribute('href')).toEqual('/posts/my-post')
218+
const langSwitcherLink = window.document.querySelector('#lang-switcher a')
219+
const link = window.document.querySelector('#post-link')
220+
expect(langSwitcherLink.getAttribute('href')).toEqual('/fr/articles/')
221+
expect(link.getAttribute('href')).toEqual('/posts/my-post')
234222
})
235223

236224
test('navigates to dynamic child route and checks path to other locale', async () => {
@@ -1149,3 +1137,32 @@ describe('no_prefix + detectBrowserLanguage + alwaysRedirect', () => {
11491137
expect(dom.querySelector('#current-locale').textContent).toBe('locale: en')
11501138
})
11511139
})
1140+
1141+
describe('prefix + detectBrowserLanguage + alwaysRedirect', () => {
1142+
let nuxt
1143+
1144+
beforeAll(async () => {
1145+
const override = {
1146+
i18n: {
1147+
defaultLocale: 'fr',
1148+
strategy: 'prefix',
1149+
detectBrowserLanguage: {
1150+
useCookie: true,
1151+
alwaysRedirect: true
1152+
}
1153+
}
1154+
}
1155+
1156+
nuxt = (await setup(loadConfig(__dirname, 'basic', override, { merge: true }))).nuxt
1157+
})
1158+
1159+
afterAll(async () => {
1160+
await nuxt.close()
1161+
})
1162+
1163+
test('redirects to defaultLocale on navigating to root (non-existant) route', async () => {
1164+
const html = await get('/')
1165+
const dom = getDom(html)
1166+
expect(dom.querySelector('#current-locale').textContent).toBe('locale: fr')
1167+
})
1168+
})

0 commit comments

Comments
 (0)
Please sign in to comment.