Skip to content

Commit

Permalink
feat: API for handling locale change during page transitions (#963)
Browse files Browse the repository at this point in the history
Useful if you want to wait for the page transition to enter before setting the locale.

Co-authored-by: Rafał Chłodnicki <rchl2k@gmail.com>
  • Loading branch information
pmrotule and rchl committed Feb 3, 2021
1 parent 15995db commit 23b9cc4
Show file tree
Hide file tree
Showing 10 changed files with 180 additions and 6 deletions.
16 changes: 16 additions & 0 deletions docs/content/en/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,22 @@ Instance of [VueI18n class](http://kazupon.github.io/vue-i18n/api/#vuei18n-class

Returns browser locale code filtered against the ones defined in options.

#### finalizePendingLocaleChange <badge>v6.20.0+</badge>

- **Arguments**:
- no arguments
- **Returns**: `Promise<undefined>`

Switches to the pending locale that would have been set on navigate, but was prevented by the option [`skipSettingLocaleOnNavigate`](./options-reference#skipsettinglocaleonnavigate). See more information in [Wait for page transition](./lang-switcher#wait-for-page-transition).

#### waitForPendingLocaleChange <badge>v6.20.0+</badge>

- **Arguments**:
- no arguments
- **Returns**: `Promise<undefined>`

Returns a promise that will be resolved once the pending locale is set.

### Properties

#### defaultDirection <badge>v6.19.0+</badge>
Expand Down
46 changes: 46 additions & 0 deletions docs/content/en/lang-switcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,49 @@ export default {
**nuxt-i18n** won't reset parameters translations for you, this means that if you use identical parameters for different routes, navigating between those routes might result in conflicting parameters. Make sure you always set params translations in such cases.

</alert>

## Wait for page transition

By default, the locale will be changed right away when navigating to a route with a different locale which means that if you have a page transition, it will fade out the page with the text already switched to the new language and fade back in with the same content.

To work around the issue, you can set the option [`skipSettingLocaleOnNavigate`](./options-reference#skipsettinglocaleonnavigate) to `true` and handle setting the locale yourself from a `beforeEnter` transition hook defined in a plugin.

```js {}[nuxt.config.js]
export default {
plugins: ['~/plugins/router'],

i18n: {
// ... your other options
skipSettingLocaleOnNavigate: true,
}
}
```

```js {}[~/plugins/router.js]
export default ({ app }) => {
app.nuxt.defaultTransition.beforeEnter = () => {
app.i18n.finalizePendingLocaleChange()
}

// Optional: wait for locale before scrolling for a smoother transition
app.router.options.scrollBehavior = async (to, from, savedPosition) => {
// Make sure the route has changed
if (to.name !== from.name) {
await app.i18n.waitForPendingLocaleChange()
}
return savedPosition || { x: 0, y: 0 }
}
}
```

If you have a specific transition defined in a page component, you would also need to call `finalizePendingLocaleChange` from there.

```js {}[~/pages/foo.vue]
export default {
transition: {
beforeEnter() {
this.$i18n.finalizePendingLocaleChange()
}
}
}
```
11 changes: 10 additions & 1 deletion docs/content/en/options-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export default {
- type: `string`
- default: `ltr`

The app's default direction. Will only be used when `dir` is not specified.
The app's default direction. Will only be used when `dir` is not specified.

## `defaultLocale`

Expand Down Expand Up @@ -268,6 +268,15 @@ A listener called right before app's locale changes.

A listener called after app's locale has changed.

## `skipSettingLocaleOnNavigate` <badge>v6.20.0+</badge>

<badge>v6.20.0+</badge>

- type: `boolean`
- default: `false`

If `true`, the locale will not be set when navigating to a new locale. This is useful if you want to wait for the page transition to end before setting the locale yourself using [`skipSettingLocaleOnNavigate`](./api#skipsettinglocaleonnavigate). See more information in [Wait for page transition](./lang-switcher#wait-for-page-transition).

## `defaultLocaleRouteNameSuffix`

- type: `string`
Expand Down
16 changes: 16 additions & 0 deletions docs/content/es/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,22 @@ Instance of [VueI18n class](http://kazupon.github.io/vue-i18n/api/#vuei18n-class

Devuelve el código del idioma que utiliza el navegador, filtrado según los códigos definidos en las opciones.

#### finalizePendingLocaleChange <badge>v6.20.0+</badge>

- **Arguments**:
- no arguments
- **Returns**: `Promise<undefined>`

Switches to the pending locale that would have been set on navigate, but was prevented by the option [`skipSettingLocaleOnNavigate`](./options-reference#skipsettinglocaleonnavigate). See more information in [Wait for page transition](./lang-switcher#wait-for-page-transition).

#### waitForPendingLocaleChange <badge>v6.20.0+</badge>

- **Arguments**:
- no arguments
- **Returns**: `Promise<undefined>`

Returns a promise that will be resolved once the pending locale is set.

### Properties

#### defaultDirection <badge>v6.19.0+</badge>
Expand Down
46 changes: 46 additions & 0 deletions docs/content/es/lang-switcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,49 @@ export default {
**nuxt-i18n** no restablecerá las traducciones de parámetros por usted, esto significa que si utiliza parámetros idénticos para diferentes rutas, navegar entre esas rutas podría generar parámetros conflictivos. Asegúrese de establecer siempre traducciones de parámetros en tales casos.

</alert>

## Wait for page transition

By default, the locale will be changed right away when navigating to a route with a different locale which means that if you have a page transition, it will fade out the page with the text already switched to the new language and fade back in with the same content.

To work around the issue, you can set the option [`skipSettingLocaleOnNavigate`](./options-reference#skipsettinglocaleonnavigate) to `true` and handle setting the locale yourself from a `beforeEnter` transition hook defined in a plugin.

```js {}[nuxt.config.js]
export default {
plugins: ['~/plugins/router'],

i18n: {
// ... your other options
skipSettingLocaleOnNavigate: true,
}
}
```

```js {}[~/plugins/router.js]
export default ({ app }) => {
app.nuxt.defaultTransition.beforeEnter = () => {
app.i18n.finalizePendingLocaleChange()
}

// Optional: wait for locale before scrolling for a smoother transition
app.router.options.scrollBehavior = async (to, from, savedPosition) => {
// Make sure the route has changed
if (to.name !== from.name) {
await app.i18n.waitForPendingLocaleChange()
}
return savedPosition || { x: 0, y: 0 }
}
}
```

If you have a specific transition defined in a page component, you would also need to call `finalizePendingLocaleChange` from there.

```js {}[~/pages/foo.vue]
export default {
transition: {
beforeEnter() {
this.$i18n.finalizePendingLocaleChange()
}
}
}
```
9 changes: 9 additions & 0 deletions docs/content/es/options-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,15 @@ A listener called right before app's locale changes.

A listener called after app's locale has changed.

## `skipSettingLocaleOnNavigate` <badge>v6.20.0+</badge>

<badge>v6.20.0+</badge>

- type: `boolean`
- default: `false`

If `true`, the locale will not be set when navigating to a new locale. This is useful if you want to wait for the page transition to end before setting the locale yourself using [`skipSettingLocaleOnNavigate`](./api#skipsettinglocaleonnavigate). See more information in [Wait for page transition](./lang-switcher#wait-for-page-transition).

## `defaultLocaleRouteNameSuffix`

- type: `string`
Expand Down
1 change: 1 addition & 0 deletions src/helpers/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ exports.DEFAULT_OPTIONS = {
},
parsePages: true,
pages: {},
skipSettingLocaleOnNavigate: false,
beforeLanguageSwitch: () => null,
onLanguageSwitched: () => null
}
Expand Down
30 changes: 29 additions & 1 deletion src/templates/plugin.main.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
onLanguageSwitched,
rootRedirect,
routesNameSeparator,
skipSettingLocaleOnNavigate,
STRATEGIES,
strategy,
vueI18n,
Expand Down Expand Up @@ -196,11 +197,33 @@ export default async (context) => {
(detectBrowserLanguage && doDetectBrowserLanguage(route)) ||
getLocaleFromRoute(route) || app.i18n.locale || app.i18n.defaultLocale || ''

await app.i18n.setLocale(finalLocale)
if (skipSettingLocaleOnNavigate) {
app.i18n.__pendingLocale = finalLocale
app.i18n.__pendingLocalePromise = new Promise(resolve => {
app.i18n.__resolvePendingLocalePromise = resolve
})
} else {
await app.i18n.setLocale(finalLocale)
}

return [null, null]
}

const finalizePendingLocaleChange = async () => {
if (!app.i18n.__pendingLocale) {
return
}
await app.i18n.setLocale(app.i18n.__pendingLocale)
app.i18n.__resolvePendingLocalePromise()
app.i18n.__pendingLocale = null
}

const waitForPendingLocaleChange = async () => {
if (app.i18n.__pendingLocale) {
await app.i18n.__pendingLocalePromise
}
}

const getBrowserLocale = () => {
if (process.client && typeof navigator !== 'undefined' && navigator.languages) {
// Get browser language either from navigator if running on client side, or from the headers
Expand Down Expand Up @@ -262,7 +285,12 @@ export default async (context) => {
i18n.getLocaleCookie = () => getLocaleCookie(req, { useCookie, cookieKey, localeCodes })
i18n.setLocale = (locale) => loadAndSetLocale(locale)
i18n.getBrowserLocale = () => getBrowserLocale()
i18n.finalizePendingLocaleChange = finalizePendingLocaleChange
i18n.waitForPendingLocaleChange = waitForPendingLocaleChange
i18n.__baseUrl = app.i18n.__baseUrl
i18n.__pendingLocale = app.i18n.__pendingLocale
i18n.__pendingLocalePromise = app.i18n.__pendingLocalePromise
i18n.__resolvePendingLocalePromise = app.i18n.__resolvePendingLocalePromise
}

// Set instance options
Expand Down
1 change: 1 addition & 0 deletions types/nuxt-i18n.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ declare namespace NuxtVueI18n {
rootRedirect?: string | null | RootRedirectInterface
routesNameSeparator?: string
seo?: boolean
skipSettingLocaleOnNavigate?: boolean,
strategy?: Strategies
vueI18n?: VueI18n.I18nOptions | string
vueI18nLoader?: boolean
Expand Down
10 changes: 6 additions & 4 deletions types/vue.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ declare module 'vue-i18n' {
// it is necessary for the $i18n property in Vue interface: "readonly $i18n: VueI18n & IVueI18n"
interface IVueI18n extends NuxtVueI18n.Options.NuxtI18nInterface {
localeProperties: NuxtVueI18n.Options.LocaleObject
getLocaleCookie() : string | undefined
setLocaleCookie(locale: string) : undefined
setLocale(locale: string) : Promise<undefined>
getBrowserLocale() : string | undefined
getLocaleCookie(): string | undefined
setLocaleCookie(locale: string): undefined
setLocale(locale: string): Promise<undefined>
getBrowserLocale(): string | undefined
finalizePendingLocaleChange(): Promise<void>
waitForPendingLocaleChange(): Promise<void>
}
}

Expand Down

0 comments on commit 23b9cc4

Please sign in to comment.