How to selectively deactivate Rails I18n translation_missing spans? - ruby-on-rails

In my Rails 5.2 app, almost all my UI, except the Admin area, is internationalized using t(). The "translation missing" spans often break the layout, but I figure that's a price worth paying in order to know when I may have misspelled a key or not committed a file or something. There is one place where the spans really don't work however, and that is when it comes to the self-described roles people have in my Groups. The roles can be pre-defined strings that are part of my en.yml, like "coordinator", "treasurer" and so on, but they can also be write-ins, like "minister of awesome". So doing t("groups.roles." + role) doesn't work because the "translation missing" span messes up the layout. This wasn't a problem in previous version of Rails because the "translation missing" spans were automatically disabled for the production environment and only the last part of the key was displayed, i.e. exactly the functionality I want here. In Rails 5.2 the spans also show up in production and this is a problem, at least for this part.
The functionality I'd really want, which used to be standard, is:
If the translation exists, display it.
If the translation doesn't exist, display the English string from en.yml (English is defined as a fallback language)
If the English doesn't exist either, display the final part of the key. With "translation missing" spans in development, without them in production.

I browsed all the answers on here about how to disable these error messages in production and the suggested solutions seem geared at Rails 4 or don't work. Or they have the side effect of deactivating fallback translations, which is an unacceptable trade-off for me because I need English fallbacks for much of my app. Overwriting or extending the TranslationHelper seems like a momentous task, especially since I'd only want the spans removed in production (like it used to work). So for my case and for now, I've decided against doing a complete override and I only changed the helper in the one place where I expect to have missing translations that are not a problem:
def human_role(role)
str = t("groups.roles." + role)
if str.include?("translation missing")
role
else
str
end
end

