I'm looking for a localisation system to use in a rails 2.2.2 app that i manage. I think i've seen one before that does exactly what i want but i can't manage to find it with google.
The system i have in mind works as follows: any ActiveRecord model (or perhaps a list of specified models, listed in a config file for example) can have a translation record associated with it, which specifies a locale and the new values for one or more of the fields in that record, for the listed locale. Please note this is a seperate sort of problem to that solved by i18n: it's a way of setting locale-specific data, rather than locale-specific names of fields or similar "system-level" strings.
For example, let's say my site provides music lessons. In the UK (the default locale), musical notes which last one "beat" are called "crotchets"* while in the US they're called "quarter notes". So, i might have a lesson that has this data:
#a Lesson object, with an entry in the "lessons" table in the db
id: 1234
name: "My Lesson"
description: "In this lesson you'll learn about crotchets."
and I want this translation record associated with it:
id: 1
translatable_type: "Lesson"
translatable_id: 1234
locale: "US"
data: {:description => "In this lesson you'll learn about quarter notes"}
Now, when i view the lesson, the system checks the current locale, and sees if there's any current-locale-specific data for this lesson, sees that there is (the description) and displays that instead of the standard description.
Has anyone seen a system like this?
thanks, max
PS - in this example i've done a simple string substitution of "crotchets" to "quarter notes", which might tempt the reader to think i just need some kind of simple string substitution translation. But that's not the case - some of the US-specific names/descriptions will be quite different to their uk counterparts and so require a completely customisable us-facing name, description, etc.
*this might not be exactly musically true in all cases but that's outside the scope of this question :)
I think that Globalize2 may be what you are after for locale-specific data solutions (I've had success using Globalize3 for my Rails 3.2 apps). If not, then hopefully the alternative solutions section on Globalize2's Github repo README file will provide something else that will work for you with Rails 2.2. Good luck!
Related
I'm creating an application where products will be created by my customer (something like an e-commerce website), so I obviously require translated descriptions stored in database, I can't force my customer to learn git/yml.
I have two ideas on how to correctly localize descriptions (and eventually product name) and store them in database, but if there is a well-known approach that I should use, I would be really happy to know it.
The first idea seems the most logical for me, but I would like to make it "transparent" to me, I don't want to write joins everywhere, so some suggestion on how to achieve this, if it's the correct one, would be appreciated.
Idea 1:
Create a database table products (with name and description field set maybe to the default locale language), then a products_translations table which contains a table structured in this way:
products_translations
- id
- locale
- product_id
- name
- description
As an example: product_translation: { id: 1, locale: 'en', product_id: 3, name: 'toy', description: 'play' }
But I want to access to translations without the requirement to write a lot of IFs everywhere. So if I write product.name it should return en or it based on current locale.
Bonus: Are there any gems that can help me to achieve this?
Idea 2: The other idea is to have a table with name_locale1, name_it and so on, but I don't like this approach because will pollute my model objects with fields and I will have a giant table.
However, in this way I can avoid join on every query for that object.
If there is a greater approach which I don't know about (a database pattern or similar), it's ok that too, I'm not forced to strict to only these two ideas, however I have to choose between the two and I really don't know which could be better.
Important: I would like to keep translations stored in yml files, except for dynamic contents, which obviously require translations in database.
I agree with PinnyM that the first approach is the better of the two, but rather than implement your own schema, I would highly recommend you implement Globalize3 where most of the structural decisions have been taken for you (and by Mr Fuchs himself, no less). Also, with the rails helpers, you just call something like product.name on a model instance and the gem will figure out how to display it in the correct locale, awesome!
The first approach is the recommended one. As you surmised, the second approach is not as clean and requires more work on the coding end with no real gain since you still have to join on this monster table. To the contrary, the first method requires at most one join, while the second approach requires a join on each attribute you may want to add localization support.
You can simply append a scope on all your product calls such as:
scope :for_locale, lambda{|locale| joins(:product_translations).
where(product_translations: {locale: locale || 'en'}) }
and pass in the session locale (or wherever you are storing it).
I have an existing Rails app that uses tire (0.4.0) to interface with an Elasticsearch (0.17.4) engine. It already has a couple of models using Tire::Persistence. I want to add a new model that takes advantage of Elasticsearch versioning, to track all changes and be able to revert to previous versions.
Right now when I retrieve any 'persisted' model instance, I check _version and it is always nil. I have not found any tire documentation that relates to versioning. Do I have to activate it somehow, or manually save records with version values? Am I even on the right track here?
I do see that certain methods return _version values for items, but others don't...
Article.first._version # => nil
Article.search("sample query").first._version # => nil
Article.find("id_123")._version # => 8
Also, versioning seems to increment by 2. Perhaps tire is not fully equipped to deal with versioning. Is it saving previous versions? How can I retrieve a previous version of a record?
[EDIT] I may have misunderstood what 'versioning' actually is in Elasticsearch. Seems like it's mostly for concurrency control. Oh wellz. (I would love to hear otherwise, though)
First of all, yes, as you write in the Edit, versions in ElasticSearch are not meant to store revisions of the document, but for concurrency control (eg. not overwriting a document with stale version).
Second, you have to declare that you want the versions returned back from search; http://www.elasticsearch.org/guide/reference/api/search/version.html
This code shows you how to do it in Tire.
require 'tire'
Tire.index('articles') do
delete
create
store id: 1, title: 'One'
store id: 2, title: 'Two'
store id: 2, title: 'Two again'
refresh
end
articles = Tire.search('articles') do
query { all }
version true
end.results
articles.each do |article|
puts "Article '#{article.title}' has version #{article._version}"
end
For the moment, it's best to read the Tire integration test suite for documentation. The documentation is of course something which should and will improve.
As for your original requirement itself, similar questions have regularly popped up for CouchDB, in the past. The Simple Document Versioning with CouchDB blog post describes one nice strategy. It would be beneficial for you to research the CouchDB solutions, since the document model is similar. (Of course, CouchDB, contrary to ElasticSearch does physically store the document revisions, thus it opens a different range of strategies.)
In ElasticSearch, your basic decision regarding working with revisions would be:
Do I want to store full revisions directly in the JSON itself?
This could make sense for smaller documents and smaller databases. Depending on your requirements, it could make searching for historic documents very simple.
The Nested Type in ElasticSearch would make working with these “revisions as nested documents” convenient and easy.
(You could also store just “diffs” of the documents in the JSON itself, but that would put more strain on your application logic, I bet.)
Do I want to store revisions as separate documents?
You may want to store revisions separately, and “link” them with their base document. The parent/child support in ElasticSearch would make it possible to work with those relationships and queries.
I'm building a multilang application that has two essential models Category and Product, where a Category has many Products..
So I want the ability to display the same categories with more than a language, for example, consider a category called Cars, it should be presented as Vehicules for a user using the french version of the application.
How could I do that? Should I store them in different models? or should I add a lang column in the Category model ?
What I thought of doing is adding a lang column in the Category model and add a default_scope call to scope it to search for only the desired language, I have two questions though:
How can I get the used language from inside a model, an I18n call ? Which method should I call on it ?
A problem arises from using this technique, a product which references a category in french wouldn't show up in a search under the category in english, how can I resolve this issue ?
Thank you
The key question you have to ask yourself is whether it's important that the Cars category should be the same object (implying the same object and the same URL) as the Vehicules category or not.
If they should be the same category, then the only question is how you translate the name into different languages. If you have a relatively small number of languages to support, you could simply store them all on the model using different columns (name_en, name_fr, etc).
Or you could store the translated names separately, such as using the I18n modules.
Alternatively, if Cars and Vehicules are separate categories, then you could follow your suggestion of adding a lang attribute to the model.
Frankie Roberto gave you a good answer. I will just add some additional info.
First the easy part: getting current locale in a model.
If you set the language in a controller as I18n.locale = something, then you may read it in a model the same way. The I18n is a global constant, after all.
Now about searching:
I generally prefer the designs where you have one category, but the name of the category is translated to many languages. The design where one car belongs to category "Cars" and another one belongs to "Vehicules" is flawed IMHO. In the later case there is no sense of making it a single site - you could create a one-language application, and install it in multiple instances, where every one is translated to a single language.
If you have a short, static list of supported languages, you may add a column for every language: name_en, name_fr, name_pl.
If your list of languages will be large or unknown at the moment of designing your application, it would be easier to store the "name" (given as an example) as a serialized hash. So, you could get the intended translation as name["fr"], name["de"] and so on.
I have already learned (the hard way :-) ) that users usually are too lazy to provide all the translations (or they just do not know all the languages), so you should be prepared that some of your models will not have the data in a language you are trying to display or search.
For every 'translated' attribute you may want to prepare a method which will supply you with the most appropriate translation in case the required one is missing.
This method may work in a way similar to:
def translated_name
([I18n.locale] + other_languages).each do |l|
return name[l] unless name[l].blank?
end
return "" # or some default value - possibly from I18n.t("some.static.default")
end
Of course, you do not need to use I18n.locale, and you should define other_languages in a way which would (ideally) match user language preferences, possibly by analyzing the "Accept-Language" header.
I sometimes use the method name name_for(lang) or name_in(lang), if I have to support more languages for data than for interface (which I shall translate and put in I18n config files).
Ah, yes - searching. :)
If you have defined the "names" as separate attributes, you may just search the appropriate column.
If you have the "names" as a single, serialized hash, you may search the column as a simple text (however I am not sure whether YAML will not mangle the non-ASCII characters) or create another column with searchable data, and then (at the model-level, not at the SQL level) partition the models into groups: "The search string has been found in your language", "The search string has been found in some other language". I believe the user of multi-lang service will be happy with search results presented in this way.
i'm using this gem to solve this kind of problem: https://github.com/jo/puret
I need to make certain output from views dynamic based on the "type" of user logged in. Specifically, the user "type" is based on the type of business they are in and using my application, so if they are a retail store my application needs to refer to "customers", "products", "purchases", etc. whereas another type of user in a services-based business expects to see references to "clients", "services", "engagements" and so on. I've abstracted other aspects of my application so that they can work with both types of users, but I need a way to dynamically display "customer" vs. "client", "product" vs. "service", and so on. (I've simplified the problem somewhat - the wording differences are more pervasive than the examples I've provided here, and there are some situations where the wording differences also appear in models, such as in validation error messages.)
This seems somewhat like locales and internationalization, but I don't want to kludge together a solution based on locales then run into a problem when I actually need to support other languages. In Java, I think this would be solved by resource bundles - that's the type of thing I believe I need.
What's the "Rails way" of solving this problem? Thanks in advance.
What about using a YAML file with each user type represented... so you could do something like this (have not tested this, but general idea may be useful):
config/language.yml:
customer:
product_name: Product
client:
product_name: Service
config/initializers/load_config.rb:
LANGUAGE = YAML.load_file("#{RAILS_ROOT}/config/language.yml")
view:
<%= LANGUAGE[user.type].product_name %>
I am developing a Ruby on Rails website and I have an "architectural" question : my application needs some parameters and I'm wondering where to store them.
In concrete terms, my application receive some requests which are evaluated and then sent. So, the Request model must have attributes concerning these treatments : a validation status and a sending status. For instance, validation status can be "accepted", "rejected" or "waiting". Sending status can be "sent", "waiting", "error during sending" or stuff like that. I have to store those status codes parameters somewhere, but I don't know what is the best solution.
I could create a model for each one and store them in the database (and having an active record model ValidationStatus for instance) but : wouldn't it be a bite excessive to create a database/model for storing data like that?
I could also just use them in the code without "storing" them, I could store them in a YAML file...
So, a more simpler question: how do you deal with your application parameters in RoR?
There are lots of global configuration plugins, most of them revolve around the idea of loading a YAML file at some point. Check this page, this plugin and even this Railscast.
I put them in the database. I have a lot of these, and they are all pretty straightforward lists of strings. The tables are all the same - id, name, description.
I generate models for them rather than having an actual model file for each one. In app/models I have a file called active_record_enums.rb, which in your case would look something like this:
ACTIVE_RECORD_ENUMS = %w{
ValidationStatus
SendingStatus
}
ACTIVE_RECORD_ENUMS.each do |classname|
eval "class #{classname} < ActiveRecord::Base; end"
classname.constantsize.class_eval do
# Add useful methods - id_for(name) and value_for(id) are handy
end
end
This file has to be required in a config file somewhere; other than that it's pretty straightforward.
(Have since viewed that rails cast mentioned above [episode 85] - it looks like a bit more 'the rails way' than below)
Another approach is to build on the existing configuration mechanism in Rails.
Lets presume there are two types of configs:
App wide configs common to dev/test/prod environments
Configs specific to envrionments dev/test/prod
For the first scenario, items in "RAILS_ROOT + '/config/environment.rb'" work. Just see that the names are captialised so they are Ruby constants. A variation to this is have a reference to another file in that environment.rb file ...
require RAILS_ROOT + '/config/appConfigCommon.rb'
and place relevant config items in that file. This has the advantage of being able to be referenced independant of Rails.
For scenario 2, a similar approach can be taken. Place items for development in "RAILS_ROOT + '/config/environments/development.rb'" or something like
require RAILS_ROOT + '/config/environments/appConfigDev.rb'
and place environment specific items in that required file, making sure they start with caps. And follow the same pattern for test/prod (and others if need be).
The config items are directly accessible in views and controllers (not sure about models) by simply using the constant name.
I am not using Ruby but I will tell you that I started out (in ASP.NET) placing lots of settings in a Web.Config file (similar to a YAML). As time went on, though, the system evolved to the point where different instances needed different settings. So, almost all of them have migrated to the database. So...if you'll be deploying multiple instances of your site, I'd strongly recommend keeping settings in a table of your database (mine has just one record, with fields for various settings). If I had done this to start, I'd have saved a significant amount of time.
I tend to just make a string column for each, and use validates_inclusion_of to set what's acceptable for them.
class Request < ActiveRecord::Base
validates_inclusion_of :validation_status, :in => ['accepted','rejected','waiting']
validates_inclusion_of :sending_status, :in => ['sent','waiting','...']
end
If you need to have things happen (ie. emails sent) when the status changes, look into using the Acts As State Machine plugin to manage it.