rails-breadcrumb and I18n - ruby-on-rails

I run into an issue with rails-breadcrumb since i localized my application.
In my Controller, I've got this :
class FooController < PrivateController
add_breadcrumb I18n.t('breadcrumbs.foo.index'), :foo_url
end
When my breadcrumb is displayed, the localized string is always taken from en.yml, no matter which language i set up in I18n.locale
After having look at the code, it occurs that add_breadcrumbacts as a before_filter, and after some test, i came to the conclusion that, even if the content of add_breadcrumb has the right locale, it seems that the value passed does not.
If I try this :
add_breadcrumb I18n.t('breadcrumbs.foo.index', :locale => "fr"), :foo_url
Everything goes fine.
How cas i force my string to be correctly localized?
Thank you per advance

I finaly got this. After i determined that my issue cames out of the fact that I18n didn't know anything about my locale as i was asking it to translate something, i monkey-patched the rails-breadcrumb to manage the localization itself.
Know i pass a Symbol as first parameters, and i call I18n.translate() in rails-breadcrumb
add_breadcrumb (:'breadcrumbs.foo.index'), :foo_url
d
# config/initializers/rails-breadcrumb-fix.rb
module Rails
module Breadcrumbs
class ActionController::Base
protected
def add_breadcrumb(name, url = '')
#breadcrumbs ||= []
# if given `name` is a Symbol, we localize it
if name.is_a?(Symbol)
name = I18n.t(name)
end
url = send(url) if url.is_a?(Symbol)
#breadcrumbs << [name, url]
end
def self.add_breadcrumb(name, url, options = {})
before_filter options do |controller|
controller.send(:add_breadcrumb, name, url)
end
end
end
module Helper
def breadcrumbs(separator = "›")
#breadcrumbs.map do |txt, path|
link_to_unless (path.blank? || current_page?(path)), h(txt), path
end.join(" #{separator} ").html_safe
end
end
end
end
ActionController::Base.send(:include, Rails::Breadcrumbs)
ActionView::Base.send(:include, Rails::Breadcrumbs::Helper)

Related

How to correctly implement I18n in Blacklight for field labels?

The docs are sketchy on how to do it properly. I tried this:
class ApplicationController < ActionController::Base
# ...
before_action :set_locale
def set_locale
session[:locale] = I18n.locale = params.delete(:locale) || session[:locale] || I18n.default_locale
end
end
And this suffices for a lot of things; however, my fields are configured like this (following the Blacklight guide):
class CatalogController < ApplicationController
include Blacklight::Catalog
configure_blacklight do |config|
# ...
config.add_facet_field 'date', label: 'Date', single: false
# ...
end
end
This configuration happens before the request is processed, so if I try to use I18n.t('Date') for label, it will not respond to changes in locale, and will always serve the labels corresponding to the default locale.
What is the "correct" way to do this?
EDIT: Found the solution for the individual fields. Still searching for the "proper" solution for the search fields (config.add_search_field). It seems those are just displaying their label if present, and #labelize-d key if not. As a quick stop-gap measure, I made this class:
class Localized
def initialize(key)
#key = key
end
def to_s
I18n.t(key)
end
end
and configured the search field with
... label: Localized.new('blacklight.search.general.all_fields')
Thanks to Surya, I saw where to look. The helper methods are already being invoked by the Blacklight's default templates, as long as one uses the correct keys. So, to localise the date field label in English, one needs the key named en.blacklight.search.fields.date.

Limiting download_links for ActiveAdmin based on AdminUser model

