Prefix routes with locale in vue.js (using vue-i18n) - javascript

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?

You 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 every to prop and push() 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 using RouterLink, as it is still globally-registered. You could also write a global mixin to add convenience methods for router.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.

Related

A single payload.js for all nuxt static generate'd routes when data is the same

I have a NuxtJS site with only one page /pages/matrix/index.vue but quite a lot of dynamic routes pointing to this page, each route using the same set of data. When generating a static build for deployment on Netlify, the dist folder currently reaches ~1.2 GB, consisting of
3125 .html files in dist/matrix (occupying ~39% of the space)
3125 folders for payload.js files in dist/_nuxt/static/[random]/matrix/ in subfolders for routes (occupying ~61% of the space)
Those 61% are 3125 copies of a 220kB payload.js with exactly the same set of data: [{}], while only the route changes:
__NUXT_JSONP__("/matrix/place2/time3,time14,time29", (function(a, b, ...) {
return {
data: [{ /* data does not change */ }],
fetch: {},
mutations: void 0
}
}("nor", "does", "this")));
I wonder if there is a way to reduce this redundancy by somehow extracting the data part? Reducing ~665 MB to just 220kB sounds alluring.
Some more background:
Routes are /matrix, /matrix/place1 or /matrix/place8/time1,time7,time18. When generating, I pull all data from a headless CMS and feed it to my page component via the payload option. First, I used File System Routing and imported the pages/matrix/index.vue like this:
// pages/matrix/_places/index.vue
<script>
import Index from '../index'
export default Index
</script>
which felt wrong but worked. I blamed this approach to the "duplication" of those payload files (frankly without completely understanding the mechanics of static generation). I now switched to extendRoutes with this nuxt.config.js setting:
router: {
extendRoutes (routes, resolve) {
routes.push(
{
name: 'matrix-place-times',
path: '/matrix/:place/:times',
component: resolve(__dirname, 'pages/matrix/index.vue')
},
{
name: 'matrix-place',
path: '/matrix/:place',
component: resolve(__dirname, 'pages/matrix/index.vue')
}
)
}
}
The amount of payload files spread across route subfolders stays the same.
Any advice on this? Using Nuxt v2.15.7.
Few things I can think about:
using SSR would solve some of those issues (you told that you wish to stay on Netlify tho)
using some aliased routes may be a good idea, especially if you have the exact same data at 2 differently named endpoints
this video also gives some leads regarding ISG or other ways to have more flexibility towards big page amounts
Nuxt3 is able to run on Cloudflare workers, this is not SSR nor exactly ISG but a middle ground, a different approach but it could render pages for not so expensive while being quick

Optional dynamic route without a custom server - NextJS - 9.5.2

I am trying to create localized routes with optional first param like /lang?/../../, but without a custom server.
From 9.5 NextJS has this option dynamic optionall parameters, if you set the folder or a file with a name:
[[...param]]. I did that.
The problem is that, i have other routes and I want all of them to be with that lang prefix, ut optional with default language, if that lang is not provided
I have a folder [[...lang]] with a file index.js, with simple function component just for testing. Now optional parameter works for the home page / and /en, but I have other files, which I want to be with that optional lang. For the example, I have about.js and I want to access it via /en/about and /about.
I can't put about.js inside [[...lang]], because, I am getting an error:
Failed to reload dynamic routes: Error: Catch-all must be the last part of the URL.
I know what it says and why is that, but I have a fixed collection of languages ['en', 'fr'] and I can check is there a lang.
Is there a way, without a custom server to use optionally a dynamic first part of the path, like
/en/about and /about ?
I think you are talking about this feature. Have a look on this https://nextjs.org/blog/next-9-5#support-for-rewrites-redirects-and-headers
To extend the answer from #Vibhav, in next.config.js:
const nextConfig = {
async rewrites(){
return [
// URLs without a base route lang param like /my-page
{
source: '/',
destination: '/'
},
// URLs with a base route lang param like /en/my-page
{
source: '/:lang*/:page*',
destination: '/:page*'
},
// URLs `/en/post/post_id`
{
source: '/:lang/:path/:page',
destination: '/:path/:page'
},
]
}
};
module.exports = withBundleAnalyzer(nextConfig);
all pages are in the pages folder. Not the best solution for now, because it works in a deep up to like /pages/another-folder/file.
You can even get the lang param in your pages or _app.js:
....
const router = useRouter();
if(router.query.lang){
pageProps.lang = router.query.lang;
}
console.log(pageProps.lang);
return (
<Layout>
<Component {...pageProps} />
</Layout>
)
For URL - /en/my-page, router.query.lang will be equal to en.
For URL - /my-page, router.query.lang will be undefined, but you can set a default lang.

