Load external translation files (jquery.i18n) - javascript

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);
// ...
});
});

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' },
})
},

Use i18next with XHR backend in client-side 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");
});
});
});
});

VueJS : Adding to existing HTML and handling imports

So I built a Single Page Application in VueJS which works nicely but the SEO sucks as expected, so I decided to make a normal HTML site with some pages having VueJS code (Remote hosting so no node else I would go SSR).
I followed this guide which works fin
I have a search.js which contains my VueJS instance and methods etc
Vue.component('todo-component', {
template: '#todo-component',
data: function () {
return {
items: [
{
id: 'item-1',
title: 'Checkout vue',
completed: false
}, {
id: 'item-2',
title: 'Use this stuff!!',
completed: false
}
],
newItem: ''
};
},
methods: {
addItem: function () {
if (this.newItem) {
var item = {
id: Math.random(0, 10000),
title: this.newItem,
completed: false
};
this.items.push(item);
this.newItem = '';
}
}
}
});
var app = new Vue({
el: '#vue-app'
});
However, I need to import stuff like axios and other components
if I add an import statement to the script above, it comes up with
import axios from "axios";
Uncaught SyntaxError: Unexpected identifier
Where should my imports go?
Since you are directly writing code running in the browser, you can simply include the axios cdn in your html code before search.js is loaded:
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
As for components import, you can read more about component registration here. Generally if your components are registered globally via Vue.component('my-component', {}) syntax, you should be able to directly use it within your code.
You're missing axios library so add it as follow :
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
i'm also providing you of how to use it when you work with browser :
new Vue({
el: '#app',
data: {
dataReceived: '',
},
methods: {
getData() {
axios.get('https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD')
.then((response) => {
this.dataReceived = response.data;
console.log(this.dataReceived);
return this.dataReceived;
})
}
}
})
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
<script src="https://unpkg.com/vue#2.5.17/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
<button #click="getData" type="button">getData</button>
<p>dataReceived: {{ dataReceived }}</p>
</div>
</body>

utilize require.js using data-main or require?

I have the following line in head.
<script data-main="{% static 'site_common/js/main.js' %}" src='{% static "site_common/bower_components/requirejs/require.js" %}'></script>
main.js has
require(['site_common/js/config'], function() {
});
at the bottom of body, I have
require(["site_common/js/config"], function () {
require(['infrastructure'], function () {
require([
'content_tab/apps/content-tab-app',
], function(ContentTabApp) {
var appOptions = {
el: "#ann-content-app",
contenttype: contenttype,
threads_obj: threads_obj,
thread_obj: thread_obj
};
var content_tab_App = new ContentTabApp(appOptions);
Backbone.history.start({
pushState: true,
hashChange: false
});
});
});
});
I had the first line (with data-main) because I thought it was required but now I think it is superplous.
But then if I remove that line, how would the page know that it needs to download require.js itself?
You are on track. In this case what you are trying to do is load require.js script and pass data-main attribute pointing to entry-point of your application which is main.js
However, there are several other patterns you should take a look at
<script data-main="{% static 'site_common/js/main.js' %}" src='{% static "site_common/bower_components/requirejs/require.js" %}'></script>
...then in your main.js
require(["site_common/js/config"], // dependencies 1st loaded
function () { // callback after dependencies above have loaded successfully
require(['infrastructure'], // this loads 2nd
function () { // after second nested dependency loaded this callback will be executed
require(['content_tab/apps/content-tab-app'], // this loads 3rd
function(ContentTabApp) { // after third dependency has loaded this function will be called
var appOptions = {
el: "#ann-content-app",
contenttype: contenttype,
threads_obj: threads_obj,
thread_obj: thread_obj
};
var content_tab_App = new ContentTabApp(appOptions);
Backbone.history.start({
pushState: true,
hashChange: false
});
});
});
});

using require.js with FB SDK

I would like to load FB SDK using require.js.
my test case is something like this:
test.js:
require([
'libs/facebook/fb'
], function(FB){
FB.api("/me", function(){});
));
I would like to have test.js run only after FB SDK is loaded, and have FB ready for it.
Any thoughts on how this can be achieved? what should my wrapper (libs/facebook/fb.js) have?
It doesn't seem like the FB API is an AMD module, so it doesn't define itself in a manner to which RequireJS is accustomed to. You will need to shim the FB API using require.config. I'm assuming test.js is the script you have provided as the data-main value for RequireJS.
require.config({
shim: {
'facebook' : {
exports: 'FB'
}
},
paths: {
'facebook' : 'libs/facebook/fb'
}
});
require(['facebook'], function(FB){
FB.api('/me', function(){});
});
Or wrap the init code in a module (the sample uses Dojo):
define( 'facebook',
[ 'dojo/dom-construct',
'dojo/_base/window',
'https://connect.facebook.net/en_US/all/debug.js' ], // remove "/debug" in live env
function( domConstruct, win )
{
// add Facebook div
domConstruct.create( 'div', { id:'fb-root' }, win.body(), 'first' );
// init the Facebook JS SDK
FB.init( {
appId: '1234567890', // App ID from the App Dashboard
channelUrl: '//' + window.location.hostname + '/facebook-channel.html', // Channel File for x-domain communication
status: true, // check the login status upon init?
cookie: true, // set sessions cookies to allow your server to access the session?
xfbml: true // parse XFBML tags on this page?
} );
// Additional initialization code such as adding Event Listeners goes here
console.log( 'Facebook ready' );
return FB;
}
);
Here is a Documentation from Facebook:
https://developers.facebook.com/docs/javascript/howto/requirejs/
require.config({
shim: {
'facebook' : {
export: 'FB'
}
},
paths: {
'facebook': '//connect.facebook.net/en_US/all'
}
})
require(['fb']);
and then add the module like this:
define(['facebook'], function(){
FB.init({
appId : 'YOUR_APP_ID',
channelUrl : '//yourdomain.com/channel.html'
});
FB.getLoginStatus(function(response) {
console.log(response);
});
});
Building on voidstate's and Dzulqarnain Nasir's Answers, here is the code I ended up using on my project.
The part that tripped me up the most was that FB.init() is apparently asynchronous. In trying to envoke the callback() (without FB.getLoginStatus), FB was not yet initialized, and I was getting "An active access token must be used to query information about the current user." errors.
RequireJS Shim Config
require.config({
// paths: { 'facebookSDK': '//connect.facebook.net/en_US/all/debug' }, // development
paths: { 'facebookSDK': '//connect.facebook.net/en_US/all' }, // production
shim: { 'facebookSDK': { exports: 'FB' } }
});
AMD Module to Initialize Facebook JS SDK
define(['facebookSDK'], function (FB) {
'use strict';
return function (settings, callback) {
var args = {
appId: settings.appId,
channelUrl: settings.channelUrl,
status: true,
cookie: true,
xfbml: true
};
console.log('Calling FB.init:', args);
FB.init(args);
if (callback && typeof (callback) === "function") {
// callback() // does not work, FB.init() is not yet finished
FB.getLoginStatus(callback);
}
};
});
This still doesn't quite address the Original Question's desired usage.
OP's code could maybe be rewritten as:
require(['libs/facebook/fb'], // where fb.js holds my above Module
function(FBinit){
FBinit({
appId: appId,
channelUrl: channelUrl
}, function(){
FB.api("/me", function(){});
});
}
);
This isn't quite as cleanas OP's original concept, but its the best I could figure out. If anyone has any, I'd love some feedback or advice on how to improve my approach. I am still very much new to RequireJS.

Categories

Resources