Rails I18n switch key based on user permissions - ruby-on-rails

I have a namespace in my routes:
namespace :fruit do
resources :apple, only [:create, :destroy]
resources :banana, only [:show]
end
and also the same key in my translation file:
en:
fruit:
apple:
name: apple
banana:
name: banana
This works just fine, what I want to do is to be able to use a completely new key for the namespace part if my user has particular permissions (user.is_a_vendor?). So for a vendor I would like the translations to be t('vendor.apple.name') instead of t('fruit.apple.name') for example but I don't want to have to conditionally do this in all of my views. Is there a way to centrally switch the keys based on the user? This seems like there should be a simple solution but I can't seem to figure it out.
I have a lot of views that already have their keys set and they use lazy loading so I was wondering if there's a way to conditionally change the lazy loading to use a different key in the top.
EDIT:
I don't want to use my own custom locale since I might want to have translations for these in other languages other than English and if I do that, this solution would lead to problems. I also don't want to use a helper since that would check the conditional every single time, I'd rather have it just select a locale key automatically. Some more research lead me down the path of i18n custom backends: https://guides.rubyonrails.org/i18n.html#using-different-backends, https://www.rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base#eager_load!-instance_method and it might be what I need to use to do this?

I would write a helper that did the switch for you. You then use your custom helper everywhere in your views, and within the helper decide which parent namespace you want to concatenate with the requested translation.

I think you can create a vendor locale (eg. en_vendor) then switch locale base on user type, so both cases you can keep the key t('fruit.apple.name') or lazy lookup t('.apple.name') on views, no need to change any views or replace(switch) keys at all.
# controller
around_action :switch_locale
def switch_locale(&action)
locale = current_user.is_a_vendor? ? :en_vendor : :en #"#{I18n.locale}_vendor"?
I18n.with_locale(locale, &action)
end
# en.yml
en:
fruit:
apple:
name: apple
banana:
name: banana
en_vendor:
fruit:
apple:
name: apple vendor
banana:
name: banana vendor
In case you already had locale file for vendor, i think you can copy/paste to or organize like en.yml file above and switch locale, it takes less effort than switch keys.

Related

Page needs to be refreshed after switching locale for Blacklight label to translate