React.js Appropiate structure of a multiple language app

Background: I'm using Django as the framework for the backend, here, I have a single app with multiple models (one per each language) and React.js as the framework for the fronted. Once the main version is over, I want to make the site multilanguage.
Question: Should I duplicate the original app (say this app is in English) deploy these two versions or maybe create these versions inside of the main version?
Directory structure case A
mainAPP-EN
mainAPP-ES
Directory structure case B
mainAPP
-> EN
-> ES
Note. The site is not complex so both options are suitable for me.
Thank you in advance for your time and help!
You don't need multiple modules to do that. In fact I would strongly suggest you do not do that, it will become very hard to maintain (IE: bugs and features need to be fixed on each language).
Django's support for multiple locales is great (official docs):
from django.utils.translation import gettext as _
def my_view(request):
output = _("Welcome to my site.")
return HttpResponse(output)
On the React side, there are many libraries, the one I like for its simplicity is Airbnb's Polyglot:
const SPANISH_PHRASES = {
locale: 'es',
phrases: {
"hello": "hola"
},
};
const polyglot = new Polyglot(SPANISH_PHRASES);
polyglot.t("hello"); // returns "hola"
Usually, you would like to define a, say, Translate component that does this under the hood:
// basic example, needs more elaboration!
export default function Translate({ phrase }) {
return <span>{ polyglot.t(phrase) }</span>
}
Then to use it:
function SomeComponent() {
return (
<SomeMenu><Translate phrase="hello" /></SomeMenu>
);
}

Angular - access value from a service in root module

