I am using the RequireJS i18n plugin to load translations into my application. I'm struggling with the concept of runtime determination of a user's preferred language.
The plugin works well if you're using navigator.language to determine the user's preferred language, but in my app, the user's language is held in a database on the server. So I need to set the locale at runtime:
require.config({
config: {
i18n: {
locale: userLocale
}
}
});
So what I need is a clever way of setting userLocale before RequireJS has loaded my application. Does anyone know what would be the best way to achieve this? Possibilities include:
1) Setting userLocale outside of my application, in a non-AMD way:
//run Ajax call to determine user's localization preferencess
var Localization = Localization || getUserLocalization();
//and then...
require.config({
config: {
i18n: {
locale: Localization.userLocale
}
}
});
require(['app']);
This makes me a little bit sad as it means some of my application will be outside of RequireJS, and thus untidy. It also means all the user's localization settings (language timezone, date format, numeric format) will be held in the global namespace.
2) Having a separate require call to retrieve localization settings
I'm not sure how this work, but perhaps:
var Localization = require(['localization']);
require.config({
config: {
i18n: {
locale: Localization.userLocale
}
}
});
require(['app']);
Perhaps this wouldn't work due to asynchronousness? Also the app would not have access to the Localization object, so it would still need to be stored as a global variable.
Can anyone see a good solution to this problem? Has anyone used the RequireJS i18n plugin to do something similar?
It seems after a lot of research, the best approach for solving this problem is to check localStorage for a locale value. If this hasn't been set yet, I load the application using a dummy language:
var locale = localStorage.getItem('locale') || 'dummy';
require.config({
config: {
i18n: {
locale: locale
}
}
});
require(['app']);
I use a language called dummy, set to an empty object in my nls file. Using a dummy, rather than a default, means I don't have to guess what the user's language might be, and potentially force them to download a whole load of translations in the wrong language:
define({
"root": false,
"dummy": {}, //dummy language with no translations if user language is unknown
"fr": true,
"en": true,
"en-uk": true,
"fr-fr": true
});
Then, when the app has loaded and the user has been logged in, I query the database using a service call, set the language in localStorage and reload the app using location.reload():
//retrieve user object (including preferred locale) from service call
user = getUserObject(userId);
locale = localStorage.getItem('locale');
if (!locale || locale !== user.locale) {
localStorage.setItem('locale', user.locale);
//reload the app
location.reload();
}
Of course, I need to support old version of IE so I have also included fallbacks using userData, but this is the gist of the solution.
This approach is partially taken from how the guys at RESThub do it.
Another option, if your page is dynamically generated with a template system, is to have the require.config({config {..} }) inlined into the generated HTML... say like this:
<!-- load require.js -->
<script src="js/lib/require.js"></script>
<!-- standard config options in this file -->
<script src="js/config.js"></script>
<!-- user specific config inlined in the Dynamic HTML -->
<script>
// now set the user's preferred locale
require.config({
config : {
i18n: {
locale: '<% user.locale %>' // i.e. use PHP to insert user's preferred language
}
}
});
require(['app']); // Call your main app here
</script>
require.config(..) can be called multiple times, but should be done before your app is loaded.
Related
I'm trying to setup localisation for an existing app. I've gotten fairly far. But something is not quite right.
My setup is having multiple translation calls in the files like so: {attribute: 'stress', text: t('dass21::I found it hard to wind down')},
As you can see, I changed the namespace separator to be a double :: since I have a lot of medical specific sentences which are hard to think keys for, so the actual sentence is the key.
To export the translation into json translations files I use: i18next 'src/**/*.js' which works fine. It makes a folder for every language, and in that folder, for every namespace it makes a file. (Eg in this case, it will make a file dass21.json).
My configuration for the i18next-parser is:
module.exports = {
// Since keys hold regular english I can't have the default settings
namespaceSeparator: '::',
keySeparator: '_',
pluralSeparator: "|",
locales: ['en', 'es'],
output: 'assets/locales/$LOCALE/$NAMESPACE.json',
}
I matches those settings in the i18next init like so:
import i18next from "i18next";
import HttpApi from 'i18next-http-backend';
i18next
.use(HttpApi)
.init({
backend: {
// for all available options read the backend's repository readme file
loadPath: '/assessments/locales/{{lng}}/{{ns}}.json',
},
fallbackLng: 'en',
lng: 'en',
supportedLngs: ['en', 'es'],
namespaceSeparator: '::',
keySeparator: '_',
pluralSeparator: "|",
});
const t = i18next.t
export default t
As you can see, I want to load my translation files over http (which are served by a node server and this works). However, it only tries to load a generic translation file called translation.js and none of my specific namespace files. Even though the call to t with dass21::I found it hard to wind down is called. (Since it is visible on the screen using its key).
How can I make sure it also tries to load the namespace specific files over http, while having the custom namespace separators?
If you're using i18next, make sure you pass in the desired namespace in to the useTranslation or withTranslation function: https://react.i18next.com/guides/multiple-translation-files
Alternatively, use the ns init option and define your namespaces.
tl:dr;
class ModuleInBundleA extends ModuleInBundleC { … }
window.moduleInBundleB.foo(new ModuleInBundleA())
class ModuleInBundleB {
public foo(bar: ModuleInBundleA|ModuleInBundleC|number) {
if (bar instanceof ModuleInBundleA || bar instanceof ModuleInBundleC) {
// always false
…
}
}
}
Details:
I'm trying to start using TypeScript + Webpack 4.41.6 on the project that has mostly old codebase. Basically I want to package several small modules onto bundles to migrate softly without moving whole project onto new js stack.
I found out that Webpack can do this with code splitting, and package shared code into bundles on it's own with some configuration. However I can't really control what will be in every bundle unless I build every bundle separately and then only share types, using my own modules as external libraries and that's bit frustrating.
Maybe on this point you can say that I'm doing something wrong already and I would like to hear how can I achieve my goal of using bundles just as vanilla javascript (controlling defer/async on my own and using script tag on my own as well), and I don't really want to pack everything as an independent package with own configuration, types export and so on.
Hope you got overall context. Closer to the point.
I have the following function, that is bundled to it's own chunk called modal-manager.js.
public showModal (modal: ModalFilter|AbstractModal|number) {
let modalId: number;
console.log(modal);
console.log(typeof modal);
console.log(modal instanceof ModalFilter);
console.log(modal instanceof AbstractModal);
if (modal instanceof AbstractModal) {
modalId = modal.getId();
} else {
modalId = modal;
}
...
};
(Originally it had no ModalFilter as ModalFilter inherits AbstractModal but I included it for demonstration purposes)
The abstract modal is bundled automatically to modal-utils.js as it's shared between modules.
Next, I have another big bundle called filter.js. This one literally creates instance of ModalFilter const modalFilter = new ModalFilter(...). I think it's work mentioning that instance of modalFilter declared to the global window variable. The trouble is that filter.js calls modal.js code (through window.modalFilter.showModal(modalFilter)) with no problems whatsoever, but I see the following result of console.log:
ModalFilter {shown: false, loading: false, closing: false, html: init(1), id: 0, …}
modal.bundle.23e2a2cb.js:264 object
modal.bundle.23e2a2cb.js:265 false
modal.bundle.23e2a2cb.js:266 false
I disabled mapping to get more into code and see this:
ModalManager.prototype.showModal = function (modal) {
var modalId;
console.log(modal);
console.log(typeof modal);
console.log(modal instanceof _builder_component_modal_filter__WEBPACK_IMPORTED_MODULE_1__[/* default */ "a"]);
console.log(modal instanceof _modal_abstract__WEBPACK_IMPORTED_MODULE_0__[/* default */ "a"]);
if (modal instanceof _modal_abstract__WEBPACK_IMPORTED_MODULE_0__[/* default */ "a"]) {
modalId = modal.getId();
}
else {
modalId = modal;
}
this.modals[modalId].show();
this.scrollLock(modalId);
};
With my understanding of how javascript works, instanceof should check the object-creator function. As code chunks separated (modal.js has no same code with modal-utils.js) the creator function should be the same. However, getting more to the details I see that webpackJsonp can be really tricky and calling them from kind-of independent environments, still it should be the same environment where FilterModal, AbstractModal is called. The ModalManager could have own environment I believe. But code called is 100% the same. Could that webpackJsonp bundle-arrays be the source of the problem? If so, how can I avoid that and make modal.js bundle understand that both filter.js and others reference the same AbstractModal from modal-utils.js?
If I'm doing it wrong, is there a simple way to start bundling small and efficient scripts build with TypeScript and Webpack (or other tools)?
Also, I see the externals feature of Webpack, but haven't figured out how to use that in my case. In general, I'm ok with current set up except instanceof issue. The reason I want to avoid multiple builds is that I'll probably have dozens of smaller bundles that shared across different modules and having dozen of npm packages for each seems excessive.
Prefacing this with; I don't know the answer to the exact issue that you are facing in regards to the instanceOf part of your question. This is aimed at the "how did you do it" part.
Approx. 4 weeks ago we also changed from a .js to .ts implementation with about 1-2 hunderd .js files. Obviously we didn't want to migrate these all at once over to .ts as the effort was too high.
What we ended up doing was identifying .js scripts which needed to run on specific pages and added these into webpack as entry files. Then for all of the other suporting scripts, if we required their contents in our new .ts files, we actually created a large index/barrel file for them all, imported them in and then webpack will automatically include these in the correct scope alongside their respective .ts files.
What does this look like?
legacy.index.ts: For every single supporting .js file that we wanted to reference in any way in .ts.
var someFile_js = require("someFile.js");
export { someFile_js };
This then allowed us to import and use this in the .ts files:
import { someFile_js } from './legacy.index';
In reply to #tonix. To load a defined list:
webpack.config
const SITE_INDEX = require('./path-to-js-file/list.js')
module.exports = {
entry: SITE_INDEX
...
}
list.js
{
"filename1": "./some-path/filename1.js"
"filename2": "./some-path/filename2.ts"
}
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 :)
I am using Twitter digits for authentication. It needs a small .js script to be downloaded and initialize it. They recommend directly fetching the file from their server.
I have to import
<script id="digits-sdk" src="https://cdn.digits.com/1/sdk.js" async></script>
and initialize the sdk using like Digits.init({}).
I am using angular2 webpack starter template which uses webpack.
Earlier I was using systemjs, where I just map the file name to the url and import it at my component. Like this
var map = { 'Digits':'https://cdn.digits.com/1/sdk' };
then import it in angular component like import * as Digits from 'Digits';
I know there is webpack externals, but it is inconsistent.
console.log(Digits) in ngOnInit() sometime shows the correct object sometimes undefined error.
I've had some luck at this. I'm using a calendar js file and wanted to get the date result from an input to my controller. In my case it was Pickaday. First import the file as you normally would in whatever your main html file is.
<script src="/assets/js/pikaday.js"></script>
Then create a class the represents the functions you need.
export class PikadaySource {
field: any;
firstDay: number;
minDate: Date;
maxDate: Date;
yearRange: number = 1;
disableWeekends: boolean;
format: string = 'MM-DD-YYYY';
init(){};
}
The purpose of the class is to prevent the transpiler from having issues. Since the imported script is in global scope it should now be usable.
We have an app using require.js, that at the same time allows extensions, and these extensions' js gets served from a different path. So in our config we have:
var require = {
baseUrl : '/app/js/modules/'
}
But the extension is served from /extension/foo-extension/js/modules/.
Also, extensions are dynamic to the point that they inject some html into the page that uses a data-module="foo/bar" which we pick up on to load that module.
Ideally we'd be able to pass / set a context for require.js that scoped the following module loads to be within baseUrl /extension/foo-extension/js/modules/. As far as I can tell we would need to do require('/extension/foo-extension/js/modules/foo/bar') to load foo/bar from foo-extension.
Here is some pseudo code to imagine where we need to handle setting the path / context:
define(['some-dep'], function(SomeDep) {
$.get('somepage', function(html) {
var extension = html.data('extension'); // "foo/bar"
var extensionBase = html.data('extensionBase'); // extension/foo-extension/js/modules/
// This is where we need to readjust require to use the base path for any foo module
});
});
Is there another solution to this part from going the absolute path route?
requirejs.config() can be called later, at any time, and the loader will merge the configs together. So it should be enough to wait until you know what you want to use for the paths config for 'extension', then set it via another requirejs.config() call, then do the loading of the extension with that path.
It sounds like you'd want to use the "paths" configuration option. Example:
var require = {
baseUrl : '/app/js/modules',
paths : {
'extension' : '/extension/foo-extension/js/modules'
}
};
Then you could simply call require('extension/foo/bar') to load /app/js/modules/extension/foo-extension/js/modules/foo/bar.js