I am trying to limit who can access the csv/json/... exports in ActiveAdmin based on the field 'limited'. I'd like to a) hide the links and b) return nothing at all if the path were to get hit anyway
I tried the following:
index downloads_links: !current_admin_user.limited? do
# ...
end
as well as
csv do
return if current_admin_user.limited?
# ...
end
I also briefly tried using procs and lambda's but that's probably not the solution here either?
Neither appear to work and are giving me nomethoderrors on ActiveAdmin::DSLResource and ActiveAdmin::CSVBuilder respectively
Any tips are welcome, thank you
i was able to achieve this with a simple monkey patch, but i was using cancan. cancan helper method 'can?' worked fine, but i wasn't testing the 'current_admin_user'. please, try it
module ActiveAdmin
module Views
class PaginatedCollection
def build_download_format_links(formats = self.class.formats)
params = request.query_parameters.except :format, :commit
links = formats.map { |format| link_to format.to_s.upcase, params: params, format: format }
unless current_admin_user.limited?
div :class => "download_links" do
text_node [I18n.t('active_admin.download'), links].flatten.join(" ").html_safe
end
end
end
end
end
end
upd:
i've tried with current_admin_user, and it worked.
also if you need to limit the formats, you can redefine formats method it this module, using your 'limited' method:
module ActiveAdmin
module Views
class PaginatedCollection
def formats
if current_admin_user.limited?
#formats ||= [:csv] # anything you need for limited users
else
#formats ||= [:csv, :xml, :json]
end
#formats.clone
end
end
end
end

How to set defaults for parameters of url helper methods?

I use language code as a prefix, e.g. www.mydomain.com/en/posts/1.
This is what I did in routes.rb:
scope ":lang" do
resources :posts
end
Now I can easily use url helpers such as: post_path(post.id, :lang => :en). The problem is that I would like to use a value in a cookie as a default language. So I could write just post_path(post.id).
Is there any way how to set default values for parameters in url helpers? I can't find the source code of url helpers - can someone point me in the right direction?
Another way: I have already tried to set it in routes.rb but it's evaluated in startup time only, this does not work for me:
scope ":lang", :defaults => { :lang => lambda { "en" } } do
resources :posts
end
Ryan Bates covered this in todays railscast: http://railscasts.com/episodes/138-i18n-revised
You find the source for url_for here: http://api.rubyonrails.org/classes/ActionDispatch/Routing/UrlFor.html
You will see it merges the given options with url_options, which in turn calls default_url_options.
Add the following as private methods to your application_controller.rb and you should be set.
def locale_from_cookie
# retrieve the locale
end
def default_url_options(options = {})
{:lang => locale_from_cookie}
end
doesterr below has almost got it. That version of default_url_options won't play nice with others. You want to augment instead of clobber options passed in:
def locale_from_cookie
# retrieve the locale
end
def default_url_options(options = {})
options.merge(:lang => locale_from_cookie)
end
This is coding from my head, so no guarantee, but give this a try in an initializer:
module MyRoutingStuff
alias :original_url_for :url_for
def url_for(options = {})
options[:lang] = :en unless options[:lang] # whatever code you want to set your default
original_url_for
end
end
ActionDispatch::Routing::UrlFor.send(:include, MyRoutingStuff)
or straight monkey-patch...
module ActionDispatch
module Routing
module UrlFor
alias :original_url_for :url_for
def url_for(options = {})
options[:lang] = :en unless options[:lang] # whatever code you want to set your default
original_url_for
end
end
end
end
The code for url_for is in actionpack/lib/routing/url_for.rb in Rails 3.0.7

How to check if a localization exists?

I am creating a little website which is currently being translated into different languages. I use Rails' out-of-the-box translation: l18n. To change the localization, a parameter called locale must be given, e.g.: http://localhost:3000/?locale=nl.
In ApplicationController this parameter is saved into a session variable and used as localization. How can I check if the locale actually exists? Are there any built in functions, or do I need to add an exists: "true" to every localization file to check it?
Rails will default to "en" as the default locale in case if a locale doesn't exist. So to be nasty if I pass http://localhost:3000/?locale=de and that translation doesn't exist, 'en' will be used.
Have a look here http://guides.rubyonrails.org/i18n.html , especially the section "2.3 Setting and Passing the Locale"
#config/initializers/available_locales.rb
# Get loaded locales conveniently
module I18n
class << self
def available_locales; backend.available_locales; end
end
module Backend
class Simple
def available_locales; translations.keys.collect { |l| l.to_s }.sort; end
end
end
end
# You need to "force-initialize" loaded locales
I18n.backend.send(:init_translations)
AVAILABLE_LOCALES = I18n.backend.available_locales
RAILS_DEFAULT_LOGGER.debug "* Loaded locales: #{AVAILABLE_LOCALES.inspect}"
You can then wrap the constant for easy access in ApplicationController:
class ApplicationController < ActionController::Base
def available_locales; AVAILABLE_LOCALES; end
end
You can implement it like this in your ApplicationController:
before_filter :set_locale
def set_locale
I18n.locale = extract_locale_from_params
end
def extract_locale_from_params
parsed_locale = params[:locale]
(available_locales.include? parsed_locale) ? parsed_locale : nil
end
HTH