I'm working on internationalisation of an Angular app where I need to dynamically detect the locale that the user has actively selected for the app (the user must actively switch locale for the site to be displayed in their preferred language).
In my root module I specify the following provider for TRANSLATIONS, where the required xlf file (the translations file) is determined dynamically using a factory that depends on the value of my LOCALE_ID:
app.module.ts
... imports go here
export function selectedLocaleFactory(): string {
return LocaleService.getCurrentLanguage();
}
providers:
[
LocaleService,
{
provide: TRANSLATIONS,
useFactory: (locale) => {
locale = locale || 'en'; // default to english if no locale provided
return require(`raw-loader!../locale/messages.${locale}.xlf`);
},
deps: [LOCALE_ID]
},
{
provide: LOCALE_ID,
useFactory: selectedLocaleFactory
}
]
As you can see, in the TRANSLATIONS provider, I'm determining the translation file to use based on the value of LOCALE_ID.
LOCALE_ID is determined by using another factory (selectedLocaleFactory), which simply tries to return a LOCALE_ID value using a method getCurrentLanguage from the LocalService. I'm not sure this is the correct/best way to get a value from my service - I mean selectedLocaleFactory isn't really acting as a true 'factory', and services are designed to be injected.
In any case, I get this compile error in app.module.ts:
Property 'getCurrentLanguage' does not exist on type 'typeof LocaleService'.
Here's an extract from my LocalService:
#Injectable()
export class LocaleService {
private currLanguage: string;
constructor( private router: Router ) {
// this.currLanguage is set here - based on the URL being accessed by the user
}
getCurrentLanguage() {
return this.currLanguage;
}
If I make getCurrentLanguage a static method inside the LocaleService it's still inaccessible:
static getCurrentLanguage() {
return this.currLanguage;
}
UPDATE:
I realised that I need to instantiate the service to access the appropriate method:
export function selectedLocaleFactory(): string {
var localeService = new LocaleService(null);
return localeService.getCurrentLanguage();
}
My LocaleService has a Router dependency, so I just set this to null. It still feels though like I'm trying to do this the wrong way. My LocaleService should really be a singleton and I'm concerned I may end up with issues using my current approach (for example issues with shadowing). Is there a more 'correct' I can access a property from my service inside my root module?
The answer by #Thierry Templier here - What is the best way to declare a global variable in Angular 2 / Typescript suggests bootstrapping the service to get access to one of its variables, however this approach gives me an error saying I can't use the service as an entry point.

meteor: import directory - modular import of methods which are needed

I'm migrating my meteor application to the import-function of meteor 1.3.
But I think this is not quite the best way it should be done. Isn't it possible to load/import just the method which is really needed?
I mean, right now just all methods are loaded by importing the the methods.js. But I would like to do that in a modular way. So if the form .fomNewElement is used in the app, the method insertArticle will be imported and so on. Not just loading everything...
Below you can see my folder structure for /imports and some content of the files. Is there anything more I could improve in the structure itself?
Also it would be great if the import would depend on user roles. Is this possible?
imports/api/article/client/article.js
import { Articles } from '../';
import { insertArticle, updateArticle } from '../methods.js';
Template.Articles.helpers({
// some helpers
});
Template.Artilces.onCreated(function() {
// some code
});
Template.Artilces.onRendered(function() {
// some code
});
Template.Articles.events({
'submit .formNewElement': function(event) {
event.preventDefault();
var title = event.target.title.value.trim();
insertArticle.call({
title: title
});
},
'click .anything': function() {}
});
As you can see, I put into that js-file all helpers, events and onCreated/onRendered code. Hope this is 'correct'... Please give me some hint, if this isn't very smart.
imports/api/article/index.js
export const Articles = new Mongo.Collection('articles');
imports/api/article/methods.js
import { Articles } from './';
export const insertArticle = new ValidatedMethod({
name: 'article.insert',
validate: new SimpleSchema({
title: { type: String }
}).validator(),
run( document ) {
Articles.insert( document );
}
});
export const updateArticle = new ValidatedMethod({
name: 'article.update',
validate: new SimpleSchema({
_id: { type: String },
'update.title': { type: String }
}).validator(),
run( { _id, update } ) {
Articles.update( _id, { $set: update } );
}
});
And the other files:
imports/startup/client/index.js
import '../../api/redactor-article/client';
imports/startup/server/index.js
import '../../api/redactor-article/server/publications.js';
import '../../api/redactor-article/methods.js';
imports/api/article/client/index.js
import './article.html';
import './article.sass';
import './article.js';
Filestructure
/imports
/api
/article
/client
article.html
article.js
article.sass
index.js
/server
publications.js
index.js
methods.js
Update
Maybe it would be a better way to structure an import module like this:
imports/
api/
articles/
publication.js
methods.js
collection.js
ui/
articles/
article.html
article.css
article.js // contains helpers, events and onCreated/onRendered
Then I have to import the files in startup/client (-> all ui files of this module AND all api files) and startup/server (-> just all api files)...
Right?
A few points:
You've put everything under imports/api. That directory is designed for collections, methods, helpers, 'business logic' and public API (e.g. if you expose a REST API, you'd do it from within that directory). Use imports/ui for your templates (including their styles and associated .js files).
You don't need to differentiate between client and server directories within imports. Just import the files you need from the respective main entry points (i.e. client/main.js and server/main.js). This point is a little more complex than I suggest here, see the link to 'structure' in the Meteor Guide, below.
index.js doesn't seem like a logical place to put your Articles collection. I'd make a file at /imports/api/articles/articles.js for it. See http://guide.meteor.com/structure.html for a good overview about where to put things and why.
Also, in the interests of following best-practices, use a default export for your Articles collection: http://guide.meteor.com/code-style.html#collections
To answer your question about how much of the file is exported (i.e. which functions), there's not much you can do about everything being loaded. The bundler needs to read the entire JS file anyway (imagine you exported an object and then changed it further down in the same file– not the best practice, but possible). If you're not using a function though, by all means, don't import it! And you can always split up your methods into seperate files if they get unmanageable.
Regarding your question about only importing bits for certain user roles: always avoid using imports or other types of obfuscation for security. The ideal way to do security on Meteor is to assume ANYTHING is accessible on the client (it pretty much is) and code your server-side code accordingly. That means, if you have an admin area, assume that anyone can access it. You can do checks in server methods and publications for this.userId and do a database lookup there to ensure the user has the correct privileges. Again, the guide has more info about this: http://guide.meteor.com/security.html
A final note about imports/exports: the idea behind them is not to reduce code size, but to provide a graph of what is actually being used (and leaving out the files that aren't) to make hot code reloading faster for a better development experience. They also make for cleaner application code that is easier to understand, because you don't have random magical globals swimming around that could have come from anywhere, and help to keep logically distinct pieces of code separate.
Best of luck :)

Categories

Resources