Use i18next with XHR backend in client-side javascript - javascript

The documentation at i18next-xhr-backend tells me to use import to load their module. But when I use the import-statement, nothing happens and Firefox gives me a SyntaxError in the developer console:
SyntaxError: import declarations may only appear at top level of a module
So how can I use i18next library with the XHR-backend? The following code example works if the .use(XHR)-line and the corresponding import is commented out (Warning: i18next::backendConnector: No backend was added via i18next.use. Will not load resources.). But it fails, if it is not: ReferenceError: XHR is not defined
//import Fetch from 'i18next-fetch-backend';
let t = null;
i18next
.use(XHR)
.init({
debug: true,
fallbackLng: ['en'],
preload: ['en'],
ns: 'translation',
defaultNS: 'translation',
keySeparator: false, // Allow usage of dots in keys
nsSeparator: false,
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
},
}, (err, _t) => {
if (err) {
reject(err);
return;
}
t = _t;
//resolve();
});
jqueryI18next.init(i18next, $, {
tName: 't', // --> appends $.t = i18next.t
i18nName: 'i18n', // --> appends $.i18n = i18next
handleName: 'localize', // --> appends $(selector).localize(opts);
selectorAttr: 'data-i18n', // selector for translating elements
targetAttr: 'i18n-target', // data-() attribute to grab target element to translate (if different than itself)
optionsAttr: 'i18n-options', // data-() attribute that contains options, will load/set if useOptionsAttr = true
useOptionsAttr: false, // see optionsAttr
parseDefaultValueFromContent: true // parses default values from content ele.val or ele.text
});
$(".nav").localize();

I needed to use i18nextXHRBackend instead of just XHR, since that is the name the class gets loaded as if no loader is used. As the README.md says:
If you don't use a module loader it will be added to window.i18nextXHRBackend
I didn't see that before, and I didn't know that this will happen automatically, but it seems that you have to find that out on your own if not using a module loader. Lesson learned, hopefully this will help some other newbies being stuck on how to use modules in javascript. Therefore, my complete localisation.js looks like this:
$(document).ready(function() {
i18next
.use(i18nextXHRBackend)
.use(i18nextBrowserLanguageDetector)
.init({
debug: true,
backend: {
loadPath: 'locales/{{lng}}/{{ns}}.json',
addPath: 'locales/add/{{lng}}/{{ns}}'
}
}, function(err, t) {
jqueryI18next.init(i18next, $);
$('.translatable').localize();
$('.language-button').click(function() {
i18next.changeLanguage(this.firstElementChild.alt).then(function(t) {
$('.translatable').localize();
$('#signupPassword').pwstrength("forceUpdate");
$('#signupPasswordConfirm').pwstrength("forceUpdate");
});
});
});
});

Related

vueHtml2Pdf renders blank page (Nuxt)

