Multilingual in Meteor - meteor

I'm developing a multilingual app in Meteor.js
I would like to know about best way in your opinion to do that; as example here is wat I'm doing right now (pretty sure that can be done better);
First I save items in mongodb with properties neted in a language root:
{
en: {
name: "english name",
content: "english content"
},
it: {
name: "italian name",
content: "italian content"
},
//since images are the same for both, are not nested
images: {
mainImage: "dataURL",
mainThumb: "dataURL"
}
}
Then I publish a subscription using currentLang session variable:
Meteor.publish("elementsCurrentLang", function(currentLang) {
var projection = {
images: 1
};
projection[currentLang] = 1;
return Elements.find({}, projection);
});
I subscribe on route using Iron Router waitOn hook:
Router.route('/eng/elements', {
waitOn: function() {
return Meteor.subscribe("municipalitiesCurrentLang", Session.get('currentLang'));
},
action: function() {
this.layout('ApplicationLayout');
this.render('elements');
}
});
Now the first problem: I would like to reuse the same template for every language, but I can't simply put in the template {{name}} or {{content}} since the subscription returns the attributes nested under lang root, so it is needed to do for example {{en.name}} for english or {{it.name}} for italian;
To avoid this I use a template helper that buids a new object; essentially it removes attributes from the lang root:
Template.elements.helpers({
elements: function() {
var elements = Elements.find();
var currentLang = Session.get('currentLang');
var resultList = [];
elements.forEach(function(element, index) {
var element = {
name: element[currentLang].name,
content: element[currentLang].nameUrl,
images: element.images
};
resultList.push(element);
});
return resultList;
}
});
And now in the template I can access attributes like wanted:
<h1>{{name}}</h1>
<p>{{content}}</p>
Before continuing with this approach I want to listen for suggestions, since I don't know if this will work well; when Session.currentLang will change, the subscription will be reloaded?
is there a way to avoid the forEach loop in template helpers?

I'm developping a multilangage web app too and I advise you to use a package, like this one : https://atmospherejs.com/tap/i18n
You can change the langage reactively. Have the same template for all your langages, as you want !
You can put it as a parameter in the route.
Personnaly I use it as a Session variable and in the user profile !
If you use this package, you also can export your app, or part of it, more easily as many developpers will use the same code.
you put all your words in json files :
en.i18n.json:
{
"hello": "hello"
}
fr.i18n.json:
{
"hello": "bonjour"
}
and
{{_ "hello" }}
will write hello or bonjour depending of the langage set. You can set it with :
TAPi18n.setLanguage(getUserLanguage())
//getUserLanguage() <- my function to get the current langage in the user profile or
the one used by the navigator

This module does what you're looking for
https://github.com/TAPevents/tap-i18n-db
As the developer says: "Extends the tap:i18n package to allow the translation of collections."

Finally there is a package which is very complete (it also works with number formats, locales...) and is being updated frequently.
https://github.com/vazco/meteor-universe-i18n
You can also install https://atmospherejs.com/universe/i18n-blaze for using it with blade.
Just name your files with the pattern locale.i80n.json and its contents like
{
name: "english name",
content: "english content"
}
then translate your strings with {{__ 'name'}}.

Related

How to change meteor router paths/slugs without reloading the template?

I have two router paths, one is /topic/:postId and one is /topic/:postId/:commentId. Both paths are pointing to the template "topic".
The first path opens the template with a list of all the post's comments. Whenever I select a comment, I filter some contents on the page based on the comment and I want the route to change to the second path (allowing users to copy the link to this filtered page directly), but I want meteor to stay on the same template and not reload the page.
In short, how can I change the URL/slugs without reloading the page?
Edit: These are my routes pointing to the same template and I'd like to jump between them without (noticeable) reload. I left out waitOn and multiple data lines for simplicity' sake:
Router.route("/topic/:postId", {
name: "Topic",
data: function () {
return {
content: "topic",
post: this.params.postId
};
}
});
Router.route("/topic/:postId/:commentId", {
name: "Comment",
data: function () {
return {
content: "topic",
post: this.params.postId,
comment: this.params.commentId
};
}
});

Meteor Iron Router. One Route, Multiple Templates. How?

I'm using the latest Meteor and Iron Router. Imagine have a 'customComputer' collection. Computers have three different states: 'ordering', 'building', 'shipped'.
Right now, I'm using three different routes for this, each with a different template
/o/_id
/b/_id
/s/_id
A computer can't be in 2 states at once, so I'd like to have one route. How do I wrangle the templates?
/c/_id
The best I can come up with is to make a "main" template that links to the others. Is this a best practice?
{{#if isOrder}}{{>orderTemplate}}{{/if}}
{{#if isBuilding}}{{>buildingTemplate}}{{/if}}
{{#if isShipped}}{{>shippedTemplate}}{{/if}}
Or dynamic templates
Here's the route:
Router.route('order', {
path: '/o/:b/:computerId',
onAfterAction: function() {
if (this.title) document.title = this.title;
},
data: function() {
if(!this.ready()) return;
var o = Computer.findOne(this.params.computerId);
if(!o) throw new Meteor.Error('Computer not found');
return o;
},
waitOn: function() {
if (!Meteor.userId()) return this.next(); // Don't subscribe if not logged in.
return [
Meteor.subscribe('computerById', this.params.computerId),
Meteor.subscribe('myProfile'),
];
},
});
Is there a better way?
I'd do your main template idea, or the dynamic template.
dynamic template tends to be better when you have quite a few options that can be dynamically configured.
But the main template I think ends up being more obvious when you only have a couple of choices.
Either way can be converted easily to the other if you think you need the other option.

Passing a Blaze Template as a Parameter to a function creates Famous.js surface

I am trying to update my famous.js surfaces' content by using Meteor's Blaze.toHTMLWithData(template, data), like Blaze.toHTMLWithData(Template.roomIlanSpecsTemplate, data), with a custom template in a function creating a famous surface inside a famous view. I want to pass the template in the cursorToArray function depending on the type of document returned to its callbacks. But I cannot have a rendered page on the browser, even there is no error in the console. If I use hardcoded version like having createFn function for each different template and then defininig and cursorToArray fucntion with that function it works.
What can be the thing I miss here?
cursorToArray = function(cursor, renderablesArray, template, createFn){
//each callback should decide which createFn to use based on result document, cos each result has a different template so a createFn.
cursor.observe({
addedAt: function(document, atIndex, before) {
renderablesArray.splice(atIndex, 0, createFn(document, template));//createRoomIlanView, createRoomRenterIlanView, createFriendLookupIlanView
},
changedAt: function(newDocument, oldDocument, atIndex) {
renderablesArray[atIndex] = createFn(newDocument, template);
},
});
}
cursorToArray(Ilans.find(), ilanViews, Template.roomIlanSpecsTemplate, createIlanView);
portion of the createFn definiton:
function createIlanView(data, template){
var ilanSpecsSurface = new Surface({
content: Blaze.toHTMLWithData(template, data),
properties: {
fontSize: "14px"
}
});
return ilanSpecsSurface;
}
If it is all about older Famous what about using Reactive Surface from https://stackoverflow.com/a/30445791/2288496
var ReactiveTemplate = famodev.ReactiveTemplate;
var reactive = new ReactiveTemplate({
template: Template.mytemplate,
data: Collection.find().fetch(),
properties: {}
});
A good example how to implement Routing, Subscriptions etc. https://github.com/sayawan/flowwy

Meteor and iron-router: dynamically specify a template?

Every example that I have seen with iron-router specifies the template name with a string. Is it possible to do this with a variable? Suppose you have several routes that all use the same dynamic path, and the same data function, but they all need different templates. Is there a way to do this without specifying a different route for every template (which would also mean changing the path I use)?
You can programmatically specify things like the template and the layout with a custom action function. The example below demonstrates showing a particular template if the required document is found based on an id in the route. You can use the same semantics for both routes and controllers.
var postsController = RouteController.extend({
waitOn: function() {
return Meteor.subscribe('post', this.params._id);
},
action: function() {
if (Posts.findOne(this.params._id)) {
this.layout('postsLayout');
this.render('posts');
} else {
this.render('notFound');
}
}
});
Once 0.8.2 is released it should be trivial to do with UI.dynamic even without Iron-Router: https://github.com/meteor/meteor/issues/2123.

Angular ui-router setup for this use case?

I want to redraw a line in a line chart without reloading it (neither template nor controller) completely when navigating from country/5 to country/7. Can this be done with ui-router?
State
country/:id
Template with directive - country.html
<lineChart data="scope.someData">
Controller
onStateParamsChange => fetch data, set scope.someData
As of today, there is no official support for what you're looking for, which in UI Router parlance is considered 'dynamic parameters'. However, if you check out this experimental branch and help us out by testing and providing feedback, it will get merged to master sooner.
Set up your route/state like so:
$stateProvider.state("country", {
url: "/country/{id:int}",
params: { id: { dynamic: true } }
/* other state configuration here */
});
Then, in your controller, you can observe changes to id like so:
$stateParams.$observe("id", function(val) {
// val is the updated value of $stateParams.id
// Here's where you can do your logic to fetch new data & update $scope
});

Resources