You can use :default option like this:
t("groups.roles.#{role}", default: role)
And if you want to have "translation missing" in development, without them in production (it's ugly a little):
t("groups.roles.#{role}", default: Rails.env.development? ? "#{role} translation missing" : role)

Related

Accessing models and modules in a Rails 7 initializer

This issue relates to a need to set a Rails config variable as the application boots, and the value of that variable needs to come from data in the database (which are then modified). So, I have an initializer with something like this:
require "#{Rails.root}/lib/modules/facet_altering.rb"
include FacetAltering
Rails.application.config.reject_subjects = FacetAltering.reject
The reject method is potentially slow and calls the Subject model (which includes some concerns).
If I try to require subject.rb, application_rb and the relevant concerns from app/models then I progress a bit further, but eventually get stuck on uninitialized constant Subject::MySpecialConcern.
There might be some better way to set the reject_subjects value; I'd prefer not to run FacetAltering.reject each time the value of reject_subjects is used, though this might be an easy 'fix' if no other solution arises (at the cost of slowing things down). Or, is there another way to access these classes as the application boots?
Edit: Following on from the comment below, this is in config/application.rb:
%W[#{Rails.root}/lib/modules #{Rails.root}/test/mailers/previews].each do |path|
config.eager_load_paths << path
end
This post offered a useful clue:
Rails Model no longer available in initializer after upgrade to Rails 7.0
So, putting my code in config/application.rb as follows did the trick:
config.after_initialize do
Rails.application.config.reject_subjects = FacetAltering.reject
end
Now to find the answer to RuntimeError: Foreign key violations found in your fixture data. Ensure you aren't referring to labels that don't exist on associations. and I might be able to complete this Rails 6 -> 7 upgrade!

Rails 4.1 locale and 'version' based on url (.com, .cz, .de, etc)

Hope somebody can point me at the right direction with this...
Basically, i have the locales setup and it works fine. However, i need to depending on how the user gets to the site (example_company.com, example_company.cz or example_company.de..) have slightly different content(views and layout).
I've managed to boil it down to a constant or env variable that if i was to run multiple instances of the site(1 for each country), i could set on the server so that i get the behaviour i need with 1 code base.
My question is, how are people dealing with this in general? is there any way i can serve all countries on the same instance and set some flag based on .com or .cz or whatever, that dictates which 'version' they get without effecting the url itself?
I already have the locales in the url and would prefer not to mix the two as i will have to support multiple languages for each version. For example, french and czech would still support english.. But if i go to the french one, i will only show 2 locales (french and english)...
Hope i managed to explain properly.. if not let me know and i will try again.
If you use Rails' built-in i18n support, you can easily select locales by TLD.
From the official Rails i18n guide:
One option you have is to set the locale from the domain name where your application runs. For example, we want www.example.com to load the English (or default) locale, and www.example.es to load the Spanish locale. Thus the top-level domain name is used for locale setting. This has several advantages:
The locale is an obvious part of the URL.
People intuitively grasp in which language the content will be displayed.
It is very trivial to implement in Rails.
Search engines seem to like that content in different languages lives at different, inter-linked domains.
You can implement it like this in your ApplicationController:
before_action :set_locale
def set_locale
I18n.locale = extract_locale_from_tld || I18n.default_locale
end
# Get locale from top-level domain or return nil if such locale is not available
# You have to put something like:
# 127.0.0.1 application.com
# 127.0.0.1 application.it
# 127.0.0.1 application.pl
# in your /etc/hosts file to try this out locally
def extract_locale_from_tld
parsed_locale = request.host.split('.').last
I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
end
Be sure to read the i18n guide in full. It covers how to use the built-in i18n support. A big advantage is you don't need separate views for each locale.

Rails project organization with many models

I'm working on a rails app that is starting to have what seems (to me) to be a lot of models. There are 15 right now, but I'm thinking about adding 3-4 more to serve as "tag" like models (I need more functionality than Acts As Taggable offers).
So, the reason this bugs me a bit, is that 7 of the 15 models belong to a common parent. Several are belong_to, and a few are has_and_belongs_to_many. All the new models I'm contemplating would belong_to the same parent as well.
So, what I'm wondering is, what is the best "Railsy" way of organizing this kind of situation?
Instead of app/models being super crowded with 6 "first-class" models and 10+ children of one of these, should/can I start using sub folders in my app folder? ie: app/models/parent/child.rb?
I know this is kind of an open-ended question, but I would really appreciate advice as to the best way to handle a rails project with a proliferation of models.
Thanks!
You can do this, I always do :)
Just beware of something: if you create a folder which has the name of one your models, it will fail. Actually, Rails will think you want to extend it.
So in your model folder, prepend the name of your class with whatever fancy you want.
Example: if you want to put models related to users, put them in models/user_related/
You'll have to add this to your application.rb file:
config.autoload_paths += Dir["#{Rails.root.to_s}/app/models/*"].find_all { |f| File.stat(f).directory? }
This will autoload all folders included in modelsdirectory.
I think apneadiving's answer is good approach
Based on research with activesupport 3.0.11, there are some rules to follow when choosing a directory name however:
The name must never match a constant in your system, or LoadError's could occur
The name must be able to be converted to a valid constant name, or NameError's will occur.
Explanation of problem #1
Apneadiving's example of a directory name app/models/user_related works as long as a
constant UserRelated is never used in your code. Otherwise a LoadError could
potentially happen.
For example, assume there was a model called UserProfile and the first time
rails sees the constant is in the UserRelated module. Rails will first try to
load a UserRelated\:\:UserProfile constant and failing that a UserProfile
constant.
If the user_profile file is at app/models/user_related/user_profile.rb, this
matches the underscored path of UserRelated\:\:UserProfile and the file would
be loaded expecting to define the UserRelated::UserProfile constant. This
would raise the following error because it really defines the UserProfile
constant.
Expected app/models/user_related/user_profile.rb to define UserRelated::UserProfile (LoadError)
This happens in the active support dependency code.
Explanation of problem #2
Another caveat is the directory name must be able turned into a valid ruby
constant name (although to follow #1 the constant should be undefined). For
example, if the directory name were app/models/user.related this would result
in the following error inside the active_support dependency code:
wrong constant name User.related (NameError)

Overriding default Rails date_select

So what I'd like to do is to override the default date_select method (I'd like to make an 'optional / unspecified' date input). What I've tried so far is this:
lib/overrides.rb
ActionView::Helpers::DateHelper::DateTimeSelector.class_eval do
def build_selects_from_types(order)
select = ''
order.reverse.each do |type|
separator = separator(type) unless type == order.first # don't add on last field
select.insert(0, separator.to_s + send("select_#{type}").to_s)
end
select.insert(0, '<p>HI!!</p>') # or whatever...
select.html_safe
end
end
I then required 'overrides' at the bottom of environment.rb but when starting WEBrick I get this error:
~/.rvm/gems/ruby-1.9.2-p0/gems/activesupport-3.0.0/lib/active_support/dependencies.rb:479:in
`load_missing_constant':
ActionView::Helpers is not missing
constant DateTimeSelector!
(ArgumentError)
So I obviously don't really know what I'm doing but this seems like a reasonable thing to attempt at least.
The error above seems to imply that it can't find the DateTimeSelector class but I've peered at the code in ~/.rvm/gems/ruby-1.9.2-p0/gems/actionpack-3.0.0/lib/action_view/helpers/date_helper.rb and I think I've got the module hierarchy right. Is it because it's a private Rails class?
Any thoughts are most welcome :)
In Ruby doesn't exist the concept of private class. Classes are never private.
The reason for the error is because the path is invalid. It should be
ActionView::Helpers::DateTimeSelector
not
ActionView::Helpers::DateHelper::DateTimeSelector
BTW, what you are trying to do is absolutely a bad idea. The fact that Ruby gives you the power of reopening classes and "patch" methods, doesn't mean you should do this for such this kind of customizations.
You should never make these chances to the Rails codebase unless you really know what you are doing. The risk is to break things that depends on this method.
The right way to go is do define a new helper and build your own logic.

Supporting different locale regions using Rails i18n

I'm using the standard Rails I18n API to localise some of our views. This is working really well, but we now have a few use cases for regional changes to the en locale.
The API guide mentions that this isn't supported directly, and other plugins should be used. However, I'm wondering whether there's a simpler way to do this.
I already have en.yml, so in theory I could just create en-AU.yml and en-US.yml which are effectively clones of en.yml but with a few regional changes applied. I could then add additional English - American and English - Australian options to our configuration which would map to the new region-specific locales and allow users to use a region-specific locale.
The only problem I can think of with this is that it isn't DRY -- I would have duplicate translations for all common English words. I can't see a way around this.
Are there any other disadvantages to this approach, or should I just bite the bullet and dive into one of the plug-ins such as Globalize2 instead?
The rails-i18n-translation-inheritance-helper is getting a bit old now, so here's my approach for a Rails 3.2 project.
If you keep both en-US and en-AU in the same en.yml file you can use the yml repeated node to have a super en section:
For example:
en: &en
errors:
messages:
expired: "has expired, please request a new one"
not_found: "not found"
en-US:
<<: *en
en-AU:
<<: *en
errors:
messages:
not_found: "tis not found"
I'm using translation inheritance helper plugin for this.
In newer versions of Rails/i18n, they've added a fallback feature. Works similar to the outdated translation inheritance helper gem
see this answer for more info: Fall back to default language if translation missing

Resources