I am using vueHtml2Pdf to generate my page to pdf, but when I wrap my content inside VueHtml2pdf tag nothing renders on my page, but it downloads when I click the download button. (Nuxt)
methods: {
downloadPDF() {
this.$refs.html2Pdf.generatePdf()
},
},
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<ArticleActions #download="downloadPDF()" />
<client-only>
<vue-html2pdf
ref="html2Pdf"
:show-layout="false"
:enable-download="true"
:pdf-quality="2"
:manual-pagination="true"
pdf-content-width="100%"
:html-to-pdf-options="htmlToPdfOptions"
>
<section slot="pdf-content">
<!-- content -->
<div
v-interpolation="{ newWindow: true }"
class="articleContent__content"
v-html="article.content"
></div>
<!-- /content -->
</section>
</vue-html2pdf>
</client-only>
I have a working solution for Nuxtv3 (with server-side rendering). After trying a bunch of different vue-specific packages, including vue-html2pdf, I realized that most of them have been written for Vue2.
Instead, I chose to use html2pdf directly.
Upon directly importing html2pdf in the component where I need to add the functionality for converting html to pdf, Nuxt throws the following error: ReferenceError: self is not defined. This essentially happens because the line where the library is being imported runs on the server side as well and when it is imported, it tries to access a variable that isn't defined on the server side.
My solution was to create a custom plugin that runs only on the client side. It is very simple to do this in Nuxtv3 by just ending the filename with .client.ts as opposed to just .ts. Here is what plugins/html2pdf.client.ts looks like:
import html2pdf from 'html2pdf.js'
export default defineNuxtPlugin(() => {
// had to make a plugin because directly importing html2pdf.js in the component
// where I need to use it was causing an error as the import would run on the server
// side and html2pdf.js is a client-side-only library. This plugin runs on the
// client side only (due to the .client extension) so it works fine.
return {
provide: {
html2pdf: (element, options) => {
return html2pdf(element, options)
}
}
}
})
Now, I can safely use it in my component as:
const { $html2pdf } = useNuxtApp()
function downloadPDF() {
if (document) {
const element = document.getElementById('html2pdf')
// clone the element: https://stackoverflow.com/questions/60557116/html2pdf-wont-print-hidden-div-after-unhiding-it/60558415#60558415
const clonedElement = element.cloneNode(true) as HTMLElement
clonedElement.classList.remove('hidden')
clonedElement.classList.add('block')
// need to append to the document, otherwise the downloading doesn't start
document.body.appendChild(clonedElement)
// https://www.npmjs.com/package/html2pdf.js/v/0.9.0#options
$html2pdf(clonedElement, {
filename: 'filename.pdf',
image: { type: 'png' },
enableLinks: true
})
clonedElement.remove()
}
}
Basic usage of html2pdf: https://www.npmjs.com/package/html2pdf.js/v/0.9.0#usage
Configuration for html2pdf: https://www.npmjs.com/package/html2pdf.js/v/0.9.0#options
If someone looking for how to use html2pdf in nuxt 2.
install html2pdf.js using npm or yarn
create html-to-pdf.js file in nuxt plugin directory with below code
import html2pdf from 'html2pdf.js'
export default (context, inject) => {
inject('html2pdf', html2pdf)
}
Then add plugin to nuxt.config.js
plugins: [
'#/plugins/axios',
......
{ src: '#/plugins/html-to-pdf', mode: 'client' },
],
How to use , example in your component menthod
methods: {
export() {
this.$html2pdf(this.$refs.document, {
margin: 1,
filename: 'file-name.pdf',
image: { type: 'jpeg', quality: 0.98 },
html2canvas: {
scale: 1,
dpi: 192,
letterRendering: true,
ignoreElements: true,
},
jsPDF: { unit: 'pt', format: 'a2', orientation: 'portrait' },
})
},

Load external translation files (jquery.i18n)

I'm trying to put a translation script on some of my pages.
It's not very big, so I want to use the scripts from the CDN.
It works fine if I put the translation directly on the page with the script.
For example:
...
<body>
<span data-i18n="foo"></span><br />
<span data-i18n="bar"></span>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/i18next/8.1.0/i18next.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-i18next/1.2.0/jquery-i18next.min.js"></script>
<script>
i18next.init(
{
debug: true,
// url.com?lang=en | url.com?lang=ru
lng: new URL(window.location.href).searchParams.get('lang'),
resources: {
en: {
translation: {
foo: 'Home',
bar: 'School',
},
},
ru: {
translation: {
foo: 'Дом',
bar: 'Школа',
},
},
},
},
function (err, t) {
jqueryI18next.init(i18next, $);
$('[data-i18n]').localize();
},
);
</script>
</body>
...
works fine (en) | works fine (ru)
BUT I want to use third-party files for translation instead of "resources" object.
SO I also connected the i18nextLocizeBackend library.
<body>
<span data-i18n="foo"></span><br />
<span data-i18n="bar"></span>
<!-- same scripts as above and one new one -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/i18next-locize-backend/3.1.3/i18nextLocizeBackend.min.js"></script>
<script>
i18next.init(
{
debug: true,
// url.com?lang=en | url.com?lang=ru
lng: new URL(window.location.href).searchParams.get('lang'),
backend: {
loadPath: './i18n/{{lng}}/{{ns}}.json',
},
},
function (err, t) {
jqueryI18next.init(i18next, $);
$('[data-i18n]').localize();
},
);
</script>
</body>
How do I use it, because in the console I get an error
i18next::backendConnector: No backend was added via i18next.use. Will not load resources.
(console.warn screenshot)
In all the examples that I found - this library is just imported and I don't need it.
You need to configure i18next with the i18next-http-backend.
Like described here: https://dev.to/adrai/the-progressive-guide-to-jquery-internationalization-i18n-using-i18next-3dc3#separate
So basically, load it: <script src="https://cdn.jsdelivr.net/npm/i18next-http-backend#1.3.2/i18nextHttpBackend.min.js"></script>
And pass it with the .use(i18nextHttpBackend) function:
$(function () {
// use plugins and options as needed, for options, detail see
// https://www.i18next.com
i18next
// i18next-http-backend
// loads translations from your server
// https://github.com/i18next/i18next-http-backend
.use(i18nextHttpBackend)
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(i18nextBrowserLanguageDetector)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
debug: true,
fallbackLng: 'en'
}, (err, t) => {
if (err) return console.error(err);
// ...
});
});

