Skip to content

Commit 13e647f

Browse files
fix: provide URL encoded paths in RouteLocation (#76)
* fix: provide URL encoded paths in RouteLocation * fix: handle Vue2 routing Co-authored-by: Bobbie Goede <[email protected]> * refactor: proper typing * refactor: removed obsolete/wrong imports & docs --------- Co-authored-by: Bobbie Goede <[email protected]>
1 parent 244902a commit 13e647f

File tree

4 files changed

+353
-5
lines changed

4 files changed

+353
-5
lines changed

packages/vue-i18n-routing/src/__test__/compatibles.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,11 @@ describe('switchLocalePath', () => {
392392
},
393393
component: { template: '<div>Category</div>' }
394394
},
395+
{
396+
path: '/count/:id',
397+
name: 'count',
398+
component: { template: '<div>Category</div>' }
399+
},
395400
{
396401
path: '/:pathMatch(.*)*',
397402
name: 'not-found',
@@ -428,11 +433,31 @@ describe('switchLocalePath', () => {
428433
assert.equal(vm.switchLocalePath('fr'), '/fr/about?foo=1&test=2')
429434
assert.equal(vm.switchLocalePath('en'), '/en/about?foo=1&test=2')
430435

436+
await router.push('/ja/about?foo=bär&four=四&foo=bar')
437+
assert.equal(vm.switchLocalePath('ja'), '/ja/about?foo=b%C3%A4r&foo=bar&four=%E5%9B%9B')
438+
assert.equal(vm.switchLocalePath('fr'), '/fr/about?foo=b%C3%A4r&foo=bar&four=%E5%9B%9B')
439+
assert.equal(vm.switchLocalePath('en'), '/en/about?foo=b%C3%A4r&foo=bar&four=%E5%9B%9B')
440+
441+
await router.push('/ja/about?foo=bär&four=四')
442+
assert.equal(vm.switchLocalePath('ja'), '/ja/about?foo=b%C3%A4r&four=%E5%9B%9B')
443+
assert.equal(vm.switchLocalePath('fr'), '/fr/about?foo=b%C3%A4r&four=%E5%9B%9B')
444+
assert.equal(vm.switchLocalePath('en'), '/en/about?foo=b%C3%A4r&four=%E5%9B%9B')
445+
431446
await router.push('/ja/category/1')
432447
assert.equal(vm.switchLocalePath('ja'), '/ja/category/japanese')
433448
assert.equal(vm.switchLocalePath('en'), '/en/category/english')
434449
assert.equal(vm.switchLocalePath('fr'), '/fr/category/franch')
435450

451+
await router.push('/ja/count/三')
452+
assert.equal(vm.switchLocalePath('ja'), '/ja/count/%E4%B8%89')
453+
assert.equal(vm.switchLocalePath('en'), '/en/count/%E4%B8%89')
454+
assert.equal(vm.switchLocalePath('fr'), '/fr/count/%E4%B8%89')
455+
456+
await router.push('/ja/count/三?foo=bär&four=四&foo=bar')
457+
assert.equal(vm.switchLocalePath('ja'), '/ja/count/%E4%B8%89?foo=b%C3%A4r&foo=bar&four=%E5%9B%9B')
458+
assert.equal(vm.switchLocalePath('fr'), '/fr/count/%E4%B8%89?foo=b%C3%A4r&foo=bar&four=%E5%9B%9B')
459+
assert.equal(vm.switchLocalePath('en'), '/en/count/%E4%B8%89?foo=b%C3%A4r&foo=bar&four=%E5%9B%9B')
460+
436461
await router.push('/ja/foo')
437462
assert.equal(vm.switchLocalePath('ja'), '/ja/not-found-japanese')
438463
assert.equal(vm.switchLocalePath('en'), '/en/not-found-english')

packages/vue-i18n-routing/src/__test__/utils.test.ts

Lines changed: 287 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { describe, it, assert, test } from 'vitest'
22

3+
import { resolvedRouteToObject } from '../compatibles/utils'
34
import { adjustRoutePathForTrailingSlash, getLocaleRouteName, findBrowserLocale } from '../utils'
45

56
import type { BrowserLocale } from '../utils'
67

78
describe('adjustRouteDefinitionForTrailingSlash', function () {
89
describe('pagePath: /foo/bar', function () {
9-
describe('trailingSlash: faawklse, isChildWithRelativePath: true', function () {
10+
describe('trailingSlash: false, isChildWithRelativePath: true', function () {
1011
it('should be trailed with slash: /foo/bar/', function () {
1112
assert.equal(adjustRoutePathForTrailingSlash('/foo/bar', true, true), '/foo/bar/')
1213
})
@@ -220,3 +221,288 @@ describe('findBrowserLocale', () => {
220221
assert.ok(locale === 'ja')
221222
})
222223
})
224+
225+
describe('resolvedRouteToObject', () => {
226+
it('should map resolved route without special characters', () => {
227+
const expected = {
228+
fullPath: '/ja/about',
229+
hash: '',
230+
query: {},
231+
name: 'about___ja',
232+
path: '/ja/about',
233+
params: {},
234+
matched: [
235+
{
236+
path: '/ja/about',
237+
redirect: undefined,
238+
name: 'about___ja',
239+
meta: {},
240+
aliasOf: undefined,
241+
beforeEnter: undefined,
242+
props: {
243+
default: false
244+
},
245+
children: [],
246+
instances: {},
247+
leaveGuards: {},
248+
updateGuards: {},
249+
enterCallbacks: {},
250+
components: {
251+
default: {
252+
template: '<div>About</div>'
253+
}
254+
}
255+
}
256+
],
257+
meta: {},
258+
redirectedFrom: undefined,
259+
href: '/ja/about'
260+
}
261+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
262+
assert.deepEqual(resolvedRouteToObject(expected as any), expected as any)
263+
})
264+
265+
it('should map resolved route without special characters and query', () => {
266+
const expected = {
267+
fullPath: '/ja/about?foo=1&test=2',
268+
hash: '',
269+
query: {
270+
foo: '1',
271+
test: '2'
272+
},
273+
name: 'about___ja',
274+
path: '/ja/about',
275+
params: {},
276+
matched: [
277+
{
278+
path: '/ja/about',
279+
redirect: undefined,
280+
name: 'about___ja',
281+
meta: {},
282+
aliasOf: undefined,
283+
beforeEnter: undefined,
284+
props: {
285+
default: false
286+
},
287+
children: [],
288+
instances: {},
289+
leaveGuards: {},
290+
updateGuards: {},
291+
enterCallbacks: {},
292+
components: {
293+
default: {
294+
template: '<div>About</div>'
295+
}
296+
}
297+
}
298+
],
299+
meta: {},
300+
redirectedFrom: undefined,
301+
href: '/ja/about?foo=1&test=2'
302+
}
303+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
304+
assert.deepEqual(resolvedRouteToObject(expected as any), expected as any)
305+
})
306+
307+
it('should map resolved route without special characters and query with special characters', () => {
308+
const expected = {
309+
fullPath: '/ja/about?foo=b%C3%A4r&foo=bar&four=%E5%9B%9B',
310+
hash: '',
311+
query: {
312+
foo: ['bär', 'bar'],
313+
four: '四'
314+
},
315+
name: 'about___ja',
316+
path: '/ja/about',
317+
params: {},
318+
matched: [
319+
{
320+
path: '/ja/about',
321+
redirect: undefined,
322+
name: 'about___ja',
323+
meta: {},
324+
aliasOf: undefined,
325+
beforeEnter: undefined,
326+
props: {
327+
default: false
328+
},
329+
children: [],
330+
instances: {},
331+
leaveGuards: {},
332+
updateGuards: {},
333+
enterCallbacks: {},
334+
components: {
335+
default: {
336+
template: '<div>About</div>'
337+
}
338+
}
339+
}
340+
],
341+
meta: {},
342+
redirectedFrom: undefined,
343+
href: '/ja/about?foo=b%C3%A4r&foo=bar&four=%E5%9B%9B'
344+
}
345+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
346+
assert.deepEqual(resolvedRouteToObject(expected as any), expected as any)
347+
})
348+
349+
it('should map resolved route with special characters and query with special characters', () => {
350+
const provided = {
351+
fullPath: '/ja/count/三?foo=b%C3%A4r&foo=bar&four=%E5%9B%9B',
352+
hash: '',
353+
query: {
354+
foo: ['bär', 'bar'],
355+
four: '四'
356+
},
357+
name: 'count___ja',
358+
path: '/ja/count/三',
359+
params: {
360+
id: '三'
361+
},
362+
matched: [
363+
{
364+
path: '/ja/count/:id',
365+
redirect: undefined,
366+
name: 'count___ja',
367+
meta: {},
368+
aliasOf: undefined,
369+
beforeEnter: undefined,
370+
props: {
371+
default: false
372+
},
373+
children: [],
374+
instances: {},
375+
leaveGuards: {},
376+
updateGuards: {},
377+
enterCallbacks: {},
378+
components: {
379+
default: {
380+
template: '<div>Category</div>'
381+
}
382+
}
383+
}
384+
],
385+
meta: {},
386+
redirectedFrom: undefined,
387+
href: '/ja/count/%E5%9B%9B?foo=b%C3%A4r&foo=bar&four=%E5%9B%9B'
388+
}
389+
const expected = {
390+
fullPath: '/ja/count/%E4%B8%89?foo=b%C3%A4r&foo=bar&four=%E5%9B%9B',
391+
hash: '',
392+
query: {
393+
foo: ['bär', 'bar'],
394+
four: '四'
395+
},
396+
name: 'count___ja',
397+
path: '/ja/count/%E4%B8%89',
398+
params: {
399+
id: '三'
400+
},
401+
matched: [
402+
{
403+
path: '/ja/count/:id',
404+
redirect: undefined,
405+
name: 'count___ja',
406+
meta: {},
407+
aliasOf: undefined,
408+
beforeEnter: undefined,
409+
props: {
410+
default: false
411+
},
412+
children: [],
413+
instances: {},
414+
leaveGuards: {},
415+
updateGuards: {},
416+
enterCallbacks: {},
417+
components: {
418+
default: {
419+
template: '<div>Category</div>'
420+
}
421+
}
422+
}
423+
],
424+
meta: {},
425+
redirectedFrom: undefined,
426+
href: '/ja/count/%E4%B8%89?foo=b%C3%A4r&foo=bar&four=%E5%9B%9B'
427+
}
428+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
429+
assert.deepEqual(resolvedRouteToObject(provided as any), expected as any)
430+
})
431+
432+
it('should map resolved route with special characters', () => {
433+
const provided = {
434+
fullPath: '/ja/count/三',
435+
hash: '',
436+
query: {},
437+
name: 'count___ja',
438+
path: '/ja/count/三',
439+
params: {
440+
id: '三'
441+
},
442+
matched: [
443+
{
444+
path: '/ja/count/:id',
445+
redirect: undefined,
446+
name: 'count___ja',
447+
meta: {},
448+
aliasOf: undefined,
449+
beforeEnter: undefined,
450+
props: {
451+
default: false
452+
},
453+
children: [],
454+
instances: {},
455+
leaveGuards: {},
456+
updateGuards: {},
457+
enterCallbacks: {},
458+
components: {
459+
default: {
460+
template: '<div>Category</div>'
461+
}
462+
}
463+
}
464+
],
465+
meta: {},
466+
redirectedFrom: undefined,
467+
href: '/ja/count/三'
468+
}
469+
const expected = {
470+
fullPath: '/ja/count/%E4%B8%89',
471+
hash: '',
472+
query: {},
473+
name: 'count___ja',
474+
path: '/ja/count/%E4%B8%89',
475+
params: {
476+
id: '三'
477+
},
478+
matched: [
479+
{
480+
path: '/ja/count/:id',
481+
redirect: undefined,
482+
name: 'count___ja',
483+
meta: {},
484+
aliasOf: undefined,
485+
beforeEnter: undefined,
486+
props: {
487+
default: false
488+
},
489+
children: [],
490+
instances: {},
491+
leaveGuards: {},
492+
updateGuards: {},
493+
enterCallbacks: {},
494+
components: {
495+
default: {
496+
template: '<div>Category</div>'
497+
}
498+
}
499+
}
500+
],
501+
meta: {},
502+
redirectedFrom: undefined,
503+
href: '/ja/count/%E4%B8%89'
504+
}
505+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
506+
assert.deepEqual(resolvedRouteToObject(provided as any), expected as any)
507+
})
508+
})

packages/vue-i18n-routing/src/compatibles/routing.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { isVue3, isRef, unref, isVue2 } from 'vue-demi'
55
import { DEFAULT_DYNAMIC_PARAMS_KEY } from '../constants'
66
import { getLocale, getLocaleRouteName, getRouteName } from '../utils'
77

8-
import { getI18nRoutingOptions, resolve, routeToObject } from './utils'
8+
import { getI18nRoutingOptions, isV4Route, resolve, resolvedRouteToObject, routeToObject } from './utils'
99

1010
import type { RoutingProxy, PrefixableOptions, SwitchLocalePathIntercepter } from './types'
1111
import type { Strategies, I18nRoutingOptions } from '../types'
@@ -231,9 +231,9 @@ export function resolveRoute(this: RoutingProxy, route: any, locale?: Locale): a
231231
}
232232

233233
try {
234-
const resolvedRoute = router.resolve(localizedRoute) as any
234+
const resolvedRoute = resolvedRouteToObject(router.resolve(localizedRoute))
235235
// prettier-ignore
236-
if (isVue3
236+
if (isV4Route(resolvedRoute)
237237
? resolvedRoute.name // for vue-router v4
238238
: resolvedRoute.route.name // for vue-router v3
239239
) {
@@ -321,7 +321,7 @@ export function switchLocalePath(this: RoutingProxy, locale: Locale): string {
321321
const baseRoute = assign({}, routeCopy, _baseRoute)
322322
let path = localePath.call(this, baseRoute, locale)
323323

324-
// custome locale path with intercepter
324+
// custom locale path with interceptor
325325
path = switchLocalePathIntercepter(path, locale)
326326

327327
return path

0 commit comments

Comments
 (0)