I am trying to create an Arabic version of a Rails app which is based on Blacklight. Here's the problem I am facing:
When I switch the language, everything translates perfectly EXCEPT the Blacklight labels. It's only when I refresh the page that the labels get translated.
This is the label I want to display: "ترتيب حسب عام" (Translation: Sort by Year)
But this is what I am getting currently: "Year ترتيب حسب"
The same issue happens when switching back to English from Arabic - the label displays in Arabic until I refresh the page.
This is what my Blacklight configuration looks like:
class CatalogController < ApplicationController
include Blacklight::Catalog
configure_blacklight do |config|
# ...
config.add_sort_field 'pub_date_sort desc, title_sort asc', :label => I18n.t('sortby.year')
# ...
end
end
Here's how I am setting the locale in my Application controller:
class ApplicationController < ActionController::Base
before_filter :set_locale
def set_locale
I18n.locale = params[:locale] || I18n.default_locale
end
# ...
end
I am not sure what's happening and I would really appreciate your help in this.
I think the problem that the correct locale is not taken into account on the first page render lies in the fact that configure_blacklight is a class method that is run when the CatalogController class gets interpreted, i.e. before the before_filter for locale switching is run. (Actually I don't quite understand why it actually works after a page reload as I would expect the before filter always to be run after setting the blacklight configuration.)
Nevertheless, support for I18n-ing search and sort fields has recently been added to the Blacklight project - see Pull Request #1566. It seems that this change is not part of a public release yet so you need to use the master branch version to be able to actually use this feature.
Blacklight in general, as well as this new feature, uses Rails I18n API for localized labels so instead of manually specifying the labels you should use the I18n dictionary YAML files (see Rails Guides for more info and options).
In your particular case, the label for a sort field is looked up under the following key:
"blacklight.search.fields.sort.#{key}"
where key is the value of the first parameter that you pass to the sort field definition. Thus, for your particular field, you should define something like the following in your dictionaries:
en:
blacklight:
search:
fields:
sort:
pub_date_sort desc, title_sort asc: "Sort by Year"
(this would be for the English version). I am amazed that YAML has no problems with spaces and commas in the key definitions, but indeed I tested that such key works OK when used by the I18n API.

Keeping track of changes using rails - "changed?"

I am building a multi lingual website, using ruby on rails, where part of the content is supposed to be user generated and they are supposed to be able to create different versions of it for all languages. The language support is handled by i18n gem.
Part of their content is created using Markdown through http://daringfireball.net/projects/markdown/basics .
In my database I save: object.content_markdown_en, object.content_html_en, object.content_markdown_sv, object.content_html_sv and so on for the different locales.
Now if a user changes the content, new html is supposed to be generated. But it seems unnecessary to regenerate the html for all locales if he only made changes in one of the languages.
I thought there might be some way to use something like
if object.content_markdown_[locale]_changed?
generate_new_html
end
that can be run in a loop for all possible locales. But I can't find any nice ways of doing this.
How about:
[:en, :sv].each do |locale|
if object.send("content_markdown_#{ locale }_changed?".to_sym)
send("generate_new_#{ locale }_html".to_sym)
end
end
You can use send to call methods by name:
object.send("content_markdown_#{locale}_changed?".to_sym)
Your loop would look like this:
%w(en sv).each { |locale|
if object.send("content_markdown_#{locale}_changed?".to_sym)
generate_new_html
end
}
However, using a separate translation table might be a better approach.

Hook into Rails' I18n

I've written a little library to hold translated content in model attributes. All you have to do is add the following to a model:
class Page < ActiveRecord::Base
i18n_attributes :title, :content
end
By convention, the data is written to the real attributes i18n_title and i18n_content as a hash (or hstore hash for Postgres). And a number of getters and setters give you access to "localized virtual attributes":
page = Page.new
page.title_en = 'Hello'
page.title_es = 'Hola'
page.i18n_title # => { en: "Hello", es: "Hola" }
I18n.locale = :es
page.title # => "Hola"
page.title_en # => "Hello"
You can use these virtual attributes in forms as well, but there's a downside: The form builder uses I18n to get the label and translate attribute validation errors. And it does of course look for keys such as activerecord.attributes.page.title_en if you use title_enin the form.
It would be very cumbersome to replicate the same translation for every available_locale in the locales/en.yml etc files:
activerecord:
attributes:
page:
title_en: "Title"
title_es: "Title"
...
What I'd like to do is execute some code after Rails has loaded all locales in the boot process and then clone translations for these keys. Is there a way to do this? Maybe a hook which gets called after the translations have been loaded from the YAML files? (The translations are not yet loaded when my lib loads.)
Or do you see another way to tackle this problem? (I've tried to alias I18n.translate, but I'm afraid this might cause major headache in the future.)
Thanks for your hints!
Although you dropped this approach, please let me share my thoughts:
I don't think it is incredible useful to add other locale strings into a translation file for a specific localization. Since a config/locales/$locale.yml usually starts (at least in my case) with
$locale:
...
there is no need for activerecord.attributes.page.title_es in an English localization file. I'd just put it in es.yml as activerecord.attributes.page.title.
I mean: isn't that the whole purpose of separate localization files? (Or from the developer/translator point of view: In which file should I search for .title_es, in en.yml, es.yml or both?)

set constant values for cuisine like Chinese,Indian in ruby on rails

I want to use Cuisines like (Chinese, Indian, US) as constant values in my application which are defined in a config file. How can I set as constants and how can access in controllers?
This is explicitly not an answer to your question, but a suggestion that you look for alternatives. I think you would be far better off creating a database table with your cuisine names in it than to use constants. Leverage rails associations so that you can write nice readable code.
The problem with using constants is that under many circumstances, they aren't really constant. What happens if you want to add Japanese? What happens if you want to add Thai, but then 6 months later decide to drop it? What happens if you decide that Indian is too broad, and you want "Northern Indian" and "Southern Indian"?
With a database table, you can ensure that the class that are associated with those constants are always in a consistent state. When you need to get them all, they are just a line of code away with
my_cuisines = Cuisine.all
with nice built in iterators.
You can use gem 'settingslogic'
model settings.rb:
class Settings < Settingslogic
source "#{Rails.root}/config/settings.yml"
namespace Rails.env
end
then, use in controller:
Settings.cousines
First, consider what Marc Talbot said. Make sure that you really don't want a normal database model. If you're sure you want to use constants then continue on:
My preferred way to do this is with a pseudo-model.
In app/models/cuisine.rb
class Cuisine
# Should come before the constant declarations
def initialize(name)
#name = name
end
Mexican = new('Mexican')
Chinese = new('Chinese')
Indian = new('Indian')
def to_s
name
end
# other related methods
# like translations, descriptions, etc.
end
Then in the everywhere else in the app you can just reference Cuisine::Mexican or Cuisine::Indian
Also depending on how you are using it you might need a list of the cuisines.
class Cuisine
...
def self.all
[Mexican, Indian, Chinese, ...]
end
end
This technique keeps the code organized and keeps you from writing yet another initializer file.

translate database fields with rails

I know about the built-in I18n in Rails, but how can I select a database field per locale?
My model structure is something like this:
title #default (englisch)
title_de #(german)
title_it #(italian)
In my template I want to be able to write only
<%= #model.title %>
and should get the value in the right language.
Is there a plugin or a solution to use different fields per different locale settings with a structure like mine?
Although your db architecture (different locales hardcoded as table columns) seems wrong to me, I think you can achieve what you want by adding a pseudo-field to your model, something along:
# example not tested
class MyModel < ActiveRecord::Base
def localized_title(locale)
locale = locale == 'en' ? '' : '_' + locale
read_attribute("title#{locale}".to_sym")
end
end
Or, provided that you somehow make your current locale visible to your models, you can similarly overwrite the default title accessor method.
Edit: You can take a look at http://github.com/iain/translatable_columns, it seems pretty much compatible with your architecture....
Try using:
http://github.com/joshmh/globalize2
It may require renaming your columns (to a different standard).
Nowadays the best way to translate active record models fields is using https://github.com/shioyama/mobility

Resources