How do I translate Quasar component? - vue.js

I have a Vue/Quasar application in which I use i18n but now I'm facing a problem: table footer doesn't get translated. In the table headers, for example, I do something like this to translate column names:
{
align: 'left',
field: (val) => val,
label: this.$t('locale.column'),
name: 'column',
required: true,
sortable: true,
},
where $t is 18n function, locale is my component and the column is actually's column's name. I don't have direct access to the footer of the table (where the pagination an the total number of the elements are) and it doesn't get translated. I also use Quasar language packs, quasar.js goes like this:
import en from 'quasar/lang/en-us.js'
/*
import other languages
*/
import {
state,
} from '#state/modules/i18n';
const locale = state.locale;
const langDictionary = {
// all the imported langs go here
};
Vue.use(Quasar, {
config: {},
components: { /* not needed if importStrategy is not 'manual' */ },
directives: { /* not needed if importStrategy is not 'manual' */ },
plugins: {
Cookies,
Dialog,
Loading,
Notify,
},
lang: langDictionary[locale]
});
export {langDictionary};

You are probably setting the language at startup correctly as lang: langDictionary[locale]. If you change the language state in your app later, you need to also inform Quasar if you want Quasar components and plugins to get correctly translated. See the docs, Change Quasar Language Pack at Runtime for how to achieve that, the docs about language pack also includes tips for dynamically loading the language pack.
You didn't specify how you change the language in your app, so I can't give an example built upon that.

Related

How to use vue component across multiple node projects?

I'm trying to build a website builder within the drag-and-drop abilities via using Vue3. So, the user will be playing with the canvas and generate a config structure that going to post the backend. Furthermore, the server-side will generate static HTML according to this config.
Eventually, the config will be like the below and it works perfectly. The config only can have HTML tags and attributes currently. Backend uses h() function to generate dom tree.
My question is: can I use .vue component that will generate on the server side as well? For example, the client-side has a Container.vue file that includes some interactions, styles, etc. How can the backend recognize/resolve this vue file?
UPDATE:
Basically, I want to use the Vue component that exists on the Client side on the backend side to generate HTML strings same as exactly client side. (including styles, interactions etc).
Currently, I'm able to generate HTML string via the below config but want to extend/support Vue component itself.
Note: client and server are completely different projects. Currently, server takes config and runs createSSRApp, renderToString methods.
Here is the gist of how server would handle the API:
https://gist.github.com/yulafezmesi/162eafcf7f0dcb3cb83fb822568a6126
{
id: "1",
tagName: "main",
root: true,
type: "container",
properties: {
class: "h-full",
style: {
width: "800px",
transform: "translateZ(0)",
},
},
children: [
{
id: "9",
type: "image",
tagName: "figure",
interactive: true,
properties: {
class: "absolute w-28",
style: {
translate: "63px 132px",
},
},
},
],
}
This might get you started: https://vuejs.org/guide/scaling-up/ssr.html#rendering-an-app
From the docs:
// this runs in Node.js on the server.
import { createSSRApp } from 'vue'
// Vue's server-rendering API is exposed under `vue/server-renderer`.
import { renderToString } from 'vue/server-renderer'
const app = createSSRApp({
data: () => ({ count: 1 }),
template: `<button #click="count++">{{ count }}</button>`
})
renderToString(app).then((html) => {
console.log(html)
})
I guess extract the template from request or by reading the submitted Vue file and use that as the template parameter value

How to Load Custom Language in Monaco using VueJS/Webpack