Using Google One Tap in Angular

I'd like to use Google One Tap in my Angular 11 app. Following the documentation I added <script async defer src="https://accounts.google.com/gsi/client"></script> to my html and then used the following code in my app.component.html:
<div id="g_id_onload"
data-client_id="MY_GOOGLE_CLIENT_ID"
data-callback="handleCredentialResponse",
data-cancel_on_tap_outside="false">
</div>
The popup works fine, though I can't seem to log in. If I create a function handleCredentialResponse in app.component.ts, I get the following error: [GSI_LOGGER]: The value of 'callback' is not a function. Configuration ignored.
If I instead try to use the JavaScript API, Typescript throws the following error: Property 'accounts' does not exist on type 'typeof google'
What should I do to be able to using Google One Tap in Angular?
I had a similar problem when I used the HTML API approach, so I ended up using the JavaScript API instead.
Here's what I did:
First, make sure to install the #types/google-one-tap package.
As you mentioned, I'm also importing the script in my index.html file, like so:
<body>
<script src="https://accounts.google.com/gsi/client" async defer></script>
<app-root></app-root>
</body>
Now, moving on to your main component which in my case is app.component.ts, import the following first:
import { CredentialResponse, PromptMomentNotification } from 'google-one-tap';
Then, you can add this on the ngOnInit(). Make sure to read the documentation to get more details on the onGoogleLibraryLoad event:
// #ts-ignore
window.onGoogleLibraryLoad = () => {
console.log('Google\'s One-tap sign in script loaded!');
// #ts-ignore
google.accounts.id.initialize({
// Ref: https://developers.google.com/identity/gsi/web/reference/js-reference#IdConfiguration
client_id: 'XXXXXXXX',
callback: this.handleCredentialResponse.bind(this), // Whatever function you want to trigger...
auto_select: true,
cancel_on_tap_outside: false
});
// OPTIONAL: In my case I want to redirect the user to an specific path.
// #ts-ignore
google.accounts.id.prompt((notification: PromptMomentNotification) => {
console.log('Google prompt event triggered...');
if (notification.getDismissedReason() === 'credential_returned') {
this.ngZone.run(() => {
this.router.navigate(['myapp/somewhere'], { replaceUrl: true });
console.log('Welcome back!');
});
}
});
};
Then, the handleCredentialResponse function is where you handle the actual response with the user's credential. In my case, I wanted to decode it first. Check this out to get more details on how the credential looks once it has been decoded: https://developers.google.com/identity/gsi/web/reference/js-reference#credential
handleCredentialResponse(response: CredentialResponse) {
// Decoding JWT token...
let decodedToken: any | null = null;
try {
decodedToken = JSON.parse(atob(response?.credential.split('.')[1]));
} catch (e) {
console.error('Error while trying to decode token', e);
}
console.log('decodedToken', decodedToken);
}
I too had the same problem in adding the function to the angular component.
Then i found a solution by adding JS function in appComponent like this:
(window as any).handleCredentialResponse = (response) => {
/* your code here for handling response.credential */
}
Hope this help!
set the div in template to be rendered in ngOnInit
`<div id="loginBtn" > </div>`
dynamically inject script tag in your login.ts as follows
constructor(private _renderer2: Renderer2, #Inject(DOCUMENT) private _document: Document){}
ngAfterViewInit() {
const script1 = this._renderer2.createElement('script');
script1.src = `https://accounts.google.com/gsi/client`;
script1.async = `true`;
script1.defer = `true`;
this._renderer2.appendChild(this._document.body, script1);
}
ngOnInit(): void {
// #ts-ignore
window.onGoogleLibraryLoad = () => {
// #ts-ignore
google.accounts.id.initialize({
client_id: '335422918527-fd2d9vpim8fpvbcgbv19aiv98hjmo7c5.apps.googleusercontent.com',
callback: this.googleResponse.bind(this),
auto_select: false,
cancel_on_tap_outside: true,
})
// #ts-ignore
google.accounts!.id.renderButton( document!.getElementById('loginBtn')!, { theme: 'outline', size: 'large', width: 200 } )
// #ts-ignore
google.accounts.id.prompt();
}
}
async googleResponse(response: google.CredentialResponse) {
// your logic goes here
}
Google One Tap js library tries to find callback in the global scope and can't find it, because your callback function is scoped somewhere inside of your app, so you can attach your callback to window, like window.callback = function(data) {...}.
Also, since you are attaching it to window, it's better to give the function a less generic name.

i18next load json from api using i18next-xhr-backend

I try to load a translation JSON file from API in React Native and parse it but it seems that load does not work because the parse method never invokes.
function initI18Next(onInit) {
return (
i18n
.use(initReactI18next)
.use(XHR)
.init({
...initOptions,
backend : backendOptions
}).then(onInit())
)}
initOptions :
export const initOptions = {
fallbackLng : 'es',
defaultNS : 'translation',
lng : 'es',
debug: true,
keySeparator : false,
initImmediate: false,
preload : ['en', 'es'],
react: {
wait: true,
useSuspense: false
},
interpolation: {
escapeValue: true // not needed for react as it escapes by default
},
resources : i18nResources
}
backend options :
export const backendOptions = {
loadPath: 'http://www.mocky.io/v2/5ecd0fe73200006400236655.json',
parse(data) {
console.log('data', data);
return data;
}
};
i18next-xhr-backend is deprecated, use i18next-http-backend instead...
Then try to set i18next debug option to true and have a look at the developer console logs.
Btw: the load path looks strange to me. It should be more like this: https://github.com/i18next/i18next-http-backend#backend-options

NodeJs Winston log line number

I am writing a module for logging in Nodejs using Winston. I am using a custom format and initialize my logger like this:
const logger = createLogger({
format: combine(
format.timestamp(),
format.printf(msg => `${JSON.stringify({timestamp: msg.timestamp,
shortmessage: msg.message,
level: msg.level,
source: config.programName,
file: __filename,
line: '' })}`) // how to get this?
),
transports: [new (transports.Console)({
level: config.logLevel, // logs up to specified level
})]
});
module.exports = {
error: function (message) {
logger.error(message);
},
info: function (message) {
logger.info(message);
},
debug: function (message) {
logger.debug(message);
}
};
As mentioned in the comment, I also need to include line number in my log. I did some research and found some workarounds (1, 2, 3), but it seems they can't be used in my case, as I use a custom format, which should be specified during the logger creation time and line number can be retrieved later.
I was thinking to use Winston's label feature, but it seems labels can contain only static data.
How can this be solved? Any ideas.

Categories

Resources