How do you find the namespace/module name programmatically in Ruby on Rails?

How do I find the name of the namespace or module 'Foo' in the filter below?
class ApplicationController < ActionController::Base
def get_module_name
#module_name = ???
end
end
class Foo::BarController < ApplicationController
before_filter :get_module_name
end
None of these solutions consider a constant with multiple parent modules. For instance:
A::B::C
As of Rails 3.2.x you can simply:
"A::B::C".deconstantize #=> "A::B"
As of Rails 3.1.x you can:
constant_name = "A::B::C"
constant_name.gsub( "::#{constant_name.demodulize}", '' )
This is because #demodulize is the opposite of #deconstantize:
"A::B::C".demodulize #=> "C"
If you really need to do this manually, try this:
constant_name = "A::B::C"
constant_name.split( '::' )[0,constant_name.split( '::' ).length-1]
For the simple case, You can use :
self.class.parent
This should do it:
def get_module_name
#module_name = self.class.to_s.split("::").first
end
For Rails 6.1
self.class.module_parent
Hettomei answer works fine up to Rails 6.0
DEPRECATION WARNING: Module#parent has been renamed to module_parent. parent is deprecated and will be removed in Rails 6.1.
This would work if the controller did have a module name, but would return the controller name if it did not.
class ApplicationController < ActionController::Base
def get_module_name
#module_name = self.class.name.split("::").first
end
end
However, if we change this up a bit to:
class ApplicatioNController < ActionController::Base
def get_module_name
my_class_name = self.class.name
if my_class_name.index("::").nil? then
#module_name = nil
else
#module_name = my_class_name.split("::").first
end
end
end
You can determine if the class has a module name or not and return something else other than the class name that you can test for.
I know this is an old thread, but I just came across the need to have separate navigation depending on the namespace of the controller. The solution I came up with was this in my application layout:
<%= render "#{controller.class.name[/^(\w*)::\w*$/, 1].try(:downcase)}/nav" %>
Which looks a bit complicated but basically does the following - it takes the controller class name, which would be for example "People" for a non-namespaced controller, and "Admin::Users" for a namespaced one. Using the [] string method with a regular expression that returns anything before two colons, or nil if there's nothing. It then changes that to lower case (the "try" is there in case there is no namespace and nil is returned). This then leaves us with either the namespace or nil. Then it simply renders the partial with or without the namespace, for example no namespace:
app/views/_nav.html.erb
or in the admin namespace:
app/views/admin/_nav.html.erb
Of course these partials have to exist for each namespace otherwise an error occurs. Now the navigation for each namespace will appear for every controller without having to change any controller or view.
my_class.name.underscore.split('/').slice(0..-2)
or
my_class.name.split('::').slice(0..-2)
With many sub-modules:
module ApplicationHelper
def namespace
controller.class.name.gsub(/(::)?\w+Controller$/, '')
end
end
Example: Foo::Bar::BazController => Foo::Bar
No one has mentioned using rpartition?
const_name = 'A::B::C'
namespace, _sep, module_name = const_name.rpartition('::')
# or if you just need the namespace
namespace = const_name.rpartition('::').first
I don't think there is a cleaner way, and I've seen this somewhere else
class ApplicationController < ActionController::Base
def get_module_name
#module_name = self.class.name.split("::").first
end
end
I recommend gsub instead of split. It's more effective that split given that you don't need any other module name.
class ApplicationController < ActionController::Base
def get_module_name
#module_name = self.class.to_s.gsub(/::.*/, '')
end
end

Resources