I've created a custom language using this tool here. I don't know what to do to load it to my VueJS app. I tried the following and get no errors, but it also doesn't show seem to work, because in the Monarch tool thing I get blue text on known functions etc, but in my editor I don't. Other languages work as expected.
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
const path = require('path');
const main = path.resolve(__dirname, './src/test/test.ts');
module.exports = {
configureWebpack: {
plugins: [
new MonacoWebpackPlugin({
languages: ['javascript', 'typescript', 'python', 'java', 'python', 'json', 'vb'],
customLanguages: [
{
label: 'test',
entry: main
}
]
})
]
}
...
I made my .ts file essentially export a conf property with all the variables or whatever that are used in the tokenizer. I also exported a language property. I'm not totally sure that is the right format.
My .ts file essentially looks like:
export const conf = {...}
export const language = {...}
I'm not totally sure what to do here. Docs are sparse for custom languages and nothing seems to be working other than I think I at least have the first part of defining the language working.
That Webpack plugin isn't actually needed.
Based on the custom language example, you can register the language at runtime via monaco.languages.setMonarchTokensProvider(). The second function argument is an instance of IMonarchLanguage, which matches the language spec in the example you linked.
<script setup lang="ts">
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'
import { ref, onMounted } from 'vue'
/**
* `customLangMonarch` contains the language spec example from
* https://microsoft.github.io/monaco-editor/monarch.html
*/
// #ts-ignore
import customLangMonarch from '#/custom-lang-monarch'
monaco.languages.register({ id: 'custom' })
monaco.languages.setMonarchTokensProvider('custom', customLangMonarch)
const editor = ref()
onMounted(() => {
monaco.editor.create(editor.value, {
language: 'custom',
})
})
</script>
demo w/Vue CLI
demo w/Vite

How do I properly import multiple components dynamically and use them in Nuxt?

I need to implement dynamic pages in a Nuxt + CMS bundle.
I send the URL to the server and if such a page exists I receive the data.
The data contains a list of components that I need to use, the number of components can be different.
I need to dynamically import these components and use them on the page.
I don't fully understand how I can properly import these components and use them.
I know that I can use the global registration of components, but in this case I am interested in dynamic imports.
Here is a demo that describes the approximate logic of my application.
https://codesandbox.io/s/dank-water-zvwmu?file=%2Fpages%2F_.vue
Here is a github issue that may be useful for you: https://github.com/nuxt/components/issues/227#issuecomment-902013353
I've used something like this before
<nuxt-dynamic :name="icon"></nuxt-dynamic>
to load dynamic SVG depending of the icon prop thanks to dynamic.
Since now, it is baked-in you should be able to do
<component :is="componentId" />
but it looks like it is costly in terms of performance.
This is of course based on Nuxt components and auto-importing them.
Also, if you want to import those from anywhere you wish, you can follow my answer here.
I used this solution. I get all the necessary data in the asyncData hook and then import the components in the created () hook
https://codesandbox.io/s/codesandbox-nuxt-uidc7?file=/pages/index.vue
asyncData({ route, redirect }) {
const dataFromServer = [
{
path: "/about",
componentName: 'myComponent'
},
];
const componentData = dataFromServer.find(
(data) => data.path === route.path
);
return { componentData };
},
data() {
return {
selectedRouteData: null,
componentData: {},
importedComponents: []
};
},
created() {
this.importComponent();
},
methods: {
async importComponent() {
const comp = await import(`~/folder/${this.componentData.componentName}.vue`);
this.importedComponents.push(comp.default);
}

Issues including Moment.js library for use with Tabulator in Vue.js

I am trying to use the Date Time functionality with Tabulator which requires the moment.js library. In my application before adding Tabulator, moment.js was already being used in certain components that level. I have a new test component that uses Tabulator and attempts to use datetime. Typically I would just import moment and use it here but it seems that moment is required within Tabulator itself.
My first thought is that Moment.js needs to be setup globally in my application so I did that.
Main.js:
```
import Vue from 'vue'
import App from './App'
import router from './router'
import { store } from './store'
import Vuetify from 'vuetify'
import 'vuetify/dist/vuetify.min.css'
import moment from 'moment'
Vue.prototype.moment = moment
...............
new Vue({
el: '#app',
data () {
return {
info: null,
loading: true,
errored: false // this.$root.$data.errored
}
},
router,
components: { App },
template: '<App/>',
store
})
```
In my component (Testpage.vue)
```
<template>
<div>
<div ref="example_table"></div>
</div>
</template>
<script>
// import moment from 'moment'
var Tabulator = require('tabulator-tables')
export default {
name: 'Test',
data: function () {
return {
tabulator: null, // variable to hold your table
tableData: [{id: 1, date: '2019-01-10'}] // data for table to display
}
},
watch: {
// update table if data changes
tableData: {
handler: function (newData) {
this.tabulator.replaceData(newData)
},
deep: true
}
},
mounted () {
// instantiate Tabulator when element is mounted
this.tabulator = new Tabulator(this.$refs.example_table, {
data: this.tableData, // link data to table
columns: [
{title: 'Date', field: 'date', formatter: 'datetime', formatterParams: {inputFormat: 'YYYY-MM-DD', outputFormat: 'DD/MM/YY', invalidPlaceholder: '(invalid date)'}}
],
}
</script>
```
I receive the error: "Uncaught (in promise) Reference Error: moment is not defined at Format.datetime (tabulator.js?ab1f:14619)"
I am able to use moment in other components by using this.$moment() but I need for it to be available in node_modules\tabulator-tables\dist\js\tabulator.js
since thats where the error is happening. Any idea how to include the library?
Go back to the first option you were trying, because annotating the Vue prototype with moment is definitely not the right approach. Even if it was recommended (which it isn't), Tabulator would have to know to find it by looking for Vue.moment. It isn't coded to do that.
One of the things I love about Open Source is that you can see exactly what a library is doing to help fix the issue. A quick search of the Tabulator code base finds this:
https://github.com/olifolkerd/tabulator/blob/3aa6f17b04cccdd36a334768635a60770aa10e38/src/js/modules/format.js
var newDatetime = moment(value, inputFormat);
The formatter is just calling moment directly, without importing it. It's clearly designed around the old-school mechanism of expecting libraries to be available globally. In browser-land that means it's on the "window" object. Two quick options could resolve this:
Use a CDN-hosted version of Moment such as https://cdnjs.com/libraries/moment.js/ by putting something like this in the header of your page template:
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.23.0/moment.min.js"></script>
Adjust your code above to set moment on window:
window.moment = moment;
ohgodwhy's comment above isn't necessarily wrong from the perspective of date-fns being better in many ways. But it won't work for you because Tabulator is hard-coded to look for moment, so you'll need moment itself to work.

Angular2 - SEO - how to manipulate the meta description

Search Results in google are displayed via TitleTag and the <meta name="description"..."/> Tag.
The <title>-Tag is editiable via Angular2 how to change page title in angular2 router
What's left is the description.
Is it possibile to write a directive in angular2, that manipulates the meta-tags in the <head> part of my page.
So depending on the selected route, the meta description changes like:
<meta name="description" content="**my description for this route**"/>
Since Angular4, you can use Angular Meta service.
import { Meta } from '#angular/platform-browser';
// [...]
constructor(private meta: Meta) {}
// [...]
this.meta.addTag({ name: 'robots', content: 'noindex' });
It is possible. I implemented it in my app and below I provide the description how it is made.
The basic idea is to use Meta from #angular/platform-browser
To dynamically change particular meta tag you have to:
Remove the old one using removeTag(attrSelector: string) : void
method.
Add the new one using addTag(tag: MetaDefinition, forceCreation?:
boolean) : HTMLMetaElement method.
And you have to do it when the router fires route change event.
Notice: In fact it is also necessary to have default <title>...</title> and <meta name="description"..." content="..."/> in head of index.html so before it is set dynamically there is already some static content.
My app-routing.module.ts content:
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
import { NgModule } from '#angular/core';
import { RouterModule, Routes, Router, NavigationEnd, ActivatedRoute } from '#angular/router';
import { StringComparisonComponent } from '../module-string-comparison/string-comparison.component';
import { ClockCalculatorComponent } from '../module-clock-calculator/clock-calculator.component';
import { Title, Meta } from '#angular/platform-browser';
const routes: Routes = [
{
path: '', redirectTo: '/string-comparison', pathMatch: 'full',
data: { title: 'String comparison title', metaDescription: 'String comparison meta description content' }
},
{
path: 'string-comparison', component: StringComparisonComponent,
data: { title: 'String comparison title', metaDescription: 'String comparison meta description content' }
},
{
path: 'clock-time-calculator', component: ClockCalculatorComponent,
data: { title: 'Clock time calculator title', metaDescription: 'Clock time calculator meta description content' }
}
];
#NgModule({
imports: [ RouterModule.forRoot(routes) ],
exports: [ RouterModule ]
})
export class AppRoutingModule {
constructor(
private router: Router,
private activatedRoute: ActivatedRoute,
private titleService: Title,
private metaService: Meta
){
//Boilerplate code to filter out only important router events and to pull out data object field from each route
this.router.events
.filter(event => event instanceof NavigationEnd)
.map(() => this.activatedRoute)
.map(route => {
while (route.firstChild) route = route.firstChild;
return route;
})
.filter(route => route.outlet === 'primary')
//Data fields are merged so we can use them directly to take title and metaDescription for each route from them
.mergeMap(route => route.data)
//Real action starts there
.subscribe((event) => {
//Changing title
this.titleService.setTitle(event['title']);
//Changing meta with name="description"
var tag = { name: 'description', content: event['metaDescription'] };
let attributeSelector : string = 'name="description"';
this.metaService.removeTag(attributeSelector);
this.metaService.addTag(tag, false);
});
}
}
As it can be seen there is an additional data object field for
each route. It contains title and metaDescription strings
which will be used as title and meta tag content.
In constructor we filter out router events and we subscribe to filtered
router event. Rxjs is used there, but in fact it is not necessary to use it. Regular if statements and loops could be used insead of stream, filter and map.
We also merge our data object field with our event so we can easily
use info like title and metaDescription strings.
We dynamically change <title>...</title> and <meta name="description"..." content="..."/> tags.
Effects:
First component
Second component
In fact I currently use a little bit more sophisticated version of this solution which uses also ngx-translate to show different title and meta description for different languages.
Full code is available in angular2-bootstrap-translate-website-starter project.
The app-routing.module.ts file with ngx-translate solution is exactly there: app-routing.module.ts.
There is also the production app which uses the same solution: http://www.online-utils.com.
For sure it is not the only way and there might be better ways to do it. But I tested this solution and it works.
In fact the solution is very similar to this from corresponding post about changing title: How to change page title in angular2 router.
Angular Meta docs: https://angular.io/docs/ts/latest/api/platform-browser/index/Meta-class.html. In fact they aren't very informative and I had to experiment and look into real .js code to make this dynamic meta change working.
I have developed and just released #ngx-meta/core plugin, which manipulates the meta tags at the route level, and allows setting the meta tags programmatically within the component constructor.
You can find detailed instructions at #ngx-meta/core github repository. Also, source files might be helpful to introduce a custom logic.
There is currently no out-of-the-box solution only an open issue to implement it https://github.com/angular/angular/issues/7438.
You can of course implement something like the title service yourself, just use the TitleService as template
A Meta service similar to Title service is in the works (currently only a pull request).