I have a locale.js
file which is responsible for defining user locale. Here it is:
import store from '@/vuex/index'
let locale
const defaultLocale = 'en_US'
if (store.getters['auth/authenticated']) {
locale = store.getters['auth/currentUser'].locale || defaultLocale
} else {
if (localStorage.getItem('locale')) {
locale = localStorage.getItem('locale')
} else {
locale = defaultLocale
}
}
export default locale
Also I have a i18n.js
file which is responsible for making i18n
instance which I use when I init my app.
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import locale from '@/services/locale'
Vue.use(VueI18n)
const fallbackLocale = 'en_US'
let i18n = new VueI18n({
locale,
fallbackLocale,
})
i18n.setLocaleMessage('ru_RU', require('@/lang/ru_RU.json'))
i18n.setLocaleMessage('en_US', require('@/lang/en_US.json'))
export { i18n }
Now I think that it'd be more convenient to have URLs prefixed with locale, like /en/profile
or /ru/profile
. This way I can share a link with locale which would be already set.
Not sure how do to this though. Making all routes child and put /:locale?
is not that convenient because router is not yet initialized (I pass i18n
and router
instances simultaneously when initing root app instance).
How can I achieve that, what would be the best approach?
I have a locale.js
file which is responsible for defining user locale. Here it is:
import store from '@/vuex/index'
let locale
const defaultLocale = 'en_US'
if (store.getters['auth/authenticated']) {
locale = store.getters['auth/currentUser'].locale || defaultLocale
} else {
if (localStorage.getItem('locale')) {
locale = localStorage.getItem('locale')
} else {
locale = defaultLocale
}
}
export default locale
Also I have a i18n.js
file which is responsible for making i18n
instance which I use when I init my app.
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import locale from '@/services/locale'
Vue.use(VueI18n)
const fallbackLocale = 'en_US'
let i18n = new VueI18n({
locale,
fallbackLocale,
})
i18n.setLocaleMessage('ru_RU', require('@/lang/ru_RU.json'))
i18n.setLocaleMessage('en_US', require('@/lang/en_US.json'))
export { i18n }
Now I think that it'd be more convenient to have URLs prefixed with locale, like /en/profile
or /ru/profile
. This way I can share a link with locale which would be already set.
Not sure how do to this though. Making all routes child and put /:locale?
is not that convenient because router is not yet initialized (I pass i18n
and router
instances simultaneously when initing root app instance).
How can I achieve that, what would be the best approach?
Share Improve this question asked May 13, 2018 at 23:33 VictorVictor 5,35315 gold badges77 silver badges127 bronze badges2 Answers
Reset to default 11You can implement router
routes: [{
path: '/:lang',
children: [
{
path: 'home'
component: Home
},
{
path: 'about',
component: About
},
{
path: 'contactus',
component: ContactUs
}
]
}]
and set locale
in beforeEach
hook
// use beforeEach route guard to set the language
router.beforeEach((to, from, next) => {
// use the language from the routing param or default language
let language = to.params.lang;
if (!language) {
language = 'en';
}
// set the current language for vuex-i18n. note that translation data
// for the language might need to be loaded first
Vue.i18n.set(language);
next();
});
There are two or three problems I can think of that comes with nesting all your routes under a single /:locale?
.
Route definitions may become ambiguous. If you have paths
/:locale?/foo/bar
and/:locale?/bar
defined as routes, what will<RouterLink to="/foo/bar" />
match? That will depend on which of those routes is defined first, and if the second of my examples is matched it will lead to an invalid locale. This problem has a simple-enough solution; just constrain your:locale
parameter using a regex. If you know the exact list of supported locales statically, you could do something like:import locales from '@/lang' // Your list of supported locales. const regexp = locales.join('|') // You may want to filter out 'en' first. const routes = [{ path: `/:locale(${regexp})?`, children: [ ... ], }]
If your translations and list of supported locales are otherwise only available at runtime (e.g. they're retrieved via an API), you may be forced to create a regex specific to your locale tag format. If they match BCP-47, I believe that means either 2 or 3 characters for the primary subtag, and the script and region are optional. If you use normalized tags (lowercase primary, titlecase script, uppercase region), that's even better, because that will further reduce ambiguity:
const routes = [{ path: '/:locale([a-z]{2,3}(-[A-Z][a-z]+)?(-([A-Z]{2}|[0-9]{3}))?', caseSensitive: true, children: [ ... ], }]
You'll want to read the spec more closely than I have to ensure that regex is correct. You'll also need to guard against unsupported locales in your
beforeEach
hook, so that you can load a "Not found" error page.As long as you do not define any routes whose first path segment could be mistaken for a locale tag, the above should fix the ambiguity problem.
Routes may accidentally be defined using root paths. Nested routes are usually defined using relative paths, i.e. paths not anchored with a
/
. However, nesting is not simply a mechanism for sharing prefixes or parameters among many routes, it is most often used for sharing layout components. Vue-router therefore allows you to override the parent route definition's path by defining an absolute path. The documentation explains:Note that nested paths that start with / will be treated as a root path. This allows you to leverage the component nesting without having to use a nested URL.
Mistakenly defining an absolute path will cause the route to only be matched for the fallback (I assume English) locale. As developers are likely to prototype and test using English most of the time, it might not appear like anything is amiss.
For a small application where all your routes are defined within a single file, this may not be a big deal as the error is probably easy to spot. But for a large application with many route definition files and many developers, such an error is going to be more difficult to catch.
Every usage of
<RouterLink>
and programmatic navigation will require injecting the locale parameter. You'll need to remember to interpolate$i18n.locale
into everyto
prop andpush()
call. Not doing so does not cause an error or break the page, so your tests are unlikely to catch this, and you won't notice any problems if you're only browsing in English. You could wrap or extend<RouterLink>
with your own component that does this automatically, but that doesn't prevent someone from accidentally usingRouterLink
, as it is still globally-registered. You could also write a global mixin to add convenience methods forrouter.push()
/.replace()
/.go()
, but this again would not protect you against accidental use of those methods.
One not-ideal solution to the above problems is to forego defining the locale as a path parameter, and instead match it prior to initializing the router. To do this, you have to pass it as the base
constructor option. Unfortunately, the base
path does not appear to be alterable, meaning locale changes will require a new page request. Since most users will likely change locale at most once, this might not be a huge problem, but nonetheless does not give the best user experience.