I need to implement an application with multi-language support using an AngularJS front-end and a Ruby on Rails server.
I am looking for a reasonable approach to render translated templates in multiple languages. I have come up with an approach I would like feedback on.
In the Angular routes definitions, set the template property to an html partial that just has an ng-include with the src attribute value set by the controller. This approach is needed to dynamically modify the path to the template to be fetched from the server; it is described here:
AngularJS - How to use $routeParams in generating the templateUrl?
So the Angular route config would look like:
angular.module('myApp', []).
config(function ($routeProvider) {
$routeProvider.when('/sites/new', {
template: '<div ng-include src="templateUrl"></div>',
controller: 'RouteController'
});
});
And the controller would look like:
// Set a prefix on the URL path, something like “es”
function RouteController($scope, $routeParams) {
$scope.templateUrl = $routeParams.locale + "/template/sites/site";
}
Here $routeParams.locale is used to set the locale, but could be a variable set by user action. The approach for dynamically modifying the template URL path to add a locale prefix seems a bit convoluted, but I know of no other way.
On the Rails side, in routes.rb, add a route:
match '/:locale/template/*template' => 'template#get'
The route uses route globbing so the params[:template] value can be a multi-level path.
The TemplateController#get action just renders the partial determined by params[:template]
The Template controller code is something like:
class TemplateController < ApplicationController
layout false
caches_page :get
def get
render(template: "template/#{params[:template]}")
end
end
The Rails I18n support for translations is used in the erb templates, translating according to the locale parameter.
In production, caching would be turned on. This would avoid incurring translation overhead. The locale prefix of the URL path would result in a per language set of translated templates to be cached.
This approach pushes translations processing to the server side as much as possible.
Is there any fundamental problems with this approach?
Could it be done better?
You may be interested in the angular-translate module.
We achieved client side internationalization in AngularJS with the i18next JQuery plugin http://i18next.com/ and created a filter called i18n.
When you initialize the Angular Application, you initialize the i18n plugin where you can provide a pattern to locate the file containing labels, and in the template use this as an example for binding labels and values.
{{'mynamespace:labels.firstname' | i18n}}
where "mynamespace" is used to separate your labels logically and used to locate JSON files with the labels. Within json files you can have one or more JSON objects with labels as properties. In the above example, the file called mynamespace-i18n-en-US.js if you provided a pattern __ns-i18n-__lng__.js
Is there a reason why the translation has to happen on the server?
Do you really need to translate the entire template?
Related
I am learning Rails 5.0, via a tutorial. Learning how to generate view templates, but the term "template" is never explicitly defined. I've searched in the Rails docs, and they seem to mention the word a lot, but also never really define it explicitly.
I know that views are the HTML, CSS associated with what the user sees. But was wondering what is a template and how is it different than a standard webpage?
I don't have an authoritative answer. But this is really rather simple. RoR lets you generate content dynamically. This means, with one template, you could generate different content (html pages). The final html page generated and served by the server is the webpage endusers see. For example, you could have a template show.html.erb with the following line:
<h> Product <%=#product.name%> </h>
From this template, different webpages for each different #product can be generated with that #product's name, depending on the #product variable, which is provided by the controller.
So templates allow you to dynamically generate content and render them as different html webpages.
According to Marc Palmer
Preventing XSS attacks
In a nutshell, to protect your app from code injection XSS exploits
you must:
Set the default grails.views.default.codec in config to "HTML"
OK.
So if I have this below in my Config.groovy
grails.views.default.codec = "none"
And in my Controller, I add:
def afterInterceptor = { model ->
model.headerJs = "alert('bingo for '+[$params.unitId]);"
}
And in my GSP:
<r:script disposition="head">${headerJs}</r:script>
It works. I see the expected javascript alert when I do View Source and I get my alert when the page serves.
But, if in Config.groovy I apply the recommended change:
grails.views.default.codec = "html"
My GSP renders
<script type="text/javascript">alert('halooba for '+[1]);</script>
which I can see is very secure.
My goal with this app is to have custom JS snippets, various properties and other values stored for the customer in the Domain. These values would be entered by our Admins (not the customer). Based on who invokes the page with an HTTP request, such as www.mydomain.com/ThisApp/?customerId=13423 (but an encoded customerId) I'd make calls to Services from my Controller to fetch the associated settings for the customer from the Domain and inject them into the GSP.
I know that I can put JS and CSS into files and then use the Resources Plugin to bring them in properly, but I'm also looking at this method for specific customizations.
So, to follow this security method, I either need to be able to unencode this, or I need to determine another method for including javascript into the GSP that does not encode it.
Any suggestions?
THANKS!
You can suggest Grails not to escape by using raw() available in GSP as:
<r:script disposition="head">${raw(headerJs)}</r:script>
For Grails 2.2.x and below, you can put the recommended encoding in your Config.groovy:
grails.views.default.codec = "html"
and use a Taglib to bring in values that are SAFE and should not be HTML encoded:
<r:script><com_myapp:getJSDeferred unitId="${params.unitId}" /></r:script>
and it will be rendered raw.
FYI: Above solution will not allow for JSON output to assign to javascript variable.
My workaround, say you have model.data defined as hashmap:
var someVar= ${raw(data as JSON).toString())};
Is it possible to not using angualar routes, because if I defined an routes like:
app.config(function($routeProvider){
$routeProvider.when('/home')
})
my url will look like www.app.com/#home,
I dont want to change url, just www.app.com nice and clean, in this case how to defined template for defferent controller and how to load the template in ng-view, and how to pass url parameter?
any idea?
You cannot do that with the built-in routing mechanism because path is required for every route. However, you can use ui-router library in order to create URL-less states and navigate to them using either code or directive.
I created this plunker to demonstrate how to use ui-router to navigate among states without modifying URL.
I've worked a while now with Backbone.js, and one of the things I nowadays run into is; Sometimes you need to have serverside logic into a .eco.jst template
For example
an i18n translation (currently look at i18n.js gem for this)
a path a route without hardcoding it (somemodel_path(somemodel))
authorisation (for example, show a delete button if the user can destroy this model). Atm I solve this by passing in some rights object in the json that gets filled in.
Rendering a html helper like simple_form or S3_file_uploader (atm I solve this with rendering it serverside, and put the display on none)
As you know, .eco get parsed by node.js, so I can't call ruby in the eco files. Most of these problems I solve by basicly creating a "data" object in the head. Similar to this:
window.data = {
some_translation = "<%= t('cool') %>",
<%= "can_destoy_model = true," if can?('destroy', Model) %>
post_edit_link = "<%= post_path(#post) %>
}
Besides this being bulky (this is just an example, normally this would be more ordened or I add a html5 data attribute to some dom element), It's time consuming, sometimes you have to recreate complete business logic which otherwise would be a oneliner in rails (take for example the S3_file_uploader, which requires encoded amazon policyfile and a token)
What are your thoughts about this? Should I perhaps not use .eco (although I like templates in seperate files instead of poluting the view). Would I able to use serverside logic if I for instance used mustache or handlebars and which gem would you recommend if so?
My experience with Backbone.js is kind of limited, but I've managed to setup an environment with logic-less templates using the following gems:
handlebars_assets
haml_assets
And a bunch of other stuff, even a mini-framework I'm currently working on (you can find it here)
I picked this approach for building Single Page Applications using Backbone.
Basically, the haml_assets gem provides sprockets with the ability to parse .haml files, this is not needed but I love HAML syntax. The handlebars_assets gem provides means to parse Handlebars templates, both on the server-side and the client-side. You can use Ruby code inside the templates and you would solve both the i18n and the path methods problems you mentioned.
I've found these tools to be excellent to help DRY an application's templates, and it can really save you from adding logic inside templates. If you use Backbone Views to take, for example, decisions on whether to show a delete button or not, you can keep the logic inside the Backbone View, and use that logic to render the proper Handlebars template (or partial).
Using your example:
Coffeescript:
class ProjectShowView extends Backbone.View
template: (context) -> HandlebarsTemplates['projects/show'](context)
deleteButtonTemplate: (context) -> HandlebarsTemplates['projects/shared/delete_button'](context)
render: (canDelete = false) ->
#$el.html(#template(#model.toJSON()))
#$('.delete_button_container').append(#deleteButtonTemplate()) if canDelete
#
The example is quite primitive and basic, but can hopefully point in the right direction. I hope it helps!
In a view I am generating an HTML canvas of figures based on model data in an app. In the view I am preloading JSON model data in the page like this (to avoid an initial request back):
<script type="text/javascript" charset="utf-8">
<% ActiveRecord::Base.include_root_in_json = false -%>
var objects = <%= #objects.to_json(:include => :other_objects) %>;
...
Based on mouse (or touch) interaction I want to redirect to other parts of my app that are controller specific (such as view, edit, delete, etc.).
Rather than hard code the URLs in my JavaScript I want to generate them from Rails (which means it always adapts the latest routes).
It seems like I have one of three options:
Add an empty attr to the model that the controller fills in with the appropriate URL (we don't want to use routes in the model) before the JSON is generated
Generate custom JSON where I add the different URLs manually
Generate the URL as a template from Rails and replace the IDs in JavaScript as appropriate
I am starting to lean towards #1 for ease of implementation and maintainability.
Are there any other options that I am missing? Is #1 not the best?
Thanks!
Chris
I wrote a bit about this on my blog: Rails Dilemma: HATEOAS in XML/JSON Responses.
I came to similar conclusions. There's no incredibly clean way to do it as far as I know, because by default the model is responsible for creating a JSON representation of itself, but generating URLs is strictly a controller/view responsibility.
Feel free to look over my thoughts/conclusions and add